Работа с DOM-деревом

Интерфейс к LXML библиотеке

Первое, что вам нужно усвоить, это то, что Grab предоставляет всего лишь удобный интерфейс к функциям библиотеки lxml. Крайне желательно, знать и понимать API библиотеки lxml. Grab предоставляет множество функций поиска данных в документе. Большинство этих функций представляют из себя xpath-запрос к DOM-дереву и последующую его обработку.

Далее описаны основные принципы использования lxml-расширения. Полный список методов (и их описание) вы можете посмотреть в API справочнике extensions_lxml.

DOM-дерево

DOM-дерево доступно через аттрибут tree <LXMLExtension.tree():

>>> g.go('http://vk.com')
<grab.response.Response object at 0x1c9ae10>
>>> g.tree
<Element html at 1c96940>
>>> print g.tree.xpath('//title/text()')[0]
Welcome!

Вычисление DOM-дерева требует значительных ресурсов процессора, поэтому оно не вычисляется сразу после получения тела документа, а лишь только при первом вызове какого-либо xpath/css метода или обращении к аттрибуту tree <LXMLExtension.tree(). DOM-дерево вычисляется один раз и затем кэшируется.

XPATH-методы

Самый часто используемый метод, это xpath(). В качестве аргумента он принимает xpath-выражение и возвращает найденный узел DOM-дерева. Пожалуйста, не путайте xpath() метод объекта Grab и xpath метод lxml.html.etree.Element объекта. Последний возвращает список элементов, в то время как xpath метод Grab-объекта возвращает первый найденный элемент. Если вам нужен список объектов, используйте xpath_list() метод. Приведу наглядный пример:

>>> g.go('http://google.com')
<grab.response.Response object at 0x1ae73d0>
>>> g.xpath_list('//*[@type="submit"]')
[<InputElement 1a35e88 name='btnG' type='submit'>, <InputElement 1c96530 name='btnI' type='submit'>]
>>> g.xpath('//*[@type="submit"]')
<InputElement 1a35e88 name='btnG' type='submit'>
>>> from lxml.html import fromstring
>>> fromstring(g.response.body).xpath('//*[@type="submit"]')
[<InputElement 1c966d0 name='btnG' type='submit'>, <InputElement 1c96598 name='btnI' type='submit'>]

Методом xpath_text вы можете извлечь текстовое содержимое из найденного DOM-элемента. Метод xpath_number извлекает в начале текстовое содержимое, затем ищет там число:

>>> g.go('http://rambler.ru')
<grab.response.Response object at 0x1c9a650>
>>> print g.xpath_text('//td[@class="Spine"]//nobr')
1996—2012
>>> print g.xpath_number('//td[@class="Spine"]//nobr')
1996

CSS-методы

Благодаря модулю cssselect, можно искать элементы в DOM-дереве с помощью CSS-выражений. Поддерживаются основные CSS2-селекторы, не все. Список и название методов для работы с CSS аналогичен списку методов для работы с xpath.

>>> g.go('http://rambler.ru')
<grab.response.Response object at 0x1c9a650>
>>> print g.css_text('td.Spine nobr')
1996—2012
>>> print g.css_number('td.Spine nobr')
1996

Обработка исключений

Если xpath/css метод не нашёл данных, то генерируется исключение DataNotFound. Класс этого ислючения унаследован от IndexError, так что можно просто ловить IndexError на заморачиваясь на импорт DataNotFound исключения:

>>> try:
...     g.xpath('//foobar')
... except IndexError:
...     print 'not found'
...
not found

Все xpath/css методы понимают аргумент default, если вы зададите его, то в случае, когда данные не были найден, вместо генерации исключения, xpath/css метод вернёт указанное значение. В качестве значения вы можете передавать даже None:

>>> print g.xpath('//foobar')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.6/dist-packages/grab/ext/lxml.py", line 134, in xpath
    raise DataNotFound('Xpath not found: %s' % path)
grab.error.DataNotFound: Xpath not found: //foobar
>>> print g.xpath('//foobar', default=None)
None
>>> print g.xpath('//foobar', default='spam')
spam