lxml XPath — извлечение текста из нескольких узлов p

Сначала ознакомьтесь с lxml XPath position() не работает.

Поскольку XPath не поддерживает извлечение текста из нескольких узлов, я решил написать цикл for, чтобы получить 30 материалов.

for i in range(1,31):
    content = "string(//div[@id='article']/p[" + (print(i)) + "]/.)"
    print(content)

Я представлял, что это вернется, как,

"string(//div[@id='article']/p[1]/.)"
"string(//div[@id='article']/p[2]/.)"
"string(//div[@id='article']/p[3]/.)"
....
"string(//div[@id='article']/p[30]/.)"  

Однако, очевидно, это не работает, как я ожидал. Я получил следующее сообщение об ошибке.

TypeError: Can't convert 'NoneType' object to str implicitly

Что я должен делать? Любой другой элегантный подход к решению этой проблемы?


person K.K.    schedule 31.08.2016    source источник


Ответы (2)


В Python3 print — это функция, которая выводит на экран и возвращает None. (В Python2 print — это оператор, и код выдаст ошибку, поскольку вы не можете поместить оператор в середину выражения.) Вместо этого для построения строки используйте метод format:

content = "string(//div[@id='article']/p[{}]/.)".format(i)

И, кстати, вы должны иметь возможность использовать position() с lxml. Например,

import lxml.html as LH
content = '''\
    <bookstore>
      <book>
        <title lang="eng">Harry Potter</title>
        <price>29.99</price>
      </book>
      <book>
        <title lang="eng">Learning XML</title>
        <price>39.95</price>
      </book>
      <book>
        <title lang="eng">Things Fall Apart</title>
        <price>19.99</price>
      </book>
      <book>
        <title lang="eng">Blood Meridian</title>
        <price>9.99</price>
      </book>
    </bookstore>'''
root = LH.fromstring(content)

# Compare with https://stackoverflow.com/a/39242701/190597
print(root.xpath('//book[position()>=1 and position()<=last()]/title/text()'))
# ['Harry Potter', 'Learning XML', 'Things Fall Apart', 'Blood Meridian']

# But note that it is equivalent to 
print(root.xpath('//book/title/text()'))
# ['Harry Potter', 'Learning XML', 'Things Fall Apart', 'Blood Meridian']

print(root.xpath('//book[position()<3]'))

отпечатки

['Harry Potter', 'Learning XML']

который показывает, что вы можете выбрать первый N books без необходимости зацикливаться.


Как отмечает Томалак, функция XPath string возвращает только строковое представление первый узел. Например,

print(root.xpath('string(//book[position()<3]/title/text())'))

только печатает

Harry Potter

Если вам нужен список строк, не используйте string.

Если, как указывает Дэниел Хейли желаемый текст находится в смеси вложенных узлов и дочерних элементов, например. <title lang="eng">Harry <b>Potter</b></title>, то можно извлечь нужный текст методом text_content:

[title.text_content() for title in root.xpath('//book[position()<3]/title')]
person unutbu    schedule 31.08.2016
comment
Одной из веских причин для использования string() является то, что title имеет смешанное содержимое (как текстовые узлы, так и дочерние элементы), и вам нужно полное строковое значение. Например, учитывая <title lang="eng">Harry <b>Potter</b></title>, вы все равно хотите вернуть Harry Potter. - person Daniel Haley; 31.08.2016
comment
Большое спасибо за ваше оперативное продолжение! Это сработало! - person K.K.; 01.09.2016

Завершающий /. в вашем xpath недействителен.

Пытаться:

content = "string(//div[@id='article']/p[" + (print(i)) + "])"

Полный пример:

import lxml.html

html = """<tag1>
<tag2>
<div id="article">
<p> stuff1 </p>
<p> stuff2 </p>
<p> stuff30 <b>more stuff</b></p>
</div>
</tag2>
</tag1>"""

root = lxml.html.fromstring(html)

for i in range(1,4):
    content = root.xpath("string(//div[@id='article']/p[" + str(i) + "])")
    print(content)
    #stuff1
    #stuff2
    #stuff30 more stuff
person Daniel Haley    schedule 31.08.2016