Короткая версия
>>> for element in selector.xpath('//element'):
... attributes = []
... # loop over all attribute nodes of the element
... for index, attribute in enumerate(element.xpath('@*'), start=1):
... # use XPath's name() string function on each attribute,
... # using their position
... attribute_name = element.xpath('name(@*[%d])' % index).extract_first()
... # Scrapy's extract() on an attribute returns its value
... attributes.append((attribute_name, attribute.extract()))
...
>>> attributes # list of (attribute name, attribute value) tuples
[(u'attr1', u'value1'), (u'attr2', u'value2')]
>>> dict(attributes)
{u'attr2': u'value2', u'attr1': u'value1'}
>>>
Длинная версия
XPath имеет name(node-set?)
function, чтобы получить имена узлов (an attribute is a node, an attribute node):
имя е unction возвращает строку, содержащую QName, представляющую расширенное имя узла в наборе узлов аргумента, которое является первым в порядке документа. (...) Если аргумент опущен, он по умолчанию устанавливает набор узлов с контекстный узел как единственный член.
(источник: http://www.w3.org/TR/xpath/#function-name)
>>> import scrapy
>>> selector = scrapy.Selector(text='''
... <html>
... <element attr1="value1" attr2="value2">some text</element>
... </html>''')
>>> selector.xpath('//element').xpath('name()').extract()
[u'element']
(Здесь, я прикован name()
от результата //element
выбора, чтобы применить функцию для всех выбранных узлов элементов Удобной особенность Scrapy селекторов.)
Вы хотите сделать то же самое с узлами атрибутов, правильно? Но это не работает:
>>> selector.xpath('//element/@*').extract()
[u'value1', u'value2']
>>> selector.xpath('//element/@*').xpath('name()').extract()
[]
>>>
Примечание: Я не знаю, если это ограничение lxml/libxml2
, который Scrapy использует под капотом, или если XPath спецификации запретить это. (Я не понимаю, почему это было бы.)
Что вы можете сделать, хотя это использование формы name(node-set)
, т. Е. С непустым набором узлов в качестве параметра. Если внимательно прочитать часть XPath 1.0 спецификации я вставил выше, как и с другими строковыми функциями, name(node-set)
принимает во внимание только первый узел в наборе узлов (в порядке документов) на:
>>> selector.xpath('//element').xpath('@*').extract()
[u'value1', u'value2']
>>> selector.xpath('//element').xpath('name(@*)').extract()
[u'attr1']
>>>
Attribute узлы также имеют позиции, поэтому вы можете перебирать все атрибуты по их позиции.Здесь мы имеем 2 (результат count(@*)
на узел контекста):
>>> for element in selector.xpath('//element'):
... print element.xpath('count(@*)').extract_first()
...
2.0
>>> for element in selector.xpath('//element'):
... for i in range(1, 2+1):
... print element.xpath('@*[%d]' % i).extract_first()
...
value1
value2
>>>
Теперь, вы можете догадаться, что мы можем сделать: позвонить name()
для каждого @*[i]
>>> for element in selector.xpath('//element'):
... for i in range(1, 2+1):
... print element.xpath('name(@*[%d])' % i).extract_first()
...
attr1
attr2
>>>
Если поставить все это вместе, и предположим, что @*
получит вам атрибуты в порядке документа (не указанный в XPath 1.0 спецификации, я думаю, но это то, что я вижу, что происходит с lxml
), вы в конечном итоге с этим:
>>> attributes = []
>>> for element in selector.xpath('//element'):
... for index, attribute in enumerate(element.xpath('@*'), start=1):
... attribute_name = element.xpath('name(@*[%d])' % index).extract_first()
... attributes.append((attribute_name, attribute.extract()))
...
>>> attributes
[(u'attr1', u'value1'), (u'attr2', u'value2')]
>>> dict(attributes)
{u'attr2': u'value2', u'attr1': u'value1'}
>>>