Пишем javascript-парсер при помощи Google Chrome Extension
Большой интерес пользователей к статье Учимся парсить сайты с библиотекой 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), затем Инструменты → Расширения, ставим в самом верху галку Режим разработчика
Далее кликаем Загрузить распакованное расширение... и указываем путь к вышеназванной папке. Если 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')+'';
})
$('#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, без расширения. Но если открыть их какой-нибудь программой для просмотра изображений, они прекрасно откроются. А в браузере на вкладке с нашим приложением мы увидим прекрассную Джессику:
остальные примеры из парсинг фото из Яндекса при попмощи Simple HTML DOM переделайте сами, это не сложно. Мы же рассмотрим более реальный пример парсера.
Есть некая доска объявлений http://www.skelbiu.lt , к примеру нам понадобились все номера телефонов из категории Недвижимость. Для этого нам потребуется спарсить страницу http://www.skelbiu.lt/skelbimai/nekilnojamasis-turtas/ и получить список подкатегорий с их url.
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')+''
})
$('#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') + ''
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());// сколько всего страниц
// выясняем id категории
var cat = $last.attr('href').match(
/([0-9]+)\?&category_id=([0-9]+)&orderBy=[0-9]+/
);
// запускаем рекурсивную обработку страниц
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]:''));
})
// после того как будут спарсены все объявления со страницы,
// вызываем callback функцию, т.е. запускаем requrs с новым параметром tik
if (f) f();
}
Результат мы увидим в консоли JavaScript, увидеть которую можно нажав ctrl+shift+j
Вот и все, как всегда выкладываю все исходники с парсером xdParser v.1.1 Google Chrome Extension.
Источник: http://xdan.ru/pishem-javascript-parser-pri-pomoshhi-google-chrome-extension