Получить весь текст внутри тега в lxml


Я хотел бы написать фрагмент кода, который будет захватывать весь текст внутри <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 55

14 ответов:

попробуй:

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'

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

просто использовать node.itertext() способ, как в:

 ''.join(node.itertext())

версия альбертова преобразовать в строки-содержание что решает ошибки сообщает 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)

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

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

import urllib2
from lxml import etree
url = 'some_url'

получение url

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

получение всего html-кода в том числе тег таблицы

tree = etree.HTML(page)

в XPath селектор

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") 

или текста = дерево.xpath ("//content/text ()")

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

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

определение 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 не имеет атрибутов, и я не думаю, что кто-то захочет использовать его даже тогда.

в ответ на комментарий @Richard выше, если вы исправите 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])

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

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

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]

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

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

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

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

вот рабочее решение. Мы можем получить контент с родительским тегом, а затем вырезать Родительский тег из вывода.

import re
from lxml import etree

def _tostr_with_tags(parent_element, html_entities=False):
    RE_CUT = r'^<([\w-]+)>(.*)</([\w-]+)>$' 
    content_with_parent = etree.tostring(parent_element)    

    def _replace_html_entities(s):
        RE_ENTITY = r'&#(\d+);'

        def repl(m):
            return unichr(int(m.group(1)))

        replaced = re.sub(RE_ENTITY, repl, s, flags=re.MULTILINE|re.UNICODE)

        return replaced

    if not html_entities:
        content_with_parent = _replace_html_entities(content_with_parent)

    content_with_parent = content_with_parent.strip() # remove 'white' characters on margins

    start_tag, content_without_parent, end_tag = re.findall(RE_CUT, content_with_parent, flags=re.UNICODE|re.MULTILINE|re.DOTALL)[0]

    if start_tag != end_tag:
        raise Exception('Start tag does not match to end tag while getting content with tags.')

    return content_without_parent

parent_element должно быть Element тип.

обратите внимание: что если вы хотите текстовое содержимое (не html-объекты в тексте) , пожалуйста, оставьте html_entities параметр как False.

у lxml есть метод для этого:

node.text_content()

Если это тег a, вы можете попробовать:

node.values()
import re
from lxml import etree

node = etree.fromstring("""
<content>Text before inner tag
    <div>Text
        <em>inside</em>
        tag
    </div>
    Text after inner tag
</content>""")

print re.search("\A<[^<>]*>(.*)</[^<>]*>\Z", etree.tostring(node), re.DOTALL).group(1)