Supprimer le HTML des chaînes en Python


271
from mechanize import Browser
br = Browser()
br.open('http://somewebpage')
html = br.response().readlines()
for line in html:
  print line

Lors de l'impression d'une ligne dans un fichier HTML, j'essaie de trouver un moyen d'afficher uniquement le contenu de chaque élément HTML et non la mise en forme elle-même. S'il le trouve '<a href="whatever.com">some text</a>', il n'imprimera que «du texte», '<b>hello</b>'affichera «bonjour», etc. Comment procéder?


16
Une considération importante est de savoir comment gérer les entités HTML (par exemple &amp;). Vous pouvez soit 1) les supprimer avec les balises (souvent indésirables et inutiles car elles sont équivalentes à du texte brut), 2) les laisser inchangées (une solution appropriée si le texte supprimé revient directement dans un contexte HTML) ou 3 ) les décoder en texte brut (si le texte supprimé va dans une base de données ou un autre contexte non HTML, ou si votre infrastructure Web effectue automatiquement l'échappement HTML du texte pour vous).
Søren Løvborg

2
pour @ SørenLøvborg point 2): stackoverflow.com/questions/753052/…
Robert

2
La meilleure réponse ici, qui a été utilisée par le projet Django jusqu'en mars 2014, s'est révélée peu sûre contre les scripts intersites - voir ce lien pour un exemple qui le fait. Je recommande d'utiliser Bleach.clean (), les striptags de Markupsafe ou les strip_tags de RECENT Django.
rescdsk

Réponses:


419

J'ai toujours utilisé cette fonction pour supprimer les balises HTML, car elle ne nécessite que le stdlib Python:

Pour Python 3:

from io import StringIO
from html.parser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        super().__init__()
        self.reset()
        self.strict = False
        self.convert_charrefs= True
        self.text = StringIO()
    def handle_data(self, d):
        self.text.write(d)
    def get_data(self):
        return self.text.getvalue()

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

Pour Python 2:

from HTMLParser import HTMLParser
from StringIO import StringIO

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.text = StringIO()
    def handle_data(self, d):
        self.text.write(d)
    def get_data(self):
        return self.text.getvalue()

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

3
Deux ans + plus tard, face au même problème, et c'est une solution beaucoup plus élégante. La seule modification que j'ai apportée a été de renvoyer self.fed sous forme de liste, plutôt que de la rejoindre, afin de pouvoir parcourir le contenu de l'élément.
directedition

47
Notez que cela supprime les entités HTML (par exemple &amp;) ainsi que les balises.
Søren Løvborg du

30
@surya Je suis sûr que vous l'avez vu
tkone

8
Merci pour la bonne réponse. Une chose à noter pour ceux d'entre vous qui utilisent des versions plus récentes de Python (3.2+) est que vous devrez appeler la __init__fonction de la classe parent . Voir ici: stackoverflow.com/questions/11061058/… .
pseudoramble

10
Pour conserver les entités html (converties en unicode), j'ai ajouté deux lignes: parser = HTMLParser()et html = parser.unescape(html)au début de la fonction strip_tags.
James Doepp - pihentagyu

157

Je n'ai pas beaucoup réfléchi aux cas qui vont manquer, mais vous pouvez faire un regex simple:

re.sub('<[^<]+?>', '', text)

Pour ceux qui ne comprennent pas l'expression régulière, cela recherche une chaîne <...>, où le contenu interne est composé d'un ou plusieurs caractères ( +) qui ne sont pas un <. Les ?moyens qu'il correspondra à la plus petite chaîne qu'il peut trouver. Par exemple <p>Hello</p>, il correspondra <'p>et </p>séparément avec le ?. Sans cela, il correspondra à la chaîne entière <..Hello..>.

Si une non-balise <apparaît en html (par exemple. 2 < 3), Elle doit de toute façon être écrite sous forme de séquence d'échappement &...afin que cela ^<ne soit pas nécessaire.


10
C'est presque exactement ainsi que les strip_tags de Django le font .
Bluu

10
Notez que cela laisse les entités HTML (par exemple &amp;) inchangées dans la sortie.
Søren Løvborg

36
On peut toujours tromper cette méthode avec quelque chose comme ceci: <script <script>> alert ("Hi!") <</script> / script>

19
NE LE FAITES PAS DE CETTE FAÇON! Comme le dit @Julio Garcia, ce n'est PAS SÛR!
rescdsk

18
Les gens, ne confondez pas la suppression HTML et la désinfection HTML. Oui, pour une entrée cassée ou malveillante, cette réponse peut produire une sortie avec des balises HTML. C'est toujours une approche parfaitement valable pour supprimer les balises HTML. Cependant , supprimer les balises HTML n'est pas une substitution valide pour un nettoyage HTML correct. La règle n'est pas difficile: chaque fois que vous insérez une chaîne de texte en clair dans la sortie HTML, vous devez toujours y échapper (en utilisant cgi.escape(s, True)), même si vous "savez" qu'elle ne contient pas de HTML (par exemple parce que vous avez supprimé le contenu HTML) . Cependant, ce n'est pas ce que OP a demandé.
Søren Løvborg

77

Vous pouvez utiliser la get_text()fonction BeautifulSoup .

from bs4 import BeautifulSoup

html_str = '''
<td><a href="http://www.fakewebsite.com">Please can you strip me?</a>
<br/><a href="http://www.fakewebsite.com">I am waiting....</a>
</td>
'''
soup = BeautifulSoup(html_str)

print(soup.get_text()) 
#or via attribute of Soup Object: print(soup.text)

Il est conseillé de spécifier explicitement l' analyseur , par exemple as BeautifulSoup(html_str, features="html.parser"), pour que la sortie soit reproductible.


32

Version courte!

import re, cgi
tag_re = re.compile(r'(<!--.*?-->|<[^>]*>)')

# Remove well-formed tags, fixing mistakes by legitimate users
no_tags = tag_re.sub('', user_input)

# Clean up anything else by escaping
ready_for_web = cgi.escape(no_tags)

Source d'expression régulière: MarkupSafe . Leur version gère également les entités HTML, contrairement à celle rapide.

Pourquoi ne puis-je pas simplement retirer les étiquettes et les laisser?

C'est une chose de garder les gens à l' <i>italicizing</i>écart des choses, sans laisser is flotter. Mais c'est une autre chose de prendre des entrées arbitraires et de les rendre complètement inoffensives. La plupart des techniques de cette page laisseront intacts des éléments comme les commentaires non fermés ( <!--) et les crochets qui ne font pas partie des balises ( blah <<<><blah). La version HTMLParser peut même laisser des balises complètes, si elles se trouvent dans un commentaire non fermé.

Et si votre modèle l'est {{ firstname }} {{ lastname }}? firstname = '<a'et lastname = 'href="http://evil.com/">'seront laissés passer par tous les décapants de balises sur cette page (sauf @Medeiros!), car ce ne sont pas des balises complètes par eux-mêmes. Il ne suffit pas de supprimer les balises HTML normales.

Django's strip_tags, une version améliorée (voir le titre suivant) de la réponse la plus importante à cette question, donne l'avertissement suivant:

Absolument AUCUNE garantie n'est fournie sur la chaîne résultante étant HTML sécurisée. Donc, NE marquez JAMAIS en sécurité le résultat d'un strip_tagsappel sans y échapper au préalable, par exemple avec escape().

Suivez leurs conseils!

Pour supprimer les balises avec HTMLParser, vous devez l'exécuter plusieurs fois.

Il est facile de contourner la première réponse à cette question.

Regardez cette chaîne ( source et discussion ):

<img<!-- --> src=x onerror=alert(1);//><!-- -->

La première fois que HTMLParser le voit, il ne peut pas dire qu'il <img...>s'agit d'une balise. Il a l'air cassé, donc HTMLParser ne s'en débarrasse pas. Il enlève seulement le <!-- comments -->, vous laissant avec

<img src=x onerror=alert(1);//>

Ce problème a été divulgué au projet Django en mars 2014. Leur ancien strip_tagsétait essentiellement le même que la première réponse à cette question. Leur nouvelle version l' exécute essentiellement en boucle jusqu'à ce que la relancer ne change pas la chaîne:

# _strip_once runs HTMLParser once, pulling out just the text of all the nodes.

def strip_tags(value):
    """Returns the given HTML with all tags stripped."""
    # Note: in typical case this loop executes _strip_once once. Loop condition
    # is redundant, but helps to reduce number of executions of _strip_once.
    while '<' in value and '>' in value:
        new_value = _strip_once(value)
        if len(new_value) >= len(value):
            # _strip_once was not able to detect more tags
            break
        value = new_value
    return value

Bien sûr, rien de tout cela n'est un problème si vous échappez toujours au résultat de strip_tags().

Mise à jour 19 mars 2015 : il y avait un bogue dans les versions de Django avant 1.4.20, 1.6.11, 1.7.7 et 1.8c1. Ces versions pourraient entrer dans une boucle infinie dans la fonction strip_tags (). La version fixe est reproduite ci-dessus. Plus de détails ici .

Bonnes choses à copier ou à utiliser

Mon exemple de code ne gère pas les entités HTML - les versions packagées Django et MarkupSafe le font.

Mon exemple de code est tiré de l'excellente bibliothèque MarkupSafe pour la prévention des scripts intersites . C'est pratique et rapide (avec des accélérations C vers sa version native Python). Il est inclus dans Google App Engine et utilisé par Jinja2 (2.7 et plus) , Mako, Pylons, etc. Il fonctionne facilement avec les modèles Django de Django 1.7.

Les strip_tags de Django et les autres utilitaires html d'une version récente sont bons, mais je les trouve moins pratiques que MarkupSafe. Ils sont assez autonomes, vous pouvez copier ce dont vous avez besoin à partir de ce fichier .

Si vous devez supprimer presque toutes les balises, la bibliothèque Bleach est bonne. Vous pouvez lui faire appliquer des règles telles que «mes utilisateurs peuvent mettre les choses en italique, mais ils ne peuvent pas créer d'iframes».

Comprenez les propriétés de votre strip-teaseuse! Exécutez des tests fuzz dessus! Voici le code que j'ai utilisé pour faire la recherche de cette réponse.

note penaude - La question elle-même concerne l'impression sur la console, mais c'est le meilleur résultat Google pour "python strip html from string", c'est pourquoi cette réponse est à 99% sur le Web.


Mon exemple de code "dernière ligne alternative" ne gère pas les entités html - à quel point est-ce mauvais?
rescdsk

Je suis en train d'analyser seulement un petit morceau de HTML sans balises spéciales, et votre version courte fait très bien le travail. Merci d'avoir partagé!
tbolender

31

J'avais besoin d'un moyen de supprimer les balises et de décoder les entités HTML en texte brut. La solution suivante est basée sur la réponse d'Eloff (que je ne pouvais pas utiliser car elle dépouille les entités).

from HTMLParser import HTMLParser
import htmlentitydefs

class HTMLTextExtractor(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.result = [ ]

    def handle_data(self, d):
        self.result.append(d)

    def handle_charref(self, number):
        codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number)
        self.result.append(unichr(codepoint))

    def handle_entityref(self, name):
        codepoint = htmlentitydefs.name2codepoint[name]
        self.result.append(unichr(codepoint))

    def get_text(self):
        return u''.join(self.result)

def html_to_text(html):
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

Un test rapide:

html = u'<a href="#">Demo <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>'
print repr(html_to_text(html))

Résultat:

u'Demo (\xac \u0394\u03b7\u03bc\u03ce)'

La gestion des erreurs:

  • Une structure HTML non valide peut provoquer une erreur HTMLParseError .
  • Les entités HTML nommées non valides (telles que &#apos;, qui sont valides en XML et XHTML, mais pas en HTML simple) provoqueront une ValueErrorexception.
  • Les entités HTML numériques spécifiant des points de code en dehors de la plage Unicode acceptables par Python (comme, sur certains systèmes, des caractères en dehors du plan multilingue de base ) provoqueront une ValueErrorexception.

Note de sécurité: ne confondez pas le dépouillement HTML (conversion de HTML en texte brut) avec la désinfection HTML (conversion de texte brut en HTML). Cette réponse supprimera le HTML et décodera les entités en texte brut - ce qui ne rend pas le résultat sûr à utiliser dans un contexte HTML.

Exemple: &lt;script&gt;alert("Hello");&lt;/script&gt;sera converti en <script>alert("Hello");</script>, ce qui est un comportement correct à 100%, mais évidemment insuffisant si le texte brut résultant est inséré tel quel dans une page HTML.

La règle n'est pas difficile: chaque fois que vous insérez une chaîne de texte en clair dans la sortie HTML, vous devez toujours y échapper (en utilisant cgi.escape(s, True)), même si vous "savez" qu'elle ne contient pas de HTML (par exemple parce que vous avez supprimé le contenu HTML) .

(Cependant, l'OP a demandé comment imprimer le résultat sur la console, auquel cas aucun échappement HTML n'est nécessaire.)

Version Python 3.4+: (avec doctest!)

import html.parser

class HTMLTextExtractor(html.parser.HTMLParser):
    def __init__(self):
        super(HTMLTextExtractor, self).__init__()
        self.result = [ ]

    def handle_data(self, d):
        self.result.append(d)

    def get_text(self):
        return ''.join(self.result)

def html_to_text(html):
    """Converts HTML to plain text (stripping tags and converting entities).
    >>> html_to_text('<a href="#">Demo<!--...--> <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>')
    'Demo (\xac \u0394\u03b7\u03bc\u03ce)'

    "Plain text" doesn't mean result can safely be used as-is in HTML.
    >>> html_to_text('&lt;script&gt;alert("Hello");&lt;/script&gt;')
    '<script>alert("Hello");</script>'

    Always use html.escape to sanitize text before using in an HTML context!

    HTMLParser will do its best to make sense of invalid HTML.
    >>> html_to_text('x < y &lt z <!--b')
    'x < y < z '

    Unrecognized named entities are included as-is. '&apos;' is recognized,
    despite being XML only.
    >>> html_to_text('&nosuchentity; &apos; ')
    "&nosuchentity; ' "
    """
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

Notez que HTMLParser s'est amélioré en Python 3 (ce qui signifie moins de code et une meilleure gestion des erreurs).


18

Il existe un moyen simple pour cela:

def remove_html_markup(s):
    tag = False
    quote = False
    out = ""

    for c in s:
            if c == '<' and not quote:
                tag = True
            elif c == '>' and not quote:
                tag = False
            elif (c == '"' or c == "'") and tag:
                quote = not quote
            elif not tag:
                out = out + c

    return out

L'idée est expliquée ici: http://youtu.be/2tu9LTDujbw

Vous pouvez le voir fonctionner ici: http://youtu.be/HPkNPcYed9M?t=35s

PS - Si vous êtes intéressé par la classe (sur le débogage intelligent avec python) je vous donne un lien: http://www.udacity.com/overview/Course/cs259/CourseRev/1 . C'est gratuit!

Vous êtes les bienvenus! :)


2
Je me demande pourquoi cette réponse vient d'être rejetée. C'est un moyen simple de résoudre le problème sans aucune lib. Juste du python pur et cela fonctionne comme le montrent les liens.
Medeiros

2
Les gens préfèrent probablement les bibliothèques pour leur assurer la sécurité. Je vous ai testé et passé le code, et je préfère toujours le petit code que je comprends plutôt que d'utiliser une bibliothèque et en supposant qu'il est correct jusqu'à ce qu'un bug apparaisse. Pour moi, c'est ce que je cherchais et encore merci. En ce qui concerne les downvotes, ne vous mettez pas dans cet état d'esprit. Les gens d'ici devraient se soucier de la qualité et non des votes. Dernièrement, SO est devenu un endroit où tout le monde veut des points et non des connaissances.
Jimmy Kane

2
Le problème avec cette solution est la gestion des erreurs. Par exemple, si vous donnez <b class="o'>x</b>comme sorties des fonctions d'entrée x. Mais en réalité, cette entrée n'est pas valide. Je pense que c'est pourquoi les gens préfèrent les bibliothèques.
laltin

1
Il fonctionne également avec cette entrée. Je viens de tester. Sachez simplement qu'à l'intérieur de ces bibliothèques, vous trouverez un code similaire. Ce n'est pas très pythonique, je sais. Ressemble à du code C ou Java. Je pense que c'est efficace et peut facilement être porté dans une autre langue.
Medeiros

1
Simple, Pythonic et semble fonctionner aussi bien ou mieux que toutes les autres méthodes discutées. Il est possible que cela ne fonctionne pas pour certains HTML mal formés, mais il n'y a pas de solution à cela.
denson

16

Si vous avez besoin de conserver des entités HTML (ie &amp;), j'ai ajouté la méthode "handle_entityref" à la réponse d' Eloff .

from HTMLParser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
    def handle_data(self, d):
        self.fed.append(d)
    def handle_entityref(self, name):
        self.fed.append('&%s;' % name)
    def get_data(self):
        return ''.join(self.fed)

def html_to_text(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

13

Si vous souhaitez supprimer toutes les balises HTML, la méthode la plus simple que j'ai trouvée consiste à utiliser BeautifulSoup:

from bs4 import BeautifulSoup  # Or from BeautifulSoup import BeautifulSoup

def stripHtmlTags(htmlTxt):
    if htmlTxt is None:
            return None
        else:
            return ''.join(BeautifulSoup(htmlTxt).findAll(text=True)) 

J'ai essayé le code de la réponse acceptée mais j'obtenais "RuntimeError: la profondeur de récursivité maximale dépassée", ce qui ne s'est pas produit avec le bloc de code ci-dessus.


1
Je viens d'essayer votre méthode car elle semble plus propre, elle a fonctionné, enfin en quelque sorte ... elle n'a pas supprimé les balises d'entrée!
kustomrtr

Je trouve qu'une simple application de BeautifulSoup a un problème avec les espaces blancs: ''.join(BeautifulSoup('<em>he</em>llo<br>world').find_all(text=True)). Ici, la sortie est "helloworld", alors que vous voulez probablement que ce soit "hello world". ' '.join(BeautifulSoup('<em>he</em>llo<br>world').find_all(text=True))n'aide pas car il devient "he llo world".
Finn Årup Nielsen

@kustomrtr, désolé mon ignorance, que dois-je mettre dans l'argument de soi? NameError: le nom 'self' n'est pas défini
Ian_De_Oliveira

@Ian_De_Oliveira Vous pouvez le supprimer, j'ai supposé qu'il se trouve dans une classe mais ce n'est pas nécessaire. J'ai également modifié la réponse pour la supprimer
Vasilis

@Ian_De_Oliveira Vous pouvez le supprimer, j'ai supposé qu'il se trouve dans une classe mais ce n'est pas nécessaire. J'ai également modifié la réponse pour la supprimer
Vasilis

10

Voici une solution simple qui supprime les balises HTML et décode les entités HTML basées sur la lxmlbibliothèque incroyablement rapide :

from lxml import html

def strip_html(s):
    return str(html.fromstring(s).text_content())

strip_html('Ein <a href="">sch&ouml;ner</a> Text.')  # Output: Ein schöner Text.

3
À partir de 2020, c'était le moyen le plus rapide et le meilleur pour séparer le contenu du HTML. Plus le bonus de gérer le décodage. Idéal pour la détection de la langue!
dfabiano

text_content()revient lxml.etree._ElementUnicodeResultdonc vous devrez peut-être le lancer en chaîne d'abord
Suzana

1
@Suzana Bon point. Il semble être casté automatiquement strpour les opérations de chaîne comme l' +indexation []. Ajout d'un casting pour faire bonne mesure de toute façon.
Robin Dinse

9

Une solution basée sur lxml.html (lxml est une bibliothèque native et donc beaucoup plus rapide que toute solution python pure).

from lxml import html
from lxml.html.clean import clean_html

tree = html.fromstring("""<span class="item-summary">
                            Detailed answers to any questions you might have
                        </span>""")

print(clean_html(tree).strip())

# >>> Detailed answers to any questions you might have

Voir également http://lxml.de/lxmlhtml.html#cleaning-up-html pour ce que fait exactement le lxml.cleaner.

Si vous avez besoin de plus de contrôle sur ce qui est purifié avant de le convertir en texte, vous pouvez utiliser explicitement le nettoyeur lxml en passant les options souhaitées dans le constructeur, par exemple:

cleaner = Cleaner(page_structure=True,
                  meta=True,
                  embedded=True,
                  links=True,
                  style=True,
                  processing_instructions=True,
                  inline_style=True,
                  scripts=True,
                  javascript=True,
                  comments=True,
                  frames=True,
                  forms=True,
                  annoying_tags=True,
                  remove_unknown_tags=True,
                  safe_attrs_only=True,
                  safe_attrs=frozenset(['src','color', 'href', 'title', 'class', 'name', 'id']),
                  remove_tags=('span', 'font', 'div')
                  )
sanitized_html = cleaner.clean_html(unsafe_html)

1
Je suis arrivé AttributeError: objet 'HtmlElement' n'a pas d' attribut 'strip'
aris

7

Le package Beautiful Soup le fait immédiatement pour vous.

from bs4 import BeautifulSoup

soup = BeautifulSoup(html)
text = soup.get_text()
print(text)

3
De la file d'attente d'examen: Puis-je vous demander de bien vouloir ajouter un peu plus de contexte autour de votre réponse. Les réponses uniquement codées sont difficiles à comprendre. Cela aidera le demandeur et les futurs lecteurs à la fois si vous pouvez ajouter plus d'informations dans votre message.
help-info.de

2

Voici ma solution pour python 3.

import html
import re

def html_to_txt(html_text):
    ## unescape html
    txt = html.unescape(html_text)
    tags = re.findall("<[^>]+>",txt)
    print("found tags: ")
    print(tags)
    for tag in tags:
        txt=txt.replace(tag,'')
    return txt

Je ne sais pas si c'est parfait, mais j'ai résolu mon cas d'utilisation et cela semble simple.


2

Vous pouvez utiliser un analyseur HTML différent ( comme lxml ou Beautiful Soup ) - celui qui offre des fonctions pour extraire uniquement du texte. Ou, vous pouvez exécuter une expression régulière sur votre chaîne de ligne qui supprime les balises. Voir les documents Python pour plus.


1
amk link est mort. Vous avez une alternative?

2
Le site Web de Python propose maintenant de bons tutoriels, voici le tutoriel
Jason Coon

5
En lxml:lxml.html.fromstring(s).text_content()
Bluu

1
L'exemple de Bluu avec lxml décode les entités HTML (par exemple &amp;) en texte.
Søren Løvborg

1

J'ai utilisé la réponse d'Eloff avec succès pour Python 3.1 [merci beaucoup!].

J'ai effectué une mise à niveau vers Python 3.2.3 et j'ai rencontré des erreurs.

La solution, fournie ici grâce au répondeur Thomas K, est d'insérer super().__init__()dans le code suivant:

def __init__(self):
    self.reset()
    self.fed = []

... pour que ça ressemble à ceci:

def __init__(self):
    super().__init__()
    self.reset()
    self.fed = []

... et cela fonctionnera pour Python 3.2.3.

Encore une fois, merci à Thomas K pour le correctif et pour le code original d'Eloff fournis ci-dessus!


1

Vous pouvez écrire votre propre fonction:

def StripTags(text):
     finished = 0
     while not finished:
         finished = 1
         start = text.find("<")
         if start >= 0:
             stop = text[start:].find(">")
             if stop >= 0:
                 text = text[:start] + text[start+stop+1:]
                 finished = 0
     return text

1
L'ajout aux chaînes crée-t-il une nouvelle copie de la chaîne?
Jeremy L

1
@Nerdling - Oui, ce qui peut entraîner des inefficacités assez impressionnantes dans les fonctions fréquemment utilisées (ou, d'ailleurs, des fonctions rarement utilisées qui agissent sur de grandes taches de texte.) Voir cette page pour plus de détails. : D
Jeremy Sandell

Teste-t-il les chaînes citées? Non.
Jimmy Kane

1

Les solutions avec HTML-Parser sont toutes cassables, si elles ne s'exécutent qu'une seule fois:

html_to_text('<<b>script>alert("hacked")<</b>/script>

résulte en:

<script>alert("hacked")</script>

ce que vous avez l'intention d'empêcher. si vous utilisez un analyseur HTML, comptez les balises jusqu'à ce que zéro soit remplacé:

from HTMLParser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
        self.containstags = False

    def handle_starttag(self, tag, attrs):
       self.containstags = True

    def handle_data(self, d):
        self.fed.append(d)

    def has_tags(self):
        return self.containstags

    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    must_filtered = True
    while ( must_filtered ):
        s = MLStripper()
        s.feed(html)
        html = s.get_data()
        must_filtered = s.has_tags()
    return html

1
Si vous appelez une fonction appelée html_to_textet que vous incorporez le texte en sortie de cette fonction dans html sans échapper à ce texte, c'est le manque d'échappement, qui est une vulnérabilité de sécurité, pas la html_to_textfonction. La html_to_textfonction ne vous a jamais promis que la sortie serait du texte. Et l'insertion de texte en html sans s'échapper est une vulnérabilité de sécurité potentielle, que vous ayez obtenu le texte html_to_text ou une autre source.
kasperd

Vous avez raison dans le cas d'un manque d'échappatoire, mais la question était de supprimer le html d'une chaîne donnée pour ne pas échapper à une chaîne donnée. Si les réponses précédentes créent de nouveaux fichiers HTML avec leurs solutions à la suite de la suppression de certains fichiers HTML, l'utilisation de ces solutions est alors dangereuse.
Falk Nisius

1

C'est une solution rapide et peut être encore plus optimisée, mais cela fonctionnera bien. Ce code remplacera toutes les balises non vides par "" et supprime toutes les balises html d'un texte d'entrée donné. Vous pouvez l'exécuter à l'aide de la sortie d'entrée ./file.py

    #!/usr/bin/python
import sys

def replace(strng,replaceText):
    rpl = 0
    while rpl > -1:
        rpl = strng.find(replaceText)
        if rpl != -1:
            strng = strng[0:rpl] + strng[rpl + len(replaceText):]
    return strng


lessThanPos = -1
count = 0
listOf = []

try:
    #write File
    writeto = open(sys.argv[2],'w')

    #read file and store it in list
    f = open(sys.argv[1],'r')
    for readLine in f.readlines():
        listOf.append(readLine)         
    f.close()

    #remove all tags  
    for line in listOf:
        count = 0;  
        lessThanPos = -1  
        lineTemp =  line

            for char in lineTemp:

            if char == "<":
                lessThanPos = count
            if char == ">":
                if lessThanPos > -1:
                    if line[lessThanPos:count + 1] != '<>':
                        lineTemp = replace(lineTemp,line[lessThanPos:count + 1])
                        lessThanPos = -1
            count = count + 1
        lineTemp = lineTemp.replace("&lt","<")
        lineTemp = lineTemp.replace("&gt",">")                  
        writeto.write(lineTemp)  
    writeto.close() 
    print "Write To --- >" , sys.argv[2]
except:
    print "Help: invalid arguments or exception"
    print "Usage : ",sys.argv[0]," inputfile outputfile"

1

Une adaptation en python 3 de la réponse de Søren-Løvborg

from html.parser import HTMLParser
from html.entities import html5

class HTMLTextExtractor(HTMLParser):
    """ Adaption of http://stackoverflow.com/a/7778368/196732 """
    def __init__(self):
        super().__init__()
        self.result = []

    def handle_data(self, d):
        self.result.append(d)

    def handle_charref(self, number):
        codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number)
        self.result.append(unichr(codepoint))

    def handle_entityref(self, name):
        if name in html5:
            self.result.append(unichr(html5[name]))

    def get_text(self):
        return u''.join(self.result)

def html_to_text(html):
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

1

Pour un projet, j'avais besoin de strip HTML, mais aussi css et js. Ainsi, j'ai fait une variation de la réponse d'Eloffs:

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.strict = False
        self.convert_charrefs= True
        self.fed = []
        self.css = False
    def handle_starttag(self, tag, attrs):
        if tag == "style" or tag=="script":
            self.css = True
    def handle_endtag(self, tag):
        if tag=="style" or tag=="script":
            self.css=False
    def handle_data(self, d):
        if not self.css:
            self.fed.append(d)
    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

1

Voici une solution similaire à la réponse actuellement acceptée ( https://stackoverflow.com/a/925630/95989 ), sauf qu'elle utilise HTMLParserdirectement la classe interne (c.-à-d. Pas de sous-classement), la rendant ainsi beaucoup plus concise:

def strip_html (texte):
    pièces = []                                                                      
    parser = HTMLParser ()                                                           
    parser.handle_data = parts.append                                               
    parser.feed (texte)                                                               
    retourner '' .join (pièces)

0

J'analyse les fichiers Lisezmoi de Github et je trouve que ce qui suit fonctionne vraiment bien:

import re
import lxml.html

def strip_markdown(x):
    links_sub = re.sub(r'\[(.+)\]\([^\)]+\)', r'\1', x)
    bold_sub = re.sub(r'\*\*([^*]+)\*\*', r'\1', links_sub)
    emph_sub = re.sub(r'\*([^*]+)\*', r'\1', bold_sub)
    return emph_sub

def strip_html(x):
    return lxml.html.fromstring(x).text_content() if x else ''

Puis

readme = """<img src="https://raw.githubusercontent.com/kootenpv/sky/master/resources/skylogo.png" />

            sky is a web scraping framework, implemented with the latest python versions in mind (3.4+). 
            It uses the asynchronous `asyncio` framework, as well as many popular modules 
            and extensions.

            Most importantly, it aims for **next generation** web crawling where machine intelligence 
            is used to speed up the development/maintainance/reliability of crawling.

            It mainly does this by considering the user to be interested in content 
            from *domains*, not just a collection of *single pages*
            ([templating approach](#templating-approach))."""

strip_markdown(strip_html(readme))

Supprime tous les démarques et html correctement.


0

En utilisant BeautifulSoup, html2text ou le code de @Eloff, la plupart du temps, il reste des éléments html, du code javascript ...

Vous pouvez donc utiliser une combinaison de ces bibliothèques et supprimer le formatage de démarque (Python 3):

import re
import html2text
from bs4 import BeautifulSoup
def html2Text(html):
    def removeMarkdown(text):
        for current in ["^[ #*]{2,30}", "^[ ]{0,30}\d\\\.", "^[ ]{0,30}\d\."]:
            markdown = re.compile(current, flags=re.MULTILINE)
            text = markdown.sub(" ", text)
        return text
    def removeAngular(text):
        angular = re.compile("[{][|].{2,40}[|][}]|[{][*].{2,40}[*][}]|[{][{].{2,40}[}][}]|\[\[.{2,40}\]\]")
        text = angular.sub(" ", text)
        return text
    h = html2text.HTML2Text()
    h.images_to_alt = True
    h.ignore_links = True
    h.ignore_emphasis = False
    h.skip_internal_links = True
    text = h.handle(html)
    soup = BeautifulSoup(text, "html.parser")
    text = soup.text
    text = removeAngular(text)
    text = removeMarkdown(text)
    return text

Cela fonctionne bien pour moi, mais il peut être amélioré, bien sûr ...


0

Code simple!. Cela supprimera toutes sortes de balises et de contenu à l'intérieur.

def rm(s):
    start=False
    end=False
    s=' '+s
    for i in range(len(s)-1):
        if i<len(s):
            if start!=False:
                if s[i]=='>':
                    end=i
                    s=s[:start]+s[end+1:]
                    start=end=False
            else:
                if s[i]=='<':
                    start=i
    if s.count('<')>0:
        self.rm(s)
    else:
        s=s.replace('&nbsp;', ' ')
        return s

Mais cela ne donnera pas le résultat complet si le texte contient des symboles <> à l' intérieur.


0
# This is a regex solution.
import re
def removeHtml(html):
  if not html: return html
  # Remove comments first
  innerText = re.compile('<!--[\s\S]*?-->').sub('',html)
  while innerText.find('>')>=0: # Loop through nested Tags
    text = re.compile('<[^<>]+?>').sub('',innerText)
    if text == innerText:
      break
    innerText = text

  return innerText.strip()

-2

Cette méthode fonctionne parfaitement pour moi et ne nécessite aucune installation supplémentaire:

import re
import htmlentitydefs

def convertentity(m):
    if m.group(1)=='#':
        try:
            return unichr(int(m.group(2)))
        except ValueError:
            return '&#%s;' % m.group(2)
        try:
            return htmlentitydefs.entitydefs[m.group(2)]
        except KeyError:
            return '&%s;' % m.group(2)

def converthtml(s):
    return re.sub(r'&(#?)(.+?);',convertentity,s)

html =  converthtml(html)
html.replace("&nbsp;", " ") ## Get rid of the remnants of certain formatting(subscript,superscript,etc).

3
Cela décode les entités HTML en texte brut, mais ne supprime évidemment aucune balise, ce qui était la question d'origine. (De plus, le deuxième bloc try-except doit être désindenté pour que le code en fasse autant).
Søren Løvborg
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.