web-dev-qa-db-fra.com

Récupère tout le texte contenu dans une balise en lxml

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>"
57
Kevin Burke

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'

39
albertov

Est-ce que text_content () fait ce dont vous avez besoin?

64
Ed Summers

Utilisez simplement la méthode node.itertext(), comme dans:

 ''.join(node.itertext())
48
Arthur Debert

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)
16
anana
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

4
d3day

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à.

3
Percival Ulysses

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.

2
Deepan Prabhu Babu

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.

2
bwingenroth

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.

1
Joshmaker

lxml a une méthode pour ça:

node.text_content()
0
Hrabal

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.

0
sergzach