2015-09-25 13 views
1

Я пытаюсь получить оба ключа и значения атрибутов какого-либо тега в XML-файле (используя scrapy и xpath).Получение имени атрибутов с помощью Scrapy XPATH

Тег что-то вроде:

<element attr1="value1" attr2="value2 ...> 

Я не знаю, ключи «attr1», «attr2» и так далее, и они могут меняться между двумя элементами. Я не понял, как получить ключи и значения с помощью xpath, есть ли другая хорошая практика для этого?

ответ

5

Короткая версия

>>> 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'} 
>>> 
1

Я пытаюсь получить оба ключа и значения атрибутов какого-либо тега в XML-файле (используя scrapy и xpath).

@*, что означает «любой атрибут». Выражение XPath //element/@* предоставит вам все атрибуты элементов element и с их атрибутами.