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 le formatage lui-même. S'il trouve '<a href="whatever.com">some text</a>'
, il n'imprimera que 'du texte', '<b>hello</b>'
affiche 'bonjour', etc. Comment procéder?
J'ai toujours utilisé cette fonction pour supprimer les balises HTML, car elle ne nécessite que la stdlib Python:
Sur Python 2
from HTMLParser import HTMLParser
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.fed = []
def handle_data(self, d):
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()
Pour Python 3
from html.parser import HTMLParser
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.strict = False
self.convert_charrefs= True
self.fed = []
def handle_data(self, d):
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()
Note: cela ne fonctionne que pour 3.1. Pour 3.2 ou plus, vous devez appeler la fonction init de la classe parente. Voir Utiliser HTMLParser en Python 3.2
Je n'ai pas beaucoup réfléchi aux cas qui vont lui manquer, mais vous pouvez faire une regex simple:
re.sub('<[^<]+?>', '', text)
Pour ceux qui ne comprennent pas les expressions rationnelles, ceci recherche une chaîne <...>
, où le contenu interne est composé d'un ou plusieurs caractères (+
) qui ne sont pas un <
. Le ?
signifie qu'il correspondra à la plus petite chaîne qu'il peut trouver. Par exemple, si l'on donne <p>Hello</p>
, il fera correspondre <'p>
et </p>
séparément avec le ?
. Sans cela, il correspondra à la chaîne entière <..Hello..>
.
Si le <
non-tag apparaît en HTML (par exemple, 2 < 3
), il devrait être écrit sous la forme d'une séquence d'échappement &...
de toute façon, de sorte que le ^<
puisse être inutile.
Pourquoi vous le faites tous à la dure? Vous pouvez utiliser la fonction BeautifulSoup get_text()
.
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)
J'avais besoin d'un moyen de décaper les balises et décoder les entités HTML en texte brut. La solution suivante est basée sur la réponse d'Eloff (que je n'ai pas pu utiliser car elle supprime 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>(¬ \u0394ημώ)</em></a>'
print repr(html_to_text(html))
Résultat:
u'Demo (\xac \u0394\u03b7\u03bc\u03ce)'
La gestion des erreurs:
&#apos;
, qui est valide en XML et XHTML, mais pas en HTML pur) provoqueront une exception ValueError
.ValueError
.Note de sécurité: Ne confondez pas la suppression HTML (conversion de HTML en texte brut) avec la purification de HTML (conversion du texte brut en HTML). Cette réponse supprimera le HTML et décodera les entités en texte brut - cela ne sécurisera pas l'utilisation du résultat dans un contexte HTML.
Exemple: <script>alert("Hello");</script>
sera converti en <script>alert("Hello");</script>
, 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: à tout moment vous insérez une chaîne de texte en clair dans la sortie HTML, vous devez toujours HTML l’échapper (avec cgi.escape(s, True)
), même si vous "savez" que cela ne contient pas HTML (par exemple parce que vous avez supprimé le contenu HTML).
(Cependant, l'OP a demandé si le résultat était imprimé 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>(¬ \u0394ημώ)</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('<script>alert("Hello");</script>')
'<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 < z <!--b')
'x < y < z '
Unrecognized named entities are included as-is. ''' is recognized,
despite being XML only.
>>> html_to_text('&nosuchentity; ' ')
"&nosuchentity; ' "
"""
s = HTMLTextExtractor()
s.feed(html)
return s.get_text()
Notez que HTMLParser a été amélioré dans Python 3 (moins de code et une meilleure gestion des erreurs).
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 Regex: MarkupSafe . Leur version gère aussi les entités HTML, alors que cette rapide ne le fait pas.
C'est une chose de garder les gens de <i>italicizing</i>
choses, sans laisser i
s flotter. Mais c’est une autre chose de prendre des entrées arbitraires et de les rendre complètement inoffensives. La plupart des techniques sur cette page laissent intacts les commentaires non fermés (<!--
) et les crochets angulaires ne faisant pas partie des balises (blah <<<><blah
). La version HTMLParser peut même laisser des balises complètes à l'intérieur, si elles se trouvent dans un commentaire non fermé.
Et si votre modèle est {{ firstname }} {{ lastname }}
? firstname = '<a'
et lastname = 'href="http://evil.com/">'
seront utilisés par chaque strip-teaseuse sur cette page (sauf @Medeiros!), car ce ne sont pas des balises complètes. Supprimer les balises HTML normales ne suffit pas.
La version strip_tags
de Django, une version améliorée (voir en-tête suivante) de la première réponse à cette question, donne l'avertissement suivant:
Absolument aucune garantie n'est fournie quant à la sécurité de la chaîne résultante en HTML. Donc, ne marquez JAMAIS en toute sécurité le résultat d'un appel
strip_tags
sans d'abord l'échapper, par exemple avecescape()
.
Suivez leurs conseils!
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 que le <img...>
est une balise. Il semble cassé, donc HTMLParser ne s'en débarrasse pas. Il ne prend que le <!-- comments -->
, vous laissant avec
<img src=x onerror=alert(1);//>
Ce problème a été révélé au projet Django en mars 2014. Leur ancien strip_tags
était essentiellement le même que la réponse principale à cette question. Leur nouvelle version l'exécute en boucle jusqu'à ce qu'il ne soit plus exécuté 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 du 19 mars 2015: Il existait un bogue dans les versions de Django antérieures à 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 corrigée est reproduite ci-dessus. Plus de détails ici .
Mon exemple de code ne gère pas les entités HTML, contrairement aux versions packagées de Django et MarkupSafe.
Mon exemple de code est tiré de l'excellente bibliothèque/ MarkupSafe pour la prévention des scripts entre sites. C'est pratique et rapide (avec C accélérations à sa version native de Python). Il est inclus dans Google App Engine , et utilisé par Jinja2 (2.7 et plus) , Mako, Pylons, etc. Cela 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 depuis ce fichier .
Si vous devez effacer presque toutes les balises, la bibliothèque Bleach est bonne. Vous pouvez le faire appliquer des règles telles que "mes utilisateurs peuvent mettre des choses en italique, mais ils ne peuvent pas créer d'iframes".
Comprenez les propriétés de votre décapant de balises! Exécutez des tests fuzz dessus! Voici le code J'avais l'habitude de faire des recherches pour cette réponse.
note timorée _ La question elle-même concerne l'impression sur la console, mais il s'agit du premier résultat de Google pour "python strip html from string", c'est pourquoi cette réponse concerne 99% du Web.
Il y a un moyen simple de faire ça:
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 le cours (sur le débogage intelligent avec python), je vous donne un lien: http://www.udacity.com/overview/Course/cs259/CourseRev/1 . C'est gratuit!
De rien! :)
Si vous devez conserver les entités HTML (c'est-à-dire &
), 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()
Si vous voulez 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 le message "RuntimeError: profondeur de récursion maximale dépassée" n'a pas été utilisé, ce qui n'est pas arrivé avec le bloc de code ci-dessus.
Une solution basée sur lxml.html - (lxml est une bibliothèque native et est 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 aussi http://lxml.de/lxmlhtml.html#cleaning-up-html pour savoir ce que fait exactement le fichier lxml.cleaner.
Si vous avez besoin de plus de contrôle sur ce qui est exactement assaini avant de convertir en texte, vous pouvez utiliser le lxml Cleaner explicitement en passant les options de votre choix 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)
Le paquet Beautiful Soup fait cela immédiatement pour vous.
from bs4 import BeautifulSoup
soup = BeautifulSoup(html)
text = soup.get_text()
print(text)
Vous pouvez utiliser un analyseur HTML différent ( comme lxml ou Beautiful Soup ) - offrant des fonctions permettant d’extraire uniquement du texte. Ou, vous pouvez exécuter une expression régulière sur votre ligne qui supprime les balises. Voir http://www.amk.ca/python/howto/regex/ pour plus d'informations.
Pour un projet, j'avais besoin de supprimer HTML, mais aussi css et js. J'ai donc fait une variante de la réponse d'Eloff:
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()
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()
Les solutions avec HTML-Parser sont toutes cassables si elles ne fonctionnent 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
Ceci est une solution rapide et peut être encore plus optimisé, mais cela fonctionnera bien. Ce code remplacera toutes les balises non vides par "" et supprimera toutes les balises HTML d'un texte d'entrée donné. Vous pourrez l'exécuter à l'aide de
#!/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("<","<")
lineTemp = lineTemp.replace(">",">")
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"
J'ai utilisé la réponse d'Eloff avec succès pour Python 3.1 [merci beaucoup!].
Je suis passé à Python 3.2.3 et j'ai rencontré des erreurs.
La solution, fournie ici grâce au répondeur Thomas K, consiste à insérer super().__init__()
dans le code suivant:
def __init__(self):
self.reset()
self.fed = []
... afin de le faire ressembler à 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 fourni ci-dessus!
Voici une solution similaire à la réponse actuellement acceptée ( https://stackoverflow.com/a/925630/95989 ), sauf qu'elle utilise directement la classe interne HTMLParser
(c'est-à-dire sans sous-classement), ce qui la rend nettement plus concise. :
def strip_html (text): parties = [] analyseur = HTMLParser () parser.handle_data = parts.append parser.feed (text) retour '' .join (parts)
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
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
Pas sûr que ce soit parfait, mais résolu mon cas d'utilisation et semble simple.
Code simple !. Cela supprimera tout type 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(' ', ' ')
return s
Mais le résultat ne sera pas complet si le texte contient des symboles <> .
J'analyse les readmes 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 ''
Et alors
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 markdown et html correctement.
hext
est un package qui peut entre autres strip HTML. C'est une alternative à beautifulsoup
. Ce qui suit a été testé avec hext==0.2.3
.
Enregistrez ceci dans un module d’utilitaire, par exemple. util/hext.py
:
import hext
_HTML_TEXT_RULE = hext.Rule('<html @text:text />')
def html_to_text(text: str) -> str:
# Ref: https://stackoverflow.com/a/56894409/
return _HTML_TEXT_RULE.extract(hext.Html(f'<html>{text}</html>'))[0]['text']
Exemples d'utilisation:
>>> from .util.hext import html_to_text
>>> html_to_text('<b>Hello world!</b>')
'Hello world!'
>>> html_to_text('<a href="google.com">some text</a>')
'some text'
>>> html_to_text('<span class="small-caps">l</span>-arginine minimizes immunosuppression and prothrombin time and enhances the genotoxicity of 5-fluorouracil in rats')
'l-arginine minimizes immunosuppression and prothrombin time and enhances the genotoxicity of 5-fluorouracil in rats'
>>> html_to_text('Attenuation of diabetic nephropathy by dietary fenugreek (<em>Trigonella foenum-graecum</em>) seeds and onion (<em>Allium cepa</em>) <em>via</em> suppression of glucose transporters and renin-angiotensin system')
'Attenuation of diabetic nephropathy by dietary fenugreek (Trigonella foenum-graecum) seeds and onion (Allium cepa) via suppression of glucose transporters and renin-angiotensin system'
Exemples d'utilisation avec du HTML mal formé:
>>> html_to_text('<b>Hello <i>world!')
'Hello world!'
>>> html_to_text('<a href="google.com">some <faketag>text')
'some text'
En utilisant BeautifulSoup, html2text ou le code de @Eloff, il reste la plupart du temps des éléments html, du code javascript ...
Vous pouvez donc utiliser une combinaison de ces bibliothèques et supprimer le formatage à la baisse (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 cela peut être amélioré, bien sûr ...