Получить весь текст внутри тега в 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>"
Ответы
Ответ 1
Try:
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'
Ответ 2
Помогает ли text_content() делать что вам нужно?
Ответ 3
Просто используйте метод node.itertext()
, как в:
''.join(node.itertext())
Ответ 4
Следующий фрагмент, который использует генераторы python, отлично работает и очень эффективен.
''.join(node.itertext()).strip()
Ответ 5
Версия альбертов 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)
Ответ 6
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 - 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 не является приятной, но она просто работает
Ответ 7
Определение 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
может быть изменен в соответствии с потребностями.
Еще одно возможное решение - сериализовать сам node, а затем отбросить начальный и конечный теги:
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
не имеет атрибутов, и я не думаю, что кто-то захочет его использовать даже тогда.
Ответ 8
В ответ на комментарий @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])
похоже, он избегает дублирования, на которое он ссылается.
Ответ 9
Один из простейших фрагментов кода, который действительно работал у меня и в соответствии с документацией в http://lxml.de/tutorial.html#using-xpath-to-find-text,
etree.tostring(html, method="text")
где etree - это node/tag, полный текст которого вы пытаетесь прочитать. Вот почему он не избавляется от script и тегов стиля.
Ответ 10
Я знаю, что это старый вопрос, но это общая проблема, и у меня есть решение, которое кажется более простым, чем предлагаемые до сих пор:
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]
В отличие от некоторых других ответов на этот вопрос, это решение сохраняет все теги, содержащиеся в нем, и атакует проблему под другим углом, чем другие рабочие решения.
Ответ 11
Вот рабочее решение. Мы можем получить контент с родительским тегом, а затем вырезать родительский тег из вывода.
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.
Ответ 12
lxml имеют способ для этого:
node.text_content()
Ответ 13
Если это тег, вы можете попробовать:
node.values()
Ответ 14
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)