Как стать автором
Обновить

Комментарии 42

Хочется обратить внимание, что PhantomJS можно запускать из без java (т.е. Selenium Standalone Server не нужен). Если при этом используется версия ниже 2, то фаерволом нужно закрыть 8910 порт (по умолчанию) или тот, на котором вы его стартанули. Дело в баге ghostdriver в результате чего он слушает 0.0.0.0 адрес, т.е. становиться доступен всем и если сервер имеет выход наружу, то очень быстро находятся желающие порулить вашим фантомом.
Сейчас проверили старый вебдрайвер эксплорера, тоже биндится на 0.0.0.0. Хорошее замечание, спасибо вам!
Можно запускать и силениум с ноды. Вообщем предпочитаю силениум. Фантом слишком лоу лвл, каспер и прочии надстройки над фантомом часто огорчают тем, что там далеко не последние движки, что может повлиять как и на тесты так и на парсинг.
конечно меня захейтят, но гоу имеет куда более лучшею динамику чем тот же груви!
Дело предпочтений. Кому на чем удобнее писать. Если нужны библиотеки, доступные в JVM, то груви удобнее
Спасибо, познавательно.
Но вообще, из того, что пробовал использовать для написания парсеров, лучше nw.js+jQuery не видел.
Пожалуйста!
К сожалению не сталкивался с nodejs, так что пока поверю на слово
Это с полным построением DOM и возможность рендеринга в скриншот? Если да, то какая скорость и сколько под себя требует ресурсов?
В примере собственно и есть скриншот, если вы про графический
Не, я про nw.js+jQuery уточняю.
Node-webkit(nw.js) это гибрид ноды и webkit — движка того же хрома, к примеру. Так что да, он умеет большую часть из того, что умеет нормальный браузер, соответственно, у него и запросы по ресурсам приблизительно те же(запустил сейчас просто окно с гуглом, отожрало около 140МБ). Впрочем, большую часть отъедает, скорее всего, рендер.
По скорости работы не замерял, основным критерием в большинстве случаев служило удобство. Если не хватает клиентского/нодовского js'а, всегда можно дописать либо использовать код на плюсах при помощи аддонов либо через node-ffi, либо код на C# через edge.js. Главный же профит для парсеров это, конечно же, использование jQuery. К примеру, это может выглядеть как-то так:

//функция для построения DOM
var _html=function(html){ 
  return $('<div></div>').html(html);
};

//К примеру, скомбинируем node'овский request и jQuery
request.get(url, function(err,res,html){
    var container=_html(html);
    var name=container.find('.name span').text().trim();
}); 

//Или то же самое на чистом jQuery
$.get(url, function(html){
    var container=_html(html);
    var name=container.find('.name span').text().trim();
});

Понятно. Как вижу по ресурсам примерно тот же фантом. На парсинге считаю все же XPath более удобным и мощным. В общем профита по ресурсам не даст. Я просто подумал было, а вдруг…
Ну, как говорится — не проверишь, не узнаешь)
Не столь функционален, но для интереса, можно глянуть и сюда: jsoup.org
Спасибо за ссылку. В похожем контексте использовал библиотеку HtmlUnit, которая с ограничениями, но поддерживает и javascript клиентский
Попробовал использовать jsoup. Огорчает отсутствие возможности выборки по xpath. В итоге воспользовался библиотекой htmlunit. Они в одной категории по сути
Вот кстати, на счет агента лучше маскироваться под google bot'а.
А IP клиента как на гугловые заменит? ;-)
А их почти никто и не проверяет. IPB к примеру хватает только агента, и он считает вас гуглом.
«Почти» не знает ни кто. Поэтому эти «никто» просто кучка школьников, не более. Которые не знают, что есть PTR.
Лично для меня кейс «в юзерагенте поисковый сервис, а PTR не сервиса» четкий триггер добавления такого адреса в черный список фаервола.
А как fantom driver по производительности себя ведет? Хотя бы в сравнении с apache httpclient+htmlcleaner или jsoup (естественно речь идет о парсинге чистого html, без js)
Ну мы сейчас будем сравнивать сферического коня в вакууме. Конечно jsoup быстрее, но он и десятой части не делает того, что фантом
На самом деле мне и интересен этот конь. Мне это интересно, чтобы прикинуть примерное время парсинга.
Надо писать бенчмарк и измерять
От долго до очень долго. Поэтому если есть возможность не использовать полный DOM с рендирингом, нужно не использовать их. А на сколько долго в каждом случае будет по разному.

Клинический пример из личной практики. Парсер на PHP+SpiderMonkey+curl написанный кастомно под некий шаблон страницы отрабатывает не дольше чем 0,5 сек. Эта же страница отработанная через парсер на базе фантома отрабатывает что-то около минуты. Но фантом на супорте именно кода парсера сильно проще и подвержен меньшему числу поломок из-за изменения страницы. Но когда основным критерием была именно скорость работы (страниц было очень много), то он был заменен на означенную связку на базе SpiderMonkey. Поддерживать стало сложнее, но получать данные стали с требуемой скоростью.
Причем фантом вебдрайвер добавляет задержку по сравнению с решением внутри JVM, так как взаимодействие между фантомом и джава процессом с веб драйвером на основе REST API и в любом случае это межпроцессное взаимодействие
Что легко невилируется запуском фантома в режиме вебдрайвера. Что впрочем не решает вопроса долгой и дорогой работы данного решения, особенно во второй ветке.
Нельзя сравнивать решения без js и решения с js с полным построение DOM. Конечно же первый рвут последних в клочья. Поэтому если есть возможность не использовать PhantomJS и подобные решения, нужно не использовать его. Ибо да, долго, дорого (по ресурсам). Но есть кейсы в которых заменить его нечем ибо несмотря на все проблемы целесообразнее использовать именно его (т.е. легче на сапорте).

Там где его скорости не хватает, но все же нужен JS, то можно использовать любой JS движок (v8 к примеру). Сапорт приложения усложняется, скорость чуть хуже, чем у простых текстовых парсеров на регулярках. Я, к примеру, использую SpiderMonkey из PHP там, где требуется поддежка JS, но где скорости фантома не хватает или нет под него железа (держать отдельный сервер с ХХ ГБ озу готов не каждый проект несмотря на относительную дешевизну соверменных серверов).
<?php
$pos2 = 0;
for ($i=0; $i<substr_count($где_искать, "что_искать"); $i++) {
	$pos1 = stripos($где_искать, "<открывающий_тег", $pos2);
	$pos2 = stripos($где_искать, "</закрывающий_тег>", $pos1);
	$temp = substr($где_искать, $pos1 + strlen(открывающий_тег), $pos2 - $pos1 - strlen(открывающий_тег));
}
?>


а я до сих пор древним методом страницы парсирую =(
Значит просто не приходилось парсить сайты с обилием AJAX или построенных на JS. Но даже для сайтов выдающих статический HTML использовать XPath удобнее, чем регулярки. На супорте проще. Потому что у меня были кейсы когда нужно получить содержимое тега который не уникален (допустим тупо span без каких либо классов) и который находится где-то в разметке причем имеет смысл именно контексте этой разметки. Утрированно "достать содержимое span который находится Х-ым потомком Y тега который в свою очередь является братом картинки Z которая с свою очередь находится где-то среди потомков div-а с классом header". Регулярка под такой контекст получалась бы совершенно дикой и трудночитаемой. Работая с регулярками мы описываем не логическую структуру данных которые хотим получить, а по сути физическую. Регулярки более низкоуровневы.

Ну и каждый инструмент должен использоваться в задачах под которые он заточен. Регулярки — работа с символами текста, XPath — навигация по логической структуру дерева без углубления в низкоуровневые аспекты.
Хочу поделиться опытом использования old-school -ного парсера вместе с PhantomJS, думаю опыт может быть полезен, плюс тут есть цифры на которых можно «пощупать» скорость работы.
Не так давно был у меня проект под который надо было парсить сайт с недвижимостью, python + requests + beatyful soup и вопрос решен довольно быстро. Навигация по html-дереву через xpath, регулярки только для анализа данных, но никак не структуры. Задача имела временные ограничения — работать парсер должен был не более 6ти часов в день. Среднее количество записей которые собирались с сервера — 67.000. Парсер был разбит на задачи, т.е. есть головной скрипт который производит общий аудит и создает отдельные задачи на парсинг, 8 воркеров отведено было для решения этих задач, каждый воркер мог еще создать отдельные задачи, на каждую из задач опять же отводилось 8 воркеров. Чтобы было понятно — сбор данных одна очередь, сбор изображений другая очередь, анализ урлов уводящих со страницы — третья. Такой вот парсер в работе был совершенно не заметен по нагрузке, и в свои 6 часов укладывался с лихвой. Потом на сайте-доноре произошли изменения которые без исполнения js никак не обработать. И тут я подключил к этому делу фантом. 12воркеров запускавших фантомы отжирали несколько гигов памяти, периодически они (экземпляры фантомов) не могли нормально «умереть» и не освобождали ресурсы, что в конечном итоге приводило к отказу сервера, т.к. в особо плохих случаях, когда проблема возникала очень часто — память на сервере просто заканчивалась физически. А это было 16 гигов оперативы и 16 гигов свапа. Данный вопрос решился относительно быстро, пайтон воркеры запускали phantomjs как обычную консольную команду и слушали output, это не было использование какого-то драйвера. В итоге, при плохом коннекте к сайту (думаю на стороне сайта-донора была защита по кол-ву обращений с одного IP) Phantomjs порой не мог загрузить страницу целиком, происходил обрыв связи, или получал ответ слишком поздно. Опять же, все мои скрипты для phantomjs-а исполнялись после того как DOM полностью построен, и задержка/обрыв связи при загрузке какого-нить скрипта блокировали исполнение задач моего phantom-а, соответственно как только я поставил таймеры на самоубийство фантома — проблема ушла. Однако пару раз сервер ночью у нас таки лёг. Но баги-то починить можно, а вот со скоростью работы сделать ничего нельзя :( Скорость сбора данных упала до 6-10 тысяч записей за отведенные 6 часов. Что любопытно, увеличение кол-ва воркеров выше 12 в моем случае приводило только к замедлению работы. Кол-во отказов в доступе/обрывов связи с сайтом-донором возрастало жутко, но это всё-таки не проблема решения, а проблема иной плоскости.
Небольшой вопрос из разряда «здрасте, я Капитан Очевидность»: какого рода произошли изменения в js? Перенесли часть логики на ajax? Если да-то знатно проще самому выполнять ajax запрос и получать готовый результат в виде json или чего они там юзают. Если нет — то какого рода были вообще изменения?

А вообще для вещей подобного плана кроме nw.js или питона сокомпани неплохо работает связка awesomium+C#+CSQuery(ну или любой другой хороший парсер). Если нужна огромная скорость парсинга — то code.google.com/p/majestic13 (крайне неудобная гадость, но по скорости работы уделывает большинство из парсеров, которые я вообще пробовал(давал прирост где-то на 40мс со страницы, что при большом кол-ве страниц довольно-таки заметный прирост). Парсил когда-то магазинчик, разгонял где-то под 40-50 страниц в секунду за счет многопоточности, правда ЦПУ жрало неимоверно).
Часть нужных ссылок была переделана из обычного тега «a» в js реализацию по onClick, и, к сожалению, место откуда берутся данные весьма не очевидно, а дебагать обфусцированный js код очень сомнительное удовольствие.

неплохо работает связка awesomium+C#+CSQuery

К счастью/сожалению (по желанию) я не знаю C# и .net платформу. Знаю Python и js, на них и писал :)

Что касается скорости парсинга, как я писал выше наибольшая проблема была в качестве связи с сайтом-донором. Сбросы соединений, 502-ые и т.д. не починить ничем, кроме разнесения процесса парсинга на разные сервера чтобы стучать с разных IP. Это при условии что я прав и такое поведение — это защита от слишком большого количества запросов от одного хоста, а не просто дохлый хостинг.
В общем-то, с учетом того, что эффект один и тот же(что так, что так дохнет немилосердно), разница невелика)
Данный вопрос решился относительно быстро, пайтон воркеры запускали phantomjs как обычную консольную команду и слушали output, это не было использование какого-то драйвера.

Оценка прироста от такого решения не проводилась? И я не очень уловил, как тогда оправляются в него команды?

Я к примеру делал так, из php запускал пачку фантомов в фоновом режиме (т.е. после завершения работы php продолжали работать). Каждый фантом на своем локальном порту. Дальше воркеры пускаемые по крону ходили на них через webdriver с использованием библиотечки от фейсбука. По ощущениям фантом занимался каким-то соплежуйством где-то глубоко внутри (во 2-ой версии просто беда) и оверхед на коннекты был небольшой.

думаю на стороне сайта-донора была защита по кол-ву обращений с одного IP

Фантомы можно запускать с использованием проксей. Я вот запускал количество фантомов равным количеству проксей. Это частично решает вопрос со скоростью обхода (воркеры тупо берут активные прокси по round-robin).

упала до 6-10 тысяч записей за отведенные 6 часов

Думаю проксями решилось бы. На одной крайне кривом сайте на базе phpBB отслеживали онлайнеров. Удавалось за 15-20 минут обходить около 1500 страниц. Крутилось что-то около 30 Phantom2 с расходом ~20ГБ ОЗУ и большим расходом ЦПУ хоть и не в полку. LA был высокий, но в целом сервер работал нормально обслуживая внутренние веб сервисы.

Кстати, вопрос. В попытке ускорить парсинг не возникала идея перенести бизнес логику парсинга в подлючаемые к фантому JS? У меня возникали большие подозрения, что это может ускорить процесс, но руки до подобной переделки уже не дошли.
Оценка прироста от такого решения не проводилась? И я не очень уловил, как тогда оправляются в него команды?

Нет, не производилось. Да и это решение было выбрано не совсем осознанно, надо было использовать какой-то headless браузер. Быстрый гуглинг для поиска решения подходящего под пайтон не принесли адекватных плодов, а с PhantomJs опыт уже был. Потому быстренько его и использовал. В тот момент когда возникла необходимость в парсинге с учетом js, было слишком мало времени на анализ, планирование, прототипирование… надо было сделать еще вчера. Потому получилось так, как я описал.
Как фантом получает команды? Через консоль. /usr/bin/phantomjs /my/path/to/script.js params а в самом script.js есть всё, что надо для работы. Результат отдаем обратно в output и слушаем пайтоном. Т.е. там нет полноценного общения, запуск и получение результата, всё. Вся логика внутри скрипта который скармливается фантому.

Фантомы можно запускать с использованием проксей

И даже если бы было нельзя, то можно было бы решить средствами не фантома, но по не техническим причинам было решено не использовать прокси.

В попытке ускорить парсинг не возникала идея перенести бизнес логику парсинга в подлючаемые к фантому JS? У меня возникали большие подозрения, что это может ускорить процесс, но руки до подобной переделки уже не дошли.

Очень сомневаюсь в действительной пользе такого решения. Зачем внутри фантома решать задачи парсинга большие, чем получение нужных данных? Думается мне что оверхед на передачу данных в сторонний скрипт (Python, php, C#, nodejs, anything_you_like) будет гораздо ниже, чем анализ данных внутри виртуального браузера. Особенно если данных какой-то относительно большой объем. Я думал избавиться от воркера который собирает картинки и делать это в фантоме, всё равно ведь страница отрисовывается. Но способа лучше чем отрисовать картинку в канвасе и сохранить данные из канваса — не нашел. А данный способ всё равно порождает дополнительный запрос за контентом, потому смысла не имеет.
Хотя в случае с использованием API для общения с браузером, возможно, всё иначе.
Очень сомневаюсь в действительной пользе такого решения.

Но именно он, как я понимаю, и был использован. "а в самом script.js есть всё, что надо для работы. Результат отдаем обратно в output"

чем анализ данных внутри виртуального браузера

У меня на одной из собираемых страниц таблица. Строится динамически аяксом. Колонок что-то около десятка. При количестве строк под сотню разбор данных из этой таблицы занимал где-то минуту из PHP по webdriver с использованием достаточно простого XPath. На сколько я знаю реализация да и сам протокол, скорость не самая это сильная сторона. Поэтому подумалось, что парсер этой таблицы через JS загруженный с фантомом должен работать быстрее. Все равно ведь сам фантом базируется на ghostdriver по сути своей наборе JS скриптов. Я надеялся, что спициализированный JS вся быстрее отработает, чем пачка JS общего назначение вынужденная к тому же поддерживать протокол. Но реализации не дошло. Пришлось срочно переписать на SpiderMonkey. Но я все думаю, помогло бы… Вот и спрашиваю у всех кто использовал фантом, был ли у них подобный опыт.
Но именно он, как я понимаю, и был использован.

Не совсем, я не отказался от всей той логики что была до появления фантома, всё что делал фантом в данном случае — кликал на элемент который якобы ссылка, отрабатывал все внутренние редиректы (от 2 до примерно 7) и возвращал итоговый url в output.

Колонок что-то около десятка. При количестве строк под сотню разбор данных из этой таблицы занимал где-то минуту из PHP по webdriver с использованием достаточно простого XPath

Звучит жутко, как я понимаю каждый xpath запрос отдавался в браузер и основная проблема была именно в этом? В случае с пайтоном и адекватным способом общения его и браузера, я бы стащил готовый html в пайтон и там уже разбирал. Может быть так и следовало поступить? Маленький html жрется очень быстро. Если надо распарсить какие-то огромные данные, то есть event-based парсинг. Доводилось мне парсить xml в 60гигов, примерно за 4 часа он был пережеван, при этом в базу ушло около 5,5 миллионов записей. В самом файле их было намного больше. Хотя таких данных в веб-парсинге пожалуй что нет.
Не, XPath там один. Хотя сейчас сходил в репозиторий уточнить, все же уже запамятовал. Два, один на отслеживание появления таблицы на странице, второй вытаскивание данных:
	/**
	 * С заданного $url загрузит информацию о заказах закупки. Вернет массив в котором элементы это заказы УЗов.
	 */
	public function loadOrders($url)
	{
		$result = array();

		$this->autoLogin();
		$this->_account->driver->get($url);
		$this->_account->driver->wait(20)->until(\WebDriverExpectedCondition::presenceOfElementLocated(
			\WebDriverBy::id('page-footer')
		));

		echo '[' . date('d/M/Y:H:i:s') . "] Load purchase orders from URL: «{$url}»\n";
		$orders_table = $this->_account->driver->wait(10)->until(\WebDriverExpectedCondition::presenceOfElementLocated(
			\WebDriverBy::xpath('//div[@id="page-body"]//div[contains(@class,"post")]//div[@class="postbody"]/div[@class="content"]/table[1]')
		));

		// Бежим по строкам таблицы с заказами
		$orders = $orders_table->findElements(\WebDriverBy::xpath('tbody/tr[position()>1]'));
		foreach ($orders as $order)
		{
			$order_prop_list = $order->findElements(\WebDriverBy::xpath('td'));
			// [NOTE] 2015-02-21 alekciy: При изменении формата выходного массива скорректировать атрибуты модели MonitPurchaseOrder
			// т.к. в OrdersMonitCommand::_load() идет массовое присвоение
			$result[] = array(
				'member'           => mb_trim($order_prop_list[0]->getText()),
				'item_name'        => mb_trim($order_prop_list[1]->getText()),
				'item_price'       => mb_trim($order_prop_list[2]->getText()),
				'item_props'       => mb_trim($order_prop_list[3]->getText()),
				'item_status'      => mb_trim($order_prop_list[4]->getText()),
				'item_status_norm' => $this->normalizeStatus($order_prop_list[4]->getText()),
			);
		}

		return $result;
	}

Причем сначала я думал в сторону аякса и проксей. Там конечно все тоже не быстро, но не на целую минуту. Вариант работы с исходником да, отличный, нужно было опробывать. Но там был сильно кривой html, а в php в плане работы с xml все немного хуже, чем в питоне.
Я тут вижу дважды вызов метода wait(X), с 20 и 10 в качестве параметра. Я правильно понимаю что это время отведенное на ожидание драйверу, прежде чем вернуть ответ? Т.е. грубо говоря ждем 20 секунд чтобы всё загрузилось потом тащим таблицу? Тогда получается что полминуты это только ожидания…
Нет, не так. Это таймаут сколько мы ждём появление элемента. Внутри ясное дело libevent и не пахнет, но отрабатывает быстрее заданного таймаута.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации