Большой интерес пользователей к статье Учимся парсить сайты с библиотекой PHP Simple HTML DOM Parser показал, что тема парсеров очень актуальна. В продолжении темы, хочу рассказать, как можно парсить сайты используя JavaScript и всю мощь библиотеки jQuery, взамен Simple HTML DOM Parser.
Нет, мы не будем использовать для обработки js, какой-нибудь серверный интерпретатор, весь парсинг и обработка данных будет происходить на Вашей машине, в Вашем браузере. Браузером будет Google Chrome, а парсер мы реализуем в виде расширения Google Chrome Extension.
Почему Google Chrome, трудно сказать, самым верным ответом наверное будет: "А почему бы и нет?!". Не сомневаюсь, что тоже самое можно будет сделать и для Opera. Однако, эта статья не про написание расширений для браузера( хотя возможно Вы почерпнете для себя и здесь, что-то новое), а про то, как писать client-side парсеры на JavaScript.
Также хочу рассказать про преимущества, которые дает такой подход к написанию парсера.
Во первых: jQuery и JavaScript в целом обладает фантастическим набором методов для работы с DOM документа, Simple HTML DOM Parser тихо курит в сторонке. Навигация по дереву DOM браузер априори обрабатывает очень быстро, это собственно его нативный функционал.
Второе: по планете давным давно шагает WEB 2.0. Для тех кто в танке: веб второй версии подразумевает динамически меняющийся контент сайта. AJAX или просто замена определенного участка страницы через JS сводит на нет работу любого php парсера. Проиллюстрирую на примере:
1 2 3 4 5 | < html > < body onload = "document.body.innerHTML='Страница была создана динамически! Так нужный Вашему парсеру email равен leroy@xdan.ru'" > email:leroy*****.ru </ body > </ html > |
Полагаю Вы догадываетесь, что увидит написанный на php парсер, загрузивший данную страницу, и тупо проверяющий содержание тега body.
Использование браузера в качестве парсер-машины позволяет, обмануть сайт, и выполнить подобные скрипты, получив результирующую страницу.
Итак для начала напишем расширение для Chrome типа Hello World!!!, а затем будем наращивать его функционал. Далее я буду называть наше расширение - приложением, так как расширение увеличивает функционал самого браузера, либо добавляет какие-то фичи к сайту, мы же пишем настоящее приложение, которое работает на базе браузера.
Создайте пустую папку с каким-нибудь внятным названием на латинице. Я назвал парсер xdParser, так обзовем и папку.
В ней создадим два файла main.html и manifest.json, такого содержания:
manifest.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "name" : "xdParser v.1.0" , "version" : "1.0" , "description" : "Parser sites" , "permissions" : [ "http://xdan.ru/*" ], "app" : { "launch" : { "local_path" : "main.html" } }, "icons" : { "48" : "Spider-48.png" , } } |
Самым интересным параметром для нашего парсера здесь будет permissions, дело в том, что по умолчанию ajax не позволяет cross-domain запросы. Прописав нужный домен в массив permissions, мы сообщаем Google Chrome'у, о том что наш ajax будет использовать кросс-доменные запросы. Если же в массив добавить "<all_urls>", то ajax-запросы будут разрешены для любого домена.
main.html
1 | Привет Мир!!! |
Чтобы новое приложение не затерялось в безликой толпе, закиньте в созданную папку набор каких-нибудь иконок, к примеру эти. Иконки должны быть png, иначе они работают не везде. Наверно глюк.
Теперь приложение надо установить, кликаем на панели браузера по иконке с гаечным ключем(Настройки и управление Google Chrome), затем Инструменты -> Расширения, ставим в самом верху галку Режим разработчика
Далее кликаем Загрузить распакованное расширение... и указываем путь к вышеназванной папке. Если manifest.json валиден, то приложение установится и мы увидим его среди прочих.
Открываем новую вкладку, в ней переходим на слайд Приложения, и видим там свое.
С Hello World разобрались, теперь напишем наш первый парсер JavaScript на Google Chrome Extensions.
Изменим main.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | < html > < head > < meta http-equiv = "content-type" content = "text/html; charset=utf-8" /> < title >xdParser v1.1</ title > < link href = "css/main.css" rel = "stylesheet" /> < script type = "text/javascript" src = "/js/jquery-1.7.2.min.js" ></ script > < script type = "text/javascript" src = "/js/main.js" ></ script > </ head > < body > < img id = "progress" src = "/css/images/progress.gif" /> < input id = "starter" type = "button" value = "Запустить парсер" /> < div id = "resultbox" > </ div > </ body > </ html > |
Как видно из кода, необходимо создать две подпапки js и css. В js закинуть два файла jquery-1.7.2.min.js и main.js
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | ( function ($){ function ajaxStart(){ $( '#progress' ).show(); } function ajaxStop(){ $( '#progress' ).hide(); } function parserGo(){ ajaxStart(); var b = $.ajax( 'http://xdan.ru' ); b.done( function (d) { analysisSite(d); ajaxStop(); }); b.fail( function (e, g, f) { alert( 'Epic Fail' ); ajaxStop(); }) } function analysisSite(data){ var res = '' ; $(data).find( 'a' ).each( function (){ res+=$( this ).text()+ '=>' +$( this ).attr( 'href' )+ '' ; }) $( '#resultbox' ).html(res); } $( function (){ $( '#progress' ).hide(); $( '#starter' ).click(parserGo); }); })(jQuery); |
Тут все просто, на кнопку #starter вешается обработчик события onclick, функция parserGo. В ней мы загружаем главную страницу блога и при удачной загрузке вызываем функцию analysisSite. В которой происходит обработка полученных данных. Запустите парсер. Для примера я вывел все ссылки на главной сайта xdan.ru.
Теперь сделаем что-нибудь посложнее. Возьмем мой пример с парсингом фото из Яндекса при попмощи Simple HTML DOM
Изменим manifest.json
1 2 3 4 5 6 | ... "permissions" : [ "http://xdan.ru/*" , "http://images.yandex.ru/*" ], ... |
не забывайте перезагружать Ваше приложение в расширениях Google Chrome, там же где Вы его устанавливали. Под каждым расширением, в том числе и Вашем есть ссылка Перезагрузить
Иначе изменения внесенные в manifest.json не вступят в силу.
Далее изменим parserGo
1 2 3 | ... var url = 'http://images.yandex.ru/yandsearch?text=' +encodeURIComponent( 'Джессика Альба' )+ '&rpt=image' ; ... |
и analysisSite
1 2 3 4 | $imgs = $(data).find( 'div.b-image img' ); if ( $imgs.length ){ requrs($imgs,$imgs.length-1); } |
Как видите я ввел еще одну функцию requrs:
1 2 3 4 5 6 | function requrs(data,index){ res+= '<img src="' +data[index].src+ '"/>' ; fs.loadRemoteFile( 'image' +index+ '.jpg' ,data[index].src, function (){ if ( index>0 )requrs(data,index-1); else $( '#resultbox' ).html(res); }); } |
где fs это экземпляр класса fileStorage для работы с файловой системой. Для его инициализации Вам понадобится файл fileStorage.js и в самом начале main.js прописать создание экземпляра этого класса:
1 2 3 4 | ( function ($){ var fs = new xdFileStorage(); function ajaxStart(){ ... |
То, как он работает, это тема отдельной статьи, отмечу лишь одни грабли в его использовании: запись следующего файла на диск должна производится только после завершения записи предыдущего. Это очень важно учитывать в асинхронных приложениях, коим и является наш парсер. Поэтому метод loadRemoteFile , одним из своих параметров принимает callback функцию, которая выполняется только по окончании записи файла на диск, и рекурсивно вызывает requrs.
По работе с файлами рекомендую почитать цикл статей по работе с файловой системой в JavaScript Работа с файлами в JavaScript, Часть 1: Основы
В результате файлы с картинками будут аккуратно сложены в папку виртуальной файловой системы Хрома, у меня это папка лежит тут: C:\Users\Leroy\AppData\Local\Google\Chrome\User Data\Default\File System, ее название может быть разным и создается самим Хромом. Повторюсь, что это лишь виртуальная файловая система, и картинок с расширением *.jpg Вы здесь не найдете. Все файлы лежат с восьмизначными числовыми именами, начиная от 00000000, без расширения. Но если открыть их какой-нибудь программой для просмотра изображений, они прекрасно откроются. А в браузере на вкладке с нашим приложением мы увидим прекрассную Джессику:
остальные примеры из парсинг фото из Яндекса при попмощи Simple HTML DOM переделайте сами, это не сложно. Мы же рассмотрим более реальный пример парсера.
Есть некая доска объявлений http://www.skelbiu.lt , к примеру нам понадобились все номера телефонов из категории Недвижимость. Для этого нам потребуется спарсить страницу http://www.skelbiu.lt/skelbimai/nekilnojamasis-turtas/ и получить список подкатегорий с их url.
DOM Inspector Google Chrome показывает нам, что все ссылки находятся так:
1 2 3 4 5 6 7 8 9 | function parserGo(){ $.ajax( 'http://www.skelbiu.lt/skelbimai/nekilnojamasis-turtas/' ).done( function (data) { var s = '' ; $(data).find( '#categoriesDiv a' ).each( function (){ s+= this .innerText+ '-' + 'http://www.skelbiu.lt' +$( this ).attr( 'href' )+ '' }) $( '#resultbox' ).html(s) }); } |
Отлично, у нас есть названия категорий и их адреса. Теперь необходимо выяснить сколько в каждой категории страниц с объявлениями, и перебрать их все. Для этого найдем на странице ссылку на последнюю страницу в подкатегории. Первая проблема с которой нам придется тут столкнутся, это то, что эта ссылка не имеет уникального идентификатора. Однако мы видим, что следующая за ней ссылка на Следующую страницу имеет id=nextLink, значит найти нужную нам ссылку можно так $last = $(data).find('#nextLink').parent().prev().find('a');
Также выясняется, что страницы работают без ЧПУ и у каждой подкатегории есть числовой идентификатор, он нам понадобится поэтому получим его из href найденной ссылки при помощи регулярного выражения var cat = $last.attr('href').match(/([0-9]+)\?&category_id=([0-9]+)&orderBy=[0-9]+/);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function parserGo(){ $.ajax( 'http://www.skelbiu.lt/skelbimai/nekilnojamasis-turtas/' ).done( function (data) { var s = '' ; $(data).find( '#categoriesDiv a' ).each( function (){ s+= this .innerText+ '-' + 'http://www.skelbiu.lt' +$( this ).attr( 'href' )+ '' var name = this .innerText; var url = 'http://www.skelbiu.lt' +$( this ).attr( 'href' ); $.ajax(url).done( function (data) { $last = $(data).find( '#nextLink' ).parent().prev().find( 'a' ); // находим ссылку на последнюю в подкатегории страницу var maxpage = parseInt($last.text()); // сколько всего страниц var cat = $last.attr( 'href' ).match(/([0-9]+)\?&category_id=([0-9]+)&orderBy=[0-9]+/); // выесняем id категории requrs( cat[2],maxpage,1,url); // запускаем рекурсивную обработку страниц }); }) $( '#resultbox' ).html(s) }); } |
Итак у нас есть id категории cat[2] и общее количество страниц maxpage. Теперь перебираем их все с 1 до maxpage. Делать это циклом используя асинхронные запросы нецелесообразно, поэтому используем рекурсивную функцию function requrs(catid,fullCount,tik,url), где catid - это id категории, fullCount - общее количество страниц в категории, tik - текущий номер страницы, url - ЧПУ адрес подкатегории.
1 2 3 4 5 6 7 8 9 10 11 12 | function requrs(catid,fullCount,tik,url){ if ( tik>fullCount ) return 0; // конец рекурсии console.log( 'Будет спарсена страница ' +tik+ ' категории ' +catid); $.ajax(url+tik+ '&category_id=' +catid+ '&orderBy=1' ).done( function (data) { analizeSite(data, function (){ if (tik+1<=fullCount){ requrs(catid,fullCount,tik+1,url); // рекурсивно запускаем функцию } },url); }); } |
Осталось распарсить полученные страницы на объявления, загрузить страницу каждого объявления и выдернуть из нее нужный нам телефон. Это сделает функция analizeSite. Каждое объявление можно найти на странице подкатегории таким образом: div.adsInfo a, а на странице с объявлением телефон можно найти регулярным выражением /<\!--googleoff\: index-->([\+0-9]+)<!--googleon: index-->/ посмотрите сами http://www.skelbiu.lt/skelbimai/butas-sviesus-ir-siltas-butas-sviesus-ir-siltas-13926351.html
1 2 3 4 5 6 7 8 | function analizeSite(data,f,url){ $(data).find( 'div.adsInfo a' ).each( function (){ var dt = $.ajax({url:url+$( this ).attr( 'href' ),async: false }).responseText; if (mch = dt.match(/<\!--googleoff\: index-->([\+0-9]+)<!--googleon: index-->/)) console.log(ticker+ ')Cпарсена страница ' +url+$( this ).attr( 'href' )+((mch&&mch.length>1)? ' найденный номер ' +mch[1]: '' )); }) if (f)f(); // после того как будут спарсены все объявления со страницы, вызываем callback функцию, т.е. запускаем requrs с новым параметром tik } |
Результат мы увидим в консоли JavaScript, увидеть которую можно нажав ctrl+shift+j
Вот и все, как всегда выкладываю все исходники с парсером xdParser v.1.1 Google Chrome Extension
Комментарии
XMLHttpRequest cannot load http://im6-tub-ru.yandex.net/i?id=183737286-42-72. Origin chrome-extension://lpobnjppnenalebacffhippolhljlmm g is not allowed by Access-Control-Allow-Origin.
как исправить?
дописал явно Url Яндекса и сайта с объявлениями..., но проблема не разрешилась (((. Что можно ещё сделать? Вопрос очень интересен...
не работает. jquery стягивает исходник, однако это не результирующая страница. т.е. она точно такая же, как если бы я зашел на целевую страницу и посмотрел её исходный код в файрфоксе или хроме. в чем проблема?
я сделал все по шагам, как здесь описано, и получил все тоже самое что можно получить с помощью PHP Simple HTML DOM Parser или обычного jQuery без заморачивания с созданием расширения для браузера?
Если есть десятки способов получить результирующую страницу (со всеми манипуляциями), почему вы не описали ни одного?
Я или что-то не понимаю, или какое-то шапито) Статья декларирует то, как получить динамически-подгружаемый контент, а по факту описывается как получить статичный контент, но через доп. грабли в виде браузера?)
Объясните плз.
Хотя идея с iframe'ом забавна) Спасибо.
Что если взять за основу этот PHP class: http://sourceforge.net/projects/snoopy/?source=recommended
Snoopy is a PHP class that simulates a web browser. It automates the task of retrieving web page content and posting forms
и когда для проверки вывожу $('#resultbox').html(data); страницу целиком. то вижу что ссылки выдачи (например запроса - блоггер) отображаются не так https://www.г- угла.com.ua/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CC0QFjAA&url=http%3A%2F%2Fwww.blogger.com%2F&ei=RbhrUuflFcHt4gTf0IGABA&usg=AFQjCNGb2S_ihucIn-YF2o0ZlC3KCh92Aw&sig2=tTNpq2lzlkER32m60Te6RQ&bvm=bv.55123115,d.bGE
а в ввиде сразу готового линка на данный сайт www.blogger.com
Как мне получить именно тот хитрый линк ?
{ "48": "Spider-48.png", } - тут последняя запятая лишняя,
в манифест надо добавить версию манифеста - "manifest_version": 2,
а ссылки на сами сайты не выдает. Как обойти их защиту?
Почему рекурсия,а не цикл??
b.fail(function (e, g, f) {
alert('Epic Fail');
Здравствуйте подскажите откуда взялись эти e,g,f и зачем?