J'aimerais écrire un extrait de code qui saisisse tout le texte contenu dans la balise <content>
, en lxml, dans les trois instances ci-dessous, y compris les balises de code. J'ai essayé tostring(getchildren())
mais cela manquerait le texte entre les balises. Je n'ai pas eu beaucoup de chance en cherchant dans l'API une fonction pertinente. Pourrais-tu m'aider?
<!--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>"
Essayer:
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))
Exemple:
from lxml import etree
node = etree.fromstring("""<content>
Text outside tag <div>Text <em>inside</em> tag</div>
</content>""")
stringify_children(node)
Produit: '\nText outside tag <div>Text <em>inside</em> tag</div>\n'
Est-ce que text_content () fait ce dont vous avez besoin?
Utilisez simplement la méthode node.itertext()
, comme dans:
''.join(node.itertext())
Une version de stringify-content d'Albertov qui résout les bugs rapportés par 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)
import urllib2
from lxml import etree
url = 'some_url'
obtenir l'URL
test = urllib2.urlopen(url)
page = test.read()
obtenir tout le code html dans la balise de table
tree = etree.HTML(page)
sélecteur xpath
table = tree.xpath("xpath_here")
res = etree.tostring(table)
res est le code html de la table cela fonctionnait pour moi.
afin que vous puissiez extraire le contenu des balises avec xpath_text () et les balises, y compris leur contenu, à l'aide de tostring ()
div = tree.xpath("//div")
div_res = etree.tostring(div)
text = tree.xpath_text("//content")
ou text = tree.xpath ("// content/text ()")
div_3 = tree.xpath("//content")
div_3_res = etree.tostring(div_3).strip('<content>').rstrip('</')
cette dernière ligne avec la méthode du strip n'utilise pas Nice, mais ça marche
Définir stringify_children
de cette façon peut être moins compliqué:
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
ou en une ligne
return (node.text if node.text is not None else '') + ''.join((etree.tostring(child, encoding='unicode') for child in node))
La logique est la même que dans cette réponse : laissez la sérialisation des nœuds enfants à lxml. La partie tail
de node
dans ce cas n’est pas intéressante dans la mesure où elle est "derrière" la balise de fin. Notez que l'argument encoding
peut être modifié en fonction de vos besoins.
Une autre solution possible consiste à sérialiser le nœud lui-même, puis à enlever les balises de début et de fin:
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]
ce qui est un peu horrible. Ce code est correct uniquement si node
n'a pas d'attribut et je ne pense pas que quiconque voudrait l'utiliser même à ce moment-là.
Un des extraits de code les plus simples, qui a réellement fonctionné pour moi et conformément à la documentation de http://lxml.de/tutorial.html#using-xpath-to-find-text is
etree.tostring(html, method="text")
où etree est un noeud/balise dont vous essayez de lire le texte complet. Vous remarquerez qu'il ne supprime pas les balises de script et de style.
En réponse au commentaire de @ Richard ci-dessus, si vous corrigez stringify_children comme suit:
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])
il semble éviter le dédoublement dont il parle.
Je sais que c'est une vieille question, mais c'est un problème courant et j'ai une solution qui semble plus simple que celles suggérées jusqu'à présent:
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]
Contrairement à certaines des autres réponses à cette question, cette solution préserve toutes les balises qu’elle contient et attaque le problème sous un angle différent des autres solutions de travail.
lxml a une méthode pour ça:
node.text_content()
Voici une solution de travail. Nous pouvons obtenir du contenu avec une balise parent, puis couper la balise parent de la sortie.
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
doit avoir le type Element
.
Veuillez noter, que si vous souhaitez du contenu texte (pas des entités html dans le texte), veuillez laisser le paramètre html_entities
sur False.