Большой интерес пользователей к статье Учимся парсить сайты с библиотекой 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 парсера. Проиллюстрирую на примере:

<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

{ 
  "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

Привет Мир!!!

Чтобы новое приложение не затерялось в безликой толпе, закиньте в созданную папку  набор каких-нибудь иконок, к примеру эти. Иконки должны быть png, иначе они работают не везде. Наверно глюк.

Теперь приложение надо установить, кликаем на панели браузера по иконке с гаечным ключемНастройки и управление Google Chrome(Настройки и управление Google Chrome), затем Инструменты -> Расширения, ставим в самом верху галку Режим разработчика

Далее кликаем  Загрузить распакованное расширение... и указываем путь к вышеназванной папке. Если manifest.json валиден, то приложение  установится и мы увидим его среди прочих.

Открываем новую вкладку, в ней переходим на слайд Приложения, и видим там свое. 

С Hello World разобрались, теперь напишем наш первый парсер JavaScript на Google Chrome Extensions.

Изменим main.html

<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

(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')+'<br/>';
	})
	$('#resultbox').html(res);
}
$(function(){
	$('#progress').hide();
	$('#starter').click(parserGo);
});
})(jQuery);

Тут все просто, на кнопку #starter вешается обработчик события onclick, функция parserGo. В ней мы загружаем главную страницу блога и при удачной загрузке вызываем функцию analysisSite. В которой происходит обработка полученных данных. Запустите парсер. Для примера я вывел все ссылки на главной сайта xdan.ru.

Теперь сделаем что-нибудь посложнее. Возьмем мой пример с парсингом фото из Яндекса при попмощи Simple HTML DOM

Изменим manifest.json

...  
"permissions": [
     "http://xdan.ru/*",
     "http://images.yandex.ru/*"
   ],
...

не забывайте перезагружать Ваше приложение в расширениях Google Chrome, там же где Вы его устанавливали. Под каждым расширением, в том числе и Вашем есть ссылка Перезагрузить

 Иначе изменения внесенные в manifest.json не вступят в силу.

Далее изменим parserGo 

...
var url = 'http://images.yandex.ru/yandsearch?text='+encodeURIComponent('Джессика Альба')+'&rpt=image';
...

и analysisSite

$imgs = $(data).find('div.b-image img');
if( $imgs.length ){
	requrs($imgs,$imgs.length-1);
}

Как видите я ввел еще одну функцию requrs:

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 прописать создание экземпляра этого класса:

(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, без расширения. Но если открыть их какой-нибудь программой для просмотра изображений, они прекрасно откроются. А в браузере на вкладке с нашим приложением мы увидим прекрассную Джессику:

Работа парсера Google Chrome Extension xdParser v.1.1. и прекрассная Джессика Альба

остальные примеры из парсинг фото из Яндекса при попмощи Simple HTML DOM переделайте сами, это не сложно. Мы же рассмотрим более реальный пример парсера.

Есть некая доска объявлений http://www.skelbiu.lt , к примеру нам понадобились все номера телефонов из категории Недвижимость. Для этого нам потребуется спарсить  страницу http://www.skelbiu.lt/skelbimai/nekilnojamasis-turtas/ и получить список подкатегорий с их url.

Парсер доски объявлений Google Chrome Extension

DOM Inspector Google Chrome показывает нам, что все ссылки находятся так:

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')+'<br/>'
		})
		$('#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]+/);

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')+'<br/>'
			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 - ЧПУ адрес подкатегории.

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

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

Работа Google Chrome Extension xdParser v.1.1 парсера сайта http://www.skelbiu.lt

Вот  и все, как всегда выкладываю все исходники с парсером xdParser v.1.1 Google Chrome Extension

Оставлять комментарии могут только зарегистрированные пользователи

Комментарии  

Максим
# Максим 26.07.2012 09:50
скачал исходники с парсером, прикрутил приложение, в main.html указал использовать js/main.js, кликнул "запустить парсер", в браузере тишина, в консоли ошибка:



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.



как исправить?
Leroy
# Leroy 26.07.2012 16:10
проблема с кросссайтовым аяксом, попробуйте явно вписать урл сервера в permissions вместо или вместе с
<all_urls>
Юрий
# Юрий 19.08.2012 17:29
Возникла такая же проблема.

дописал явно Url Яндекса и сайта с объявлениями..., но проблема не разрешилась (((. Что можно ещё сделать? Вопрос очень интересен...
Саша
# Саша 22.08.2012 20:32
Что делать если manifest.json не валиден, после переименвоания имеет значок ярлыка, ие устанавливаеться как расширение для браузера, что делать?
игорь
# игорь 03.11.2012 08:52
"Использование браузера в качестве парсер-машины позволяет, обмануть сайт, и выполнить подобные скрипты, получив результирующую страницу. "



не работает. jquery стягивает исходник, однако это не результирующая страница. т.е. она точно такая же, как если бы я зашел на целевую страницу и посмотрел её исходный код в файрфоксе или хроме. в чем проблема?
Leroy
# Leroy 03.11.2012 22:47
ну так надо загружать не jquery =) есть пара десятков других способов. а уже потом парсить через jquery то что нагрузилось
игорь
# игорь 03.11.2012 23:54
йо-моё=) так а какой смысл несет эта статья тогда?

я сделал все по шагам, как здесь описано, и получил все тоже самое что можно получить с помощью PHP Simple HTML DOM Parser или обычного jQuery без заморачивания с созданием расширения для браузера?



Если есть десятки способов получить результирующую страницу (со всеми манипуляциями), почему вы не описали ни одного?

Я или что-то не понимаю, или какое-то шапито) Статья декларирует то, как получить динамически-подгружаемый контент, а по факту описывается как получить статичный контент, но через доп. грабли в виде браузера?)



Объясните плз.
Leroy
# Leroy 04.11.2012 01:32
загружаем в ифрейм и парсим через джикуери, на пхп вы такого не сделаете. в том прелесть парсинга через js, естественное исполнение кода в браузере никаким эмулятором не выполнишь
игорь
# игорь 15.11.2012 05:39
Все что хотел сделать спарсить, сделал с помощью PhantomJS.

Хотя идея с iframe'ом забавна) Спасибо.
Ohotnik
# Ohotnik 04.06.2013 06:05
Прива! а нельзя этот парсер сделать независимым от браузера, чтобы можно было делать все на странице своего сайта?

Что если взять за основу этот 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
Leroy
# Leroy 06.06.2013 17:24
вам нужен серверный парсер, а тут описан клиентсий. Еще будет полезна к прочтению эта статья
Comred
# Comred 10.07.2013 06:42
Здравствуйте, я застрял на стадии добавления manifest.json в гугл хром. Нажимаю Загрузить распакованное расширение... И мне выдает ошибку Could not load extension "D:\xdParser'. Manifest is not valid JSON. Line:13, column:37, Trailing comma nit allowed. Я так понял какая-то проблема с завершающей запятой на линии 13 в колонке 37, но никакие манипуляции с запятыми не помогли. Подскажите пожалуйста в чем дело.
Leroy
# Leroy 10.07.2013 12:38
ну я не телепат, код в студию
Cyanide
# Cyanide 12.08.2017 15:54
В файле манифеста, описанного в статье ошибка - "icons": { "48": "Spider-48.png", }. В фигурных скобках, после запятой необходимо описать остальные иконки: "icons": { "16": "Spider-16.png", "48": "Spider-48.png", "128": "Spider-128.png" } либо убрать запятую и оставить описание только одной иконки
Ohm-
# Ohm- 17.07.2013 22:20
Сделал все как в примере. Получил ссылки с вашего сайта. Вопросы: Как можно парсить PHP страницу? Если доступ на сайт, только через админ панель, можно ли вообще парсить, такую страницу? http://kirih.tmweb.ru/
Антон
# Антон 30.07.2013 20:44
Добрый день! Скачал и установил xdParser-v.1.1, но почему-то не появляется иконка плагина! Что делать?
irirni
# irirni 10.07.2014 16:17
У меня точно такая же проблема.. Подскажите пожалуйста
strongest
# strongest 15.08.2013 01:24
Подскажите как распознавать форму на сайте и заполнять поля формы? Например есть несколько тысяч объявлений, ручками разумеется не охота это делать. Данные находятся в базе данных и выводятся в формате json.
Leroy
# Leroy 15.08.2013 02:09
в каком смысле распознать форму? ­document.getElementsByTagName('form') не?
SergG
# SergG 17.09.2013 05:48
Скажите пожалуйста, возможно ли использовать php при написании плагина для chrome?
Leroy
# Leroy 17.09.2013 13:38
PHP это серверный язык. Чтобы его запустить не достаточно одного браузера. Расширения для Хрома тем и хороши, что для работы, кроме самого Хрома ничего не требуют. Однако, к примеру в паре моих плагинов для CKEditor syn и translater, которые тоже работают на js, я использовал php на сервере, как облако.
Николай
# Николай 22.10.2013 04:26
а эмуляцию клика по элементу можно делать?
Leroy
# Leroy 22.10.2013 17:35
это может только js
Николай
# Николай 26.10.2013 20:49
с эмуляцией клика разобрался . пробую спарсить данные поисковика (г- угла) по запросу.

и когда для проверки вывожу $('#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

Как мне получить именно тот хитрый линк ?
Leroy
# Leroy 10.07.2014 17:37
насколько вижу он есть в параметре url запроса выше
Макс
# Макс 24.05.2015 17:19
Полезная статья. Подскажи, как авторизацию при помощи cookie подцепить к такому расширению?
Сэм
# Сэм 11.09.2015 20:02
Статья хорошая, но надо исправить в ней:
{ "48": "Spider-48.png", } - тут последняя запятая лишняя,
в манифест надо добавить версию манифеста - "manifest_version": 2,
Азат
# Азат 03.03.2016 09:22
не работает с https
ALEKSANDR
# ALEKSANDR 20.09.2016 16:44
Попробовал спарсить ссылки на сайт http://www.liveinternet.ru/rating/ru/auto по той же схеме что и в первом примере, выдало только =>/=>/stat/=>/add=>undefined=>/help/geo-rating.html=>#=>/corp/=>mailto:
а ссылки на сами сайты не выдает. Как обойти их защиту?
Dima
# Dima 17.05.2017 18:36
Итак у нас есть id категории cat[2] и общее количество страниц maxpage. Теперь перебираем их все с 1 до maxpage. Делать это циклом используя асинхронные запросы нецелесообразно, поэтому используем рекурсивную функцию
Dima
# Dima 17.05.2017 18:37
Итак у нас есть id категории cat[2] и общее количество страниц maxpage. Теперь перебираем их все с 1 до maxpage. Делать это циклом используя асинхронные запросы нецелесообразно, поэтому используем рекурсивную функцию

Почему рекурсия,а не цикл??
Leroy
# Leroy 18.05.2017 06:32
Если делать циклом, то 1000 страниц - 1000 одновременных запросов к серверу. Все они будут работать асинхронно. Часть потеряется, часть загрузит сервер и т.п. Поэтому рекурсия, как только закончился один запрос, вызываем другой
Виталий.h
# Виталий.h 17.09.2019 18:04
Код:b.fail(function (e, g, f) {

    alert('Epic Fail');


Здравствуйте подскажите откуда взялись эти e,g,f и зачем?