Comments 55
«Пример реального парсера» — ссылки нет.
Мне приходилось сталкиваться с сайтами где нужна «хитрая» авторизация, когда после входа, средствами js дергается third party сайт, который собственно и финализирует авторизацию.
Стояла задача автоматического забора информации с подобного ресурса — доходило до абсурда в виде сборки webkit'а, по концовке все делалось в .net winforms с их IE based webbrowser'ом который такое умеет.
Так вот там в .net 4.5 асинхронность очень забавно делается — в самом простом случае просто дописывается async к определению метода и все.
Стояла задача автоматического забора информации с подобного ресурса — доходило до абсурда в виде сборки webkit'а, по концовке все делалось в .net winforms с их IE based webbrowser'ом который такое умеет.
Так вот там в .net 4.5 асинхронность очень забавно делается — в самом простом случае просто дописывается async к определению метода и все.
UFO just landed and posted this here
UFO just landed and posted this here
Потому что я всегда любил велосипеды. Было интересно попробовать свой собственный асинхронный велосипед. Кроме всего прочего скрапи меня немного пугает свою монструозностью. В моём же модуле spider всего 700 строчек, которые я сам писал. Месяц назад сознательно попытался написать один парсер на скрапи, но его багафича отбила всякое желание делать что-то дальше: groups.google.com/group/scrapy-users/browse_thread/thread/5092eb5cc8695f2a/aa36618c58c539ec Но это всё мелочи жизни. Просто у меня есть время и желание писать свои решения, — я их пишу. А ну и ещё — синхронный интерфейс Grab я уже пишу/использую много лет, поэтому хотелось как-то заюзать эти наработки. Очень здорово получилось, что получилось натянуть старый интерфейс Grab на асинхронность.
А ещё scrapy не поддерживает socks-прокси. Впрочем grab:spider — тоже, в debian stable устаревшая версия curl-библиотеки, в которой есть баг — multicurl не работает с socks. В более новых версиях curl этот баг пофиксили.
А ещё scrapy не поддерживает socks-прокси. Впрочем grab:spider — тоже, в debian stable устаревшая версия curl-библиотеки, в которой есть баг — multicurl не работает с socks. В более новых версиях curl этот баг пофиксили.
но его багафича отбила всякое желание делать что-то дальше: groups.google.com/group/scrapy-users/browse_thread/thread/5092eb5cc8695f2a/aa36618c58c539ec
Хохохо… Ругаться что
hxs.select('//div[@class="thumb"]').extract()[0]
медленнее чем hxs.select('//div[@class="thumb" and position() = 1]').extract()[0]
все равно что ругаться на то, что db.query("SELECT * FROM my_huge_table")[0]
медленнее чем db.query("SELECT * FROM my_huge_table LIMIT 1")[0]
Я объясню. Мне нужно было пробежаться по всем этим элементам, не просто достать первый такой элемент, а перебрать их все и у каждого взять несколько свойств. Если вы попробуете это сделать через lxml/xpath, то никаких тормозов не будет, а scrapy делает что-то больно хитрое, такое хитрое, что оно в 200 раз медленнее работает. Судя по всему он каждую ноду из результата заворачивает в какой-то свой класс — это и даёт феерическое замедление.
Если вам не сложно, приведите, пожалуйста, минимальные примеры кода для lxml и для Scrapy HtmlXPathSelector, которые решают вашу задачу и которые сильно различаются по скорости… Тот кусок, который вы запостили в тикет, как выяснилось, не до конца отражает вашу задачу (вам нужна итерация, а в тикете забирается только первый элемент).
Хоть я со Scrapy сам использую lxml вместо ихнего HtmlXPathSelector, но стало очень интересно что-же там такое медленное.
Хоть я со Scrapy сам использую lxml вместо ихнего HtmlXPathSelector, но стало очень интересно что-же там такое медленное.
Попробую завтра, башка не варит уже сегодня.
Так, с примером проблема. Я не знаю, как в scrapy без создания целого парсера просто получить HtmlXPathSelector от какого-либо содерижмого, он хочет какой-то response-объект, если вы мне подскажите, как его сделать, я напишу пример.
from scrapy.selector import HtmlXPathSelector
hxs = HtmlXpathSelector(text=page_body)
Нагуглил тут github.com/scrapy/scrapy/blob/master/scrapy/selector/lxmlsel.py
PS: я не минусовал. Я не ради троллинга, просто думаю если и правда все так печально, может патч какой сочиню.
pyquery предоставляет возможность писать селекторы в виде jquery-селекторов, не путайте их с css-селекторами.Это разные вещи, хоть и похожи. Кроме того, я писал выше, что lxml не поддерживает сложные css-селекторы. Мне сейчас проверять лень.
промахнулись.
Да, чё-то глючит меня. Я тест написал.
Показана реальная ситуация, я очень часто итерируюсь по xpath выборке и применяю дополнительные xpath-выражения к каждому элементу выборки.
У меня такие результаты:
# -*- coding: utf-8 -*-
import time
from scrapy.selector import HtmlXPathSelector
import urllib
from lxml.html import fromstring
data = urllib.urlopen('http://tubesexclips.com/').read()
start = time.time()
hxs = HtmlXPathSelector(text=data)
scrapy_results = set()
for elem in hxs.select('//div[@class="added-download"]/a'):
href, text = elem.select('@href').extract()[0], elem.select('text()').extract()[0]
scrapy_results.add((href, text))
print 'HtmlXpathSelector: %.2f' % (time.time() - start)
start = time.time()
tree = fromstring(data)
lxml_results = set()
for elem in tree.xpath('//div[@class="added-download"]/a'):
href, text = elem.xpath('@href')[0], elem.xpath('text()')[0]
lxml_results.add((href, text))
print 'lxml: %.2f' % (time.time() - start)
print 'Equal: %s' % (scrapy_results == lxml_results)
Показана реальная ситуация, я очень часто итерируюсь по xpath выборке и применяю дополнительные xpath-выражения к каждому элементу выборки.
У меня такие результаты:
lorien@athlon:/web/barn$ python speed3.py
HtmlXpathSelector: 0.75
lxml: 0.02
Equal: True
Да, подтверждаю. У меня такого же порядка получились результаты. Даже если в качестве бэкенда к HtmlXPathSelector использовать lxml
Спасибо! Появится время — поищу в чем причина.
from scrapy.conf import settings
settings['SELECTORS_BACKEND'] == 'lxml'
Спасибо! Появится время — поищу в чем причина.
Собственно, пофиксил github.com/scrapy/scrapy/pull/79
Не знаю как скоро рассмотрят, но после фикса скорость стала одинаковой примерно.
Не знаю как скоро рассмотрят, но после фикса скорость стала одинаковой примерно.
о, уже приняли pull-request. Благодарю за содействие в улучшении вашего конкурента))
А как тут с обработкой ошибок? Например, сетевых.
Я в моем случае с использованием Queue и потоков при неудаче (даже если hammer mode не отработал) кладу задание обратно в очередь и делаю Н попыток скачки/парсинга.
Потом в случае чего выдаю ошибку. Промежуточные структуры данных сохраняю в сериализованном виде на диск, чтобы потом если что можно было перезапуститься с ключиком --continue
А тут как предлагается обрабатывать подобные ситуации?
Я в моем случае с использованием Queue и потоков при неудаче (даже если hammer mode не отработал) кладу задание обратно в очередь и делаю Н попыток скачки/парсинга.
Потом в случае чего выдаю ошибку. Промежуточные структуры данных сохраняю в сериализованном виде на диск, чтобы потом если что можно было перезапуститься с ключиком --continue
А тут как предлагается обрабатывать подобные ситуации?
Задания с сетевые ошибками (в том числе c кодами > 400 and != 404) по-умолчанию засылаются обратно в очередь:
* bitbucket.org/lorien/grab/src/7000b67ff7ad/grab/spider/base.py#cl-520
Количество попыток можно настраивать. Когда количество попыток для задания истекает, то просто логируется ошибка:
* bitbucket.org/lorien/grab/src/7000b67ff7ad/grab/spider/base.py#cl-395
Можно в принципе добависть фичку, чтобы вызывался callback какой-нить настраиваемый в таком случае. Можно будет в этом callback взять и опять запихать задание в очередь, чтобы ещё раз повторить цикл попыток.
> Потом в случае чего выдаю ошибку. Промежуточные структуры данных сохраняю в сериализованном виде на диск, чтобы потом если что можно было перезапуститься с ключиком --continue
Я обычно настраиваю парсер так, чтобы он работал без ошибок и потом запускаю главный парсинг.
Я сейчас эксперементирую с кэшем. Записываю в монго скачанные документы. Это поведение можно так включить: bot = SomeSpider(use_cache=True, cache_db='some-db-name') Кэшируются только GET-запросы. Столкнулся с проблемой, что не влезает всё на винчестер, ну нету у меня винчестера на 2 терабайта. Тогда я придумал на лету применять gzip-компрессию. Включается опцией use_cache_compression=True. Документы сжимаются в 10 раз — это очень круто. Но нужно время CPU на компрессию-декомпрессию — это не круто :( Я пытался через multiprocessing организовать обработку на множестве ядер, вроде как есть ускорение в два раза на 4-ядерном атлоне. Но это пока нихрена не оттестировано даже на эмпирическом уровне. Короче пока работа с кэшем довольно медленная, я вообще пока плохо понимаю что делаю — все эти асинхронности и mongodb — новые для меня области :) Как минимум, работа с кешем очень упрощает отладку парсера на начальном этапе.
Конец brain dump'а :)
* bitbucket.org/lorien/grab/src/7000b67ff7ad/grab/spider/base.py#cl-520
Количество попыток можно настраивать. Когда количество попыток для задания истекает, то просто логируется ошибка:
* bitbucket.org/lorien/grab/src/7000b67ff7ad/grab/spider/base.py#cl-395
Можно в принципе добависть фичку, чтобы вызывался callback какой-нить настраиваемый в таком случае. Можно будет в этом callback взять и опять запихать задание в очередь, чтобы ещё раз повторить цикл попыток.
> Потом в случае чего выдаю ошибку. Промежуточные структуры данных сохраняю в сериализованном виде на диск, чтобы потом если что можно было перезапуститься с ключиком --continue
Я обычно настраиваю парсер так, чтобы он работал без ошибок и потом запускаю главный парсинг.
Я сейчас эксперементирую с кэшем. Записываю в монго скачанные документы. Это поведение можно так включить: bot = SomeSpider(use_cache=True, cache_db='some-db-name') Кэшируются только GET-запросы. Столкнулся с проблемой, что не влезает всё на винчестер, ну нету у меня винчестера на 2 терабайта. Тогда я придумал на лету применять gzip-компрессию. Включается опцией use_cache_compression=True. Документы сжимаются в 10 раз — это очень круто. Но нужно время CPU на компрессию-декомпрессию — это не круто :( Я пытался через multiprocessing организовать обработку на множестве ядер, вроде как есть ускорение в два раза на 4-ядерном атлоне. Но это пока нихрена не оттестировано даже на эмпирическом уровне. Короче пока работа с кэшем довольно медленная, я вообще пока плохо понимаю что делаю — все эти асинхронности и mongodb — новые для меня области :) Как минимум, работа с кешем очень упрощает отладку парсера на начальном этапе.
Конец brain dump'а :)
Спасибо :)
Уже заюзал кеширование, мне нравится :) Еще раз спасибо
Кстати, что дает distributed_mode? Я попробовал запускаться с ним, так вся память выжралась и все зависло.
Еще фичреквест — хорошо бы было добавить handler для SIGINT, чтобы можно было правильно прервать цикл парсинга нажав CTRL+C. Я так в своих скриптах обычно делаю.
Еще фичреквест — хорошо бы было добавить handler для SIGINT, чтобы можно было правильно прервать цикл парсинга нажав CTRL+C. Я так в своих скриптах обычно делаю.
Ага, интересная идея. У меня там кстати щас есть хэндлер на SIGUSR1 — пишется статистика счётчиков в /tmp/spider.log или куда-то туда… Топорно конечно, но вот как бы думаю в этом направлении.
Так, возник такой вопрос. Допустим, у меня есть task_initial, который скачивает страницу. На этой странице есть куча картинок, которые я тоже должен выкачать. Парсю страницу, генерю новые Task'и, в обработчике task_image сохраняю картинку куда нужно, генерю миниатюру и т.д. Внимание вопрос — как мне вернуться к обработке страницы после того как обработались все картинки и сгенерить например HTML'ину которая включает в себя результат работы Task'ов картинок?
Гыгы, без понятия :) Можно сделать два спайдера и запускать второй после того как первый скачал все картинки. Ну или можно как-то извращаться и в task_images и определять что это последняя картинка и запускать нужный обработчик. Да, не помешала бы какая-то встроенная фича для такой задачи (генерация сигнала после обработки помеченных тасков), но пока ничего такого нет.
Начали познавать «прелести» асинхронного программирования?))
Ну или если какой-либо аналог Scrapy 'meta' (shared_data) не поддерживается в Grab, то дописать свой костыльчик
def task_initial(...):
#... parse page ...
self.pages[url] = common_data
self.counters[url] = len(images_list)
for img in images_list:
yield Task('save_img', grab=grab, shared_data={'page_url': url})
def task_save_img(...):
# ... store image, generate thumb ...
self.counters[page_url] -= 1
if self.counters[page_url] == 0:
self.generate_my_page(self.pages[page_url])
Ну или если какой-либо аналог Scrapy 'meta' (shared_data) не поддерживается в Grab, то дописать свой костыльчик
Scrapy meta это я про doc.scrapy.org/en/latest/topics/request-response.html#scrapy.http.Request.meta
Я уже ничо не соображаю. Сейчас спать пойду. Но могу сказать, чего есть в Task объектах: можно передавать сколько угодно именованных параметров и они потом будут доступны как аттрибуты task объекта, который передаётся в обработчик третьим параметром. Наверное это аналог meta из scrapy.
distributed mode — это мои эксперименты с выполнением task-обработчиков на нескольких ядрах процессора с помощью multiprocessing, но это всё нереально сырое, лучше эту опцию пока не трогать.
А вообще круто, попробую в одном проекте. Как раз и mongo там тоже планируется.
Автор, если Вы рассматривали BeautifulSoup, можете написать, чем «парсящая» часть вашей библиотеки лучше его? Может ли Ваша библиотека обрабатывать «не совсем корректный» html?
О, любитель ковычек, вы забыли слово лучше в кавычки заключить :)
Я использовал BeatifulSoup до тех пор, пока не созрел до lxml. Это библиотека написанная на C. Она полностью поддерживает поиск через xpath-выражения, а также понимает битый html. Grab использует lxml для парсинга, но вообще ничего не мешает вам в том же Grab:Spider работать с BeautifulSoup.
Плюсы BeautifulSoup:
* pure python
* умещается в одном файле
* допускаю, что новичку проще вникнуть в работу с BS, чем с lxml
Минусы BeautifulSoup
* отвратительно медленный
* возможно жрёт память, мне щас лень писать тесты для памяти
* меня лично напрягает писать два длинных camel case имени в строке импорта: «from BeautifulSoup import BeautifulSoup»
Для скорости мне тесты писать не лень. Запустите у себя на компьютере и ужаснитесь:
Если вы ещё не используете lxml для парсинга, то мне вас жаль :)
Я использовал BeatifulSoup до тех пор, пока не созрел до lxml. Это библиотека написанная на C. Она полностью поддерживает поиск через xpath-выражения, а также понимает битый html. Grab использует lxml для парсинга, но вообще ничего не мешает вам в том же Grab:Spider работать с BeautifulSoup.
Плюсы BeautifulSoup:
* pure python
* умещается в одном файле
* допускаю, что новичку проще вникнуть в работу с BS, чем с lxml
Минусы BeautifulSoup
* отвратительно медленный
* возможно жрёт память, мне щас лень писать тесты для памяти
* меня лично напрягает писать два длинных camel case имени в строке импорта: «from BeautifulSoup import BeautifulSoup»
Для скорости мне тесты писать не лень. Запустите у себя на компьютере и ужаснитесь:
from lxml.html import fromstring
from BeautifulSoup import BeautifulSoup
import time
import urllib
data = urllib.urlopen('http://habrahabr.ru').read()
start = time.time()
for x in xrange(10):
tree = BeautifulSoup(data)
print tree.find('title').text
print 'BeautifulSoup: %.2f' % (time.time() - start)
start = time.time()
for x in xrange(10):
tree = fromstring(data)
print tree.xpath('//title')[0].text
print 'lxml: %.2f' % (time.time() - start)
Если вы ещё не используете lxml для парсинга, то мне вас жаль :)
Опять-же, в lxml поддерживается православный XPath (или CSS-селекторы для тех кому лень XPath изучить), в то время как в BeautifulSoup для выборки какой-то свой уникальный костыль)
А лично я предпочитаю pyquery, голый lxml не осилил почему-то :)
А там можно задать условия, например, на текст внутри элемента?
//div/strong[contains(text(), «Google»)]
//div/strong[contains(text(), «Google»)]
Хм, вроде как нет. У меня такой необходимости, по крайней мере, не возникало :)
Бывают сайты с табличной вёрсткой, где множество вложенных тэгов table, tr, td. Там особо не к чему привязаться кроме поясняющего текста внутри нужной ячейки
Из документации:
>>> d('p').filter(lambda i: PyQuery(this).text() == 'Hi')
[]
>>> d('p').filter(lambda i: PyQuery(this).text() == 'Hi')
[]
Ясно. Ну такой подход ожидаемо тормозит по сравнению с lxml. Тормозит чуть ли не в 10 раз: dumpz.org/122917/ Думаю тормоза из-за того, что каждый раз вызывается функция и каждый раз в ней стрится дополнительный PyQuery-объект.
А вообще, раз мы про pyquery заговорили, я подумал — самое время — и замержился с pyquery-форком. В общем, теперь в грабе через аттрибут `pyquery` доступно PyQuery-дерево.
А вообще, раз мы про pyquery заговорили, я подумал — самое время — и замержился с pyquery-форком. В общем, теперь в грабе через аттрибут `pyquery` доступно PyQuery-дерево.
в lxml же есть csselect (http://lxml.de/cssselect.html) — зачем pyquery нужен, так и не понял)
Так я-ж написал что lxml CSS селекторы тоже умеет lxml.de/cssselect.html )))
Или у pyquery они какие-то особые?
Кстати, по-секрету, lxml транслирует CSS селекторы в XPath и потом просто выполняет полученный XPath запрос
Или у pyquery они какие-то особые?
Кстати, по-секрету, lxml транслирует CSS селекторы в XPath и потом просто выполняет полученный XPath запрос
>>> from lxml.cssselect import CSSSelector
>>> sel = CSSSelector('div.content')
>>> sel.path
"descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' content ')]"
А, ну да, pyquery какие-то дополнительные CSS селекторы предоставляет bitbucket.org/olauzanne/pyquery/src/da3b6b3d9d59/pyquery/cssselectpatch.py
Хм, проблемка у меня одна возникла.
Пишу спайдер. В методе обработчике генерю еще пачку методов, которые делают POST на другой сайт. Они попадают в очередь, я вижу в консоли сообщение POST… и… всё. Дальше выполнение приостанавливается. log_dir никакой информации не дает. Примерный код: dumpz.org/120523/
Пишу спайдер. В методе обработчике генерю еще пачку методов, которые делают POST на другой сайт. Они попадают в очередь, я вижу в консоли сообщение POST… и… всё. Дальше выполнение приостанавливается. log_dir никакой информации не дает. Примерный код: dumpz.org/120523/
Ой, не на тот уровень ответил
Да и нашел где косяк — в update'е словаря
больше спать надо :)
Да и нашел где косяк — в update'е словаря
больше спать надо :)
Если вы задаёте `post` опцию, то писать `method=«post»` уже не обязательно, он таким будет автоматически. Да и disable_cache не нужно, post запросы не кэшируются:
* bitbucket.org/lorien/grab/src/7000b67ff7ad/grab/spider/base.py#cl-409
* bitbucket.org/lorien/grab/src/7000b67ff7ad/grab/spider/base.py#cl-519
* bitbucket.org/lorien/grab/src/7000b67ff7ad/grab/spider/base.py#cl-409
* bitbucket.org/lorien/grab/src/7000b67ff7ad/grab/spider/base.py#cl-519
Ага. Но у меня почему-то без явного задания method не работало.
Тут еще на одни грабли наступил. У меня шлются POST'ы в форму Google Translate. В однопоточном режиме всегда (99.9%) отрабатывалось корректно. В ассинхронном же регулярно вылазят страницы, которые не содержат перевод. Такое ощущение, что при post'е передаются не все данные и где-то по сети что-то обрезается. Я думаю, что собака порыта в pycurl, потому что с Н-ной попытки как правило такие запросы отрабатывают нормально (я просто проверяю при парсинге, при нобходимости снова делаю yield.
Сейчас я под Windows 7 32 bit вынужден работать, не знаю как в Linux. Но довольно интересная картина.
Тут еще на одни грабли наступил. У меня шлются POST'ы в форму Google Translate. В однопоточном режиме всегда (99.9%) отрабатывалось корректно. В ассинхронном же регулярно вылазят страницы, которые не содержат перевод. Такое ощущение, что при post'е передаются не все данные и где-то по сети что-то обрезается. Я думаю, что собака порыта в pycurl, потому что с Н-ной попытки как правило такие запросы отрабатывают нормально (я просто проверяю при парсинге, при нобходимости снова делаю yield.
Сейчас я под Windows 7 32 bit вынужден работать, не знаю как в Linux. Но довольно интересная картина.
Кстати, мы можем делать yield из не-task функции? У меня почему-то не заработало.
Sign up to leave a comment.
Фреймворк для парсинга Grab:Spider