Как итерация python lxml обрабатывает текст тега? [Дубликат]

У меня была та же проблема. В моем случае у меня было 2 вложенных Relative Layouts. RelativeLayout всегда должен выполнять два прохода. Если вы вложите RelativeLayouts, вы получите экспоненциальный алгоритм измерения.

54
задан Rafa 13 January 2016 в 21:17
поделиться

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'

35
ответ дан albertov 20 August 2018 в 11:39
поделиться
  • 1
    @delnan. Он не нужен, tostring уже обрабатывает рекурсивный случай. Вы заставили меня сомневаться, поэтому я попробовал это на реальном коде и обновил ответ на примере. Спасибо, что указали это. – albertov 7 January 2011 в 14:36
  • 2
    Код сломан и создает дублированный контент: & gt; & gt; & gt; & gt; & gt; stringify_children (lxmlhtml.fromstring ('A & lt; div & gt; B & lt; / div & gt; C')) 'A & lt; p & lt; / & gt; B & lt; div & gt; B & lt; / div & gt; CC' – hoju 10 January 2013 в 01:43
  • 3
    Чтобы исправить ошибку @hoju, добавьте with_tail=False в качестве параметра в tostring(). Итак, tostring(c, with_tail=False). Это устранит проблему с хвостовым текстом (C). Для исправления проблемы с префиксным текстом (A) это, кажется, ошибка в tostring(), которая добавляет тег <p>, поэтому это не ошибка в коде OP. – anana 27 January 2015 в 15:38
  • 4
    Вторую ошибку можно устранить, удалив c.text из списка parts. Я отправил новый ответ с исправленными ошибками. – anana 27 January 2015 в 16:20
  • 5
    Должно добавить tostring(c, encoding=str) для запуска на Python 3. – Antoine Dusséaux 9 January 2017 в 23:12

В ответ на комментарий Ричарда выше, если вы исправляете 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])

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

2
ответ дан bwingenroth 20 August 2018 в 11:39
поделиться
import urllib2
from lxml import etree
url = 'some_url'

получение url

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

получение всего html-кода внутри тега table

tree = etree.HTML(page)

селектор xpath

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

res is 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('</')

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

4
ответ дан d3day 20 August 2018 в 11:39
поделиться
  • 1
    Для меня это работает достаточно хорошо и, по общему признанию, намного проще. Я знаю, что у меня есть & lt; details & gt; & lt; / details & gt; тег - каждый раз - и я могу его разбить – Yablargo 15 January 2014 в 17:10
  • 2
    Удалить xpath_text из lxml? В нем говорится AttributeError: 'lxml.etree._Element' object has no attribute 'xpath_text' – roger 10 April 2015 в 07:40

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

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

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

1
ответ дан Deepan Prabhu Babu 20 August 2018 в 11:39
поделиться

lxml имеют метод для этого:

node.text_content()
-1
ответ дан Hrabal 20 August 2018 в 11:39
поделиться

Выполняет ли text_content () то, что вам нужно?

63
ответ дан Jacob Marble 20 August 2018 в 11:39
поделиться
  • 1
    text_content () удаляет всю разметку, и OP хочет сохранить разметку внутри тега. – benselme 29 October 2013 в 22:31
  • 2
    @benselme, почему я использую text_content, он говорит AttributeError: 'lxml.etree._Element' object has no attribute 'text_content' – roger 10 April 2015 в 07:38
  • 3
    @roger text_content() доступен, только если ваше дерево является HTML (т. е. если оно было проанализировано с помощью методов в lxml.html). – Louis 15 July 2015 в 19:14
  • 4
    @EdSummers Большое спасибо! Это полезно при анализе тега <p>. Мне не хватало текста (например, вложенные ссылки) при использовании text() в XPath, но ваш метод работал для меня !. – Sam Chats 6 July 2017 в 09:21

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

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]

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

1
ответ дан Joshmaker 20 August 2018 в 11:39
поделиться
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) 
-3
ответ дан kazufusa 20 August 2018 в 11:39
поделиться

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

3
ответ дан Percival Ulysses 20 August 2018 в 11:39
поделиться
  • 1
    node.text if node.text is not None else '' может быть просто node.txt or '' – yprez 11 March 2016 в 20:41
  • 2
    Играя в Лазаре немного здесь (шутка воскрешения ... не пунный), но я видел этот пост несколько раз, когда я не мог точно помнить, что я сделал. Данный node.text возвращает только текст, который не рассматривается как часть итератора (при итерации непосредственно в узел, так же как node.getChildren (), я считаю), кажется, что решение можно легко упростить от этого до: ''.join([node.text or ''] + [etree.tostring(e) for e in node]) – Tim Alexander 3 July 2017 в 18:11

Версия stringify-content Альбертова, которая решает баги , сообщенные 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)
15
ответ дан Peter Varo 20 August 2018 в 11:39
поделиться

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

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

15
ответ дан Sandeep 20 August 2018 в 11:39
поделиться
  • 1
    Это отлично работало, спасибо :) – J.J 23 October 2016 в 12:25
  • 2
    Круто! Ницца! Благодаря! – philipp 19 March 2018 в 16:52
  • 3
    Если узел получен из текста с отступом, в зависимости от анализатора, он обычно имеет текст отступа, который itertext () будет переплетаться в обычных текстовых фрагментах. В зависимости от фактической настройки может быть полезно следующее: ' '.join(node.itertext('span', 'b')) - использовать текст только с тегов <span> и <b>, отбрасывая теги с помощью «\n»; от отступа. – Zoltan K. 8 April 2018 в 10:28

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

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.

0
ответ дан sergzach 20 August 2018 в 11:39
поделиться

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

node.values()
-2
ответ дан Stony 20 August 2018 в 11:39
поделиться
  • 1
    Это не получает текст внутри тега, он получает атрибуты внутри тега. – Timothy P. Jurka 1 February 2013 в 21:28

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

 ''.join(node.itertext())
38
ответ дан vinzee 20 August 2018 в 11:39
поделиться
  • 1
    Это отлично работает, но удаляет любые теги, которые вы, возможно, захотите. – Yablargo 15 January 2014 в 16:50
  • 2
    ''.join(node.itertext()) выглядит чище. – Nigel Tufnel 18 February 2014 в 13:20
  • 3
    Один из этих случаев, который я хотел бы, имел больше, чем один, чтобы дать. – Private 22 April 2015 в 08:59
  • 4
    Должна ли строка содержать пробел? Или я чего-то не хватает? – Private 23 April 2015 в 09:56
  • 5
    @Private Это зависит от ваших конкретных потребностей. Например, я мог бы иметь разметку типа <word><pre>con</pre>gregate</word>, чтобы указать префикс в слове. Предположим, я хочу извлечь слово без разметки. Если я использую .join с пробелом, то я бы получил "con gregate", тогда как без пробела я получаю "congregate". – Louis 1 September 2015 в 20:18
Другие вопросы по тегам:

Похожие вопросы: