Получить весь текст внутри тега в lxml - PullRequest
64 голосов
/ 07 января 2011

Я хотел бы написать фрагмент кода, который будет захватывать весь текст внутри тега <content> в lxml во всех трех нижеприведенных случаях, включая теги кода. Я пробовал tostring(getchildren()), но это пропустило бы текст между тегами. Мне не очень повезло, что я искал в API соответствующую функцию. Не могли бы вы помочь мне?

<!--1-->
<content>
<div>Text inside tag</div>
</content>
#should return "<div>Text inside tag</div>

<!--2-->
<content>
Text with no tag
</content>
#should return "Text with no tag"


<!--3-->
<content>
Text outside tag <div>Text inside tag</div>
</content>
#should return "Text outside tag <div>Text inside tag</div>"

Ответы [ 14 ]

66 голосов
/ 15 августа 2012

text_content () делает то, что вам нужно?

55 голосов
/ 25 февраля 2013

Просто используйте метод node.itertext(), например:

 ''.join(node.itertext())
41 голосов
/ 07 января 2011

Попробуйте:

def stringify_children(node):
    from lxml.etree import tostring
    from itertools import chain
    parts = ([node.text] +
            list(chain(*([c.text, tostring(c), c.tail] for c in node.getchildren()))) +
            [node.tail])
    # filter removes possible Nones in texts and tails
    return ''.join(filter(None, parts))

Пример:

from lxml import etree
node = etree.fromstring("""<content>
Text outside tag <div>Text <em>inside</em> tag</div>
</content>""")
stringify_children(node)

Производит: '\nText outside tag <div>Text <em>inside</em> tag</div>\n'

17 голосов
/ 27 июня 2016

Следующий фрагмент кода, который использует генераторы Python, работает отлично и очень эффективно.

''.join(node.itertext()).strip()

16 голосов
/ 27 января 2015

Версия stringify-содержимого Альбертова, которая устраняет ошибки , о которых сообщает hoju:

def stringify_children(node):
    from lxml.etree import tostring
    from itertools import chain
    return ''.join(
        chunk for chunk in chain(
            (node.text,),
            chain(*((tostring(child, with_tail=False), child.tail) for child in node.getchildren())),
            (node.tail,)) if chunk)
4 голосов
/ 19 августа 2012
import urllib2
from lxml import etree
url = 'some_url'

получение URL

test = urllib2.urlopen(url)
page = test.read()

получение всего HTML-кода, включая табличный тег

tree = etree.HTML(page)

xpath selector

table = tree.xpath("xpath_here")
res = etree.tostring(table)

res - HTML-код таблицы это делало для меня работу.

, чтобы вы могли извлечь содержимое тегов с помощью xpath_text () и теги, включая их содержимое, с помощью tostring ()

div = tree.xpath("//div")
div_res = etree.tostring(div)
text = tree.xpath_text("//content") 

или text = tree.xpath ("// content / text ()")

div_3 = tree.xpath("//content")
div_3_res = etree.tostring(div_3).strip('<content>').rstrip('</')

эта последняя строка с использованием метода полосы не очень хороша, но она просто работает

3 голосов
/ 11 июня 2014

Определение stringify_children этот способ может быть менее сложным:

from lxml import etree

def stringify_children(node):
    s = node.text
    if s is None:
        s = ''
    for child in node:
        s += etree.tostring(child, encoding='unicode')
    return s

или в одну строку

return (node.text if node.text is not None else '') + ''.join((etree.tostring(child, encoding='unicode') for child in node))

Обоснование такое же, как в этот ответ : оставьте сериализацию дочерних узлов в lxml.tail часть node в этом случае не интересна, так как находится «за» конечным тегом.Обратите внимание, что аргумент encoding может быть изменен в соответствии с вашими потребностями.

Другое возможное решение - сериализация самого узла, а затем удаление начального и конечного тега:

def stringify_children(node):
    s = etree.tostring(node, encoding='unicode', with_tail=False)
    return s[s.index(node.tag) + 1 + len(node.tag): s.rindex(node.tag) - 2]

что несколько ужасноЭтот код верен, только если node не имеет атрибутов, и я не думаю, что кто-то захочет использовать его даже тогда.

2 голосов
/ 05 июля 2017

Один из самых простых фрагментов кода, который на самом деле работал для меня, и согласно документации на http://lxml.de/tutorial.html#using-xpath-to-find-text это

etree.tostring(html, method="text")

, где etree - это узел / тег, полный текст которого вы пытаетесьчитать.Но вот, он не избавляется от тегов скриптов и стилей.

2 голосов
/ 30 апреля 2013

В ответ на комментарий @ Ричарда выше, если вы исправите stringify_children следующим образом:

 parts = ([node.text] +
--            list(chain(*([c.text, tostring(c), c.tail] for c in node.getchildren()))) +
++            list(chain(*([tostring(c)] for c in node.getchildren()))) +
           [node.tail])

кажется, что он избегает дублирования, на которое он ссылается.

1 голос
/ 09 сентября 2015

Я знаю, что это старый вопрос, но это распространенная проблема, и у меня есть решение, которое кажется более простым, чем предложенные ранее:

def stringify_children(node):
    """Given a LXML tag, return contents as a string

       >>> html = "<p><strong>Sample sentence</strong> with tags.</p>"
       >>> node = lxml.html.fragment_fromstring(html)
       >>> extract_html_content(node)
       "<strong>Sample sentence</strong> with tags."
    """
    if node is None or (len(node) == 0 and not getattr(node, 'text', None)):
        return ""
    node.attrib.clear()
    opening_tag = len(node.tag) + 2
    closing_tag = -(len(node.tag) + 3)
    return lxml.html.tostring(node)[opening_tag:closing_tag]

В отличие от некоторых других ответов на этот вопросВопрос: это решение сохраняет все теги, содержащиеся в нем, и решает проблему под другим углом, чем другие рабочие решения.

...