Получить весь текст внутри тега в 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 ответов:
Попробуйте:
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 () то, что вам нужно?
Версия альбертова 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)
Следующий фрагмент кода, использующий генераторы 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. Часть
tailnodeв данном случае не интересна, так как она находится "за" конечным тегом. Обратите внимание, что аргументencodingможет быть изменен в соответствии с вашими потребностями.Другим возможным решением является сериализация самого узла, а затем удаление тега start и end прочь:
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.
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)
Comments