scraping - python web
Eliminar HTML de cadenas en Python (21)
¡Version corta!
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)
Fuente Regex: MarkupSafe . Su versión también maneja entidades HTML, mientras que esta rápida no lo hace.
¿Por qué no puedo simplemente quitar las etiquetas y dejarlas?
Una cosa es evitar que la gente <i>italicizing</i>
cosas, sin dejar que flote por ahí. Pero es otra cosa tomar una entrada arbitraria y hacerla completamente inofensiva. La mayoría de las técnicas en esta página dejarán intactos los comentarios no cerrados ( <!--
) y los corchetes angulares que no forman parte de las etiquetas ( blah <<<><blah
). La versión HTMLParser puede incluso dejar etiquetas completas, si están dentro de un comentario no cerrado.
¿Qué pasa si su plantilla es {{ firstname }} {{ lastname }}
? Todos los separadores de etiquetas de esta página dejarán pasar firstname = ''<a''
y lastname = ''href="http://evil.com/">''
(excepto @Medeiros!), porque no son etiquetas completas en su propia Eliminar las etiquetas HTML normales no es suficiente.
Las strip_tags
de Django, una versión mejorada (ver el siguiente encabezado) de la respuesta principal a esta pregunta, da la siguiente advertencia:
Absolutamente NO hay garantía de que la cadena resultante sea segura para HTML. Por lo tanto, NUNCA marque seguro el resultado de una llamada
strip_tags
sin escapar primero, por ejemplo, conescape()
.
¡Sigue sus consejos!
Para eliminar etiquetas con HTMLParser, debes ejecutarlo varias veces.
Es fácil eludir la respuesta principal a esta pregunta.
Mira esta cadena ( fuente y discusión ):
<img<!-- --> src=x onerror=alert(1);//><!-- -->
La primera vez que HTMLParser lo ve, no puede decir que <img...>
es una etiqueta. Parece roto, por lo que HTMLParser no se deshace de él. Solo saca los <!-- comments -->
, dejándote con
<img src=x onerror=alert(1);//>
Este problema se reveló al proyecto Django en marzo de 2014. Sus antiguos strip_tags
eran esencialmente los mismos que la respuesta principal a esta pregunta. Su nueva versión básicamente lo ejecuta en un bucle hasta que ejecutarlo de nuevo no cambia la cadena:
# _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
Por supuesto, nada de esto es un problema si siempre se escapa del resultado de strip_tags()
.
Actualización del 19 de marzo de 2015 : hubo un error en las versiones de Django anteriores a 1.4.20, 1.6.11, 1.7.7 y 1.8c1. Estas versiones podrían ingresar un bucle infinito en la función strip_tags (). La versión fija se reproduce arriba. Más detalles aquí .
Buenas cosas para copiar o usar
El código de mi ejemplo no controla las entidades HTML: las versiones empaquetadas de Django y MarkupSafe sí lo hacen.
Mi código de ejemplo se MarkupSafe de la excelente biblioteca MarkupSafe para la prevención de secuencias de comandos entre sitios. Es conveniente y rápido (con aceleraciones de C a su versión nativa de Python). Se incluye en Google App Engine y es utilizado por Jinja2 (2.7 y versiones superiores) , Mako, Pylons y más. Funciona fácilmente con las plantillas Django de Django 1.7.
Las strip_tags de Django y otras utilidades html de una versión reciente son buenas, pero las encuentro menos convenientes que MarkupSafe. Son bastante independientes, podría copiar lo que necesita de este archivo .
Si necesita eliminar casi todas las etiquetas, la biblioteca Bleach es buena. Puede hacer que aplique reglas como "mis usuarios pueden poner en cursiva las cosas, pero no pueden hacer iframes".
¡Comprende las propiedades de tu tag stripper! Ejecutar pruebas fuzz en él! Aquí está el código que solía hacer la investigación para esta respuesta.
nota tímida : la pregunta en sí misma es sobre la impresión en la consola, pero este es el resultado principal de Google para "python strip html from string", por lo que esta es la respuesta del 99% sobre la web.
from mechanize import Browser
br = Browser()
br.open(''http://somewebpage'')
html = br.response().readlines()
for line in html:
print line
Al imprimir una línea en un archivo HTML, estoy tratando de encontrar una manera de mostrar solo los contenidos de cada elemento HTML y no el formato en sí. Si encuentra ''<a href="whatever.com">some text</a>''
, solo imprimirá ''algo de texto'', ''<b>hello</b>''
imprime ''hola'', etc. ¿Cómo Uno va sobre hacer esto?
¿Por qué todos ustedes lo hacen de la manera difícil? Puede utilizar la función 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)
Aquí hay una solución similar a la respuesta actualmente aceptada ( https://.com/a/925630/95989 ), excepto que usa la clase interna HTMLParser
directamente (es decir, sin subclasificación), por lo que es significativamente más terso:
def strip_html(text): parts = [] parser = HTMLParser() parser.handle_data = parts.append parser.feed(text) return ''''.join(parts)
El paquete Beautiful Soup hace esto inmediatamente por ti.
from bs4 import BeautifulSoup
soup = BeautifulSoup(html)
text = soup.get_text()
print(text)
Esta es una solución rápida y puede optimizarse aún más, pero funcionará bien. Este código reemplazará todas las etiquetas que no estén vacías con "" y elimina todas las etiquetas html que forman un texto de entrada determinado. Puede ejecutarlo usando la entrada de entrada ./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("<","<")
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"
Este método funciona perfectamente para mí y no requiere instalaciones adicionales:
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(" ", " ") ## Get rid of the remnants of certain formatting(subscript,superscript,etc).
Estoy analizando las lecturas de Github y encuentro que lo siguiente realmente funciona 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 ''''
Y entonces
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))
Elimina todas las rebajas y html correctamente.
Hay una manera simple de esto:
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
La idea se explica aquí: http://youtu.be/2tu9LTDujbw
Puedes verlo trabajando aquí: http://youtu.be/HPkNPcYed9M?t=35s
PD: si estás interesado en la clase (sobre la depuración inteligente con python) te doy un enlace: http://www.udacity.com/overview/Course/cs259/CourseRev/1 . ¡Es gratis!
¡De nada! :)
He utilizado con éxito la respuesta de Eloff para Python 3.1 [muchas gracias!].
Actualicé a Python 3.2.3 y me encontré con errores.
La solución, proporcionada here gracias al respondedor Thomas K, es insertar super().__init__()
en el siguiente código:
def __init__(self):
self.reset()
self.fed = []
... para que se vea así:
def __init__(self):
super().__init__()
self.reset()
self.fed = []
... y funcionará para Python 3.2.3.
Nuevamente, gracias a Thomas K por la corrección y por el código original de Eloff que se proporciona arriba.
Las soluciones con HTML-Parser se pueden romper, si se ejecutan solo una vez:
html_to_text(''<<b>script>alert("hacked")<</b>/script>
resultados en:
<script>alert("hacked")</script>
Lo que pretendes prevenir. Si usa un analizador HTML, cuente las etiquetas hasta que se reemplacen los cero:
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
Necesitaba una forma de quitar las etiquetas y decodificar las entidades HTML a texto sin formato. La siguiente solución se basa en la respuesta de Eloff (que no pude usar porque elimina las entidades).
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()
Una prueba rápida:
html = u''<a href="#">Demo <em>(¬ /u0394ημώ)</em></a>''
print repr(html_to_text(html))
Resultado:
u''Demo (/xac /u0394/u03b7/u03bc/u03ce)''
Manejo de errores:
- La estructura HTML no HTMLParseError puede provocar un HTMLParseError .
- Las entidades HTML con nombre no válidas (como
&#apos;
que son válidas en XML y XHTML, pero no en HTML simple) causarán una excepciónValueError
. - Las entidades HTML numéricas que especifican puntos de código fuera del rango Unicode aceptable por Python (como, en algunos sistemas, caracteres fuera del plano multilingüe básico ) causarán una excepción
ValueError
.
Nota de seguridad: no confunda la eliminación de HTML (conversión de HTML en texto sin formato) con la limpieza de HTML (conversión de texto sin formato en HTML). Esta respuesta eliminará HTML y decodificará las entidades en texto sin formato, lo que no hace que el resultado sea seguro para usar en un contexto HTML.
Ejemplo: <script>alert("Hello");</script>
se convertirá en <script>alert("Hello");</script>
, que es 100% correcto, pero obviamente no es suficiente si el texto sin formato resultante se inserta como está en una página HTML.
La regla no es difícil: cada vez que inserte una cadena de texto sin formato en la salida HTML, siempre debe escapar de HTML (usando cgi.escape(s, True)
), incluso si "sabe" que no contiene HTML (por ejemplo, porque ha eliminado el contenido HTML).
(Sin embargo, el OP preguntó sobre la impresión del resultado en la consola, en cuyo caso no es necesario que se escape HTML).
Versión de Python 3.4+: (con 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()
Tenga en cuenta que HTMLParser ha mejorado en Python 3 (lo que significa menos código y mejor manejo de errores).
No he pensado mucho en los casos que fallará, pero puedes hacer una expresión regular simple:
re.sub(''<[^<]+?>'', '''', text)
Para aquellos que no entienden las expresiones regulares, esto busca una cadena <...>
, donde el contenido interno está formado por uno o más caracteres ( +
) que no es un <
. El ?
significa que coincidirá con la cadena más pequeña que pueda encontrar. Por ejemplo, dado <p>Hello</p>
, coincidirá con <''p>
y </p>
separado con el ?
. Sin él, coincidirá con toda la cadena <..Hello..>
.
Si no aparece la etiqueta <
en html (p. Ej., 2 < 3
), debe escribirse como una secuencia de escape &...
todos modos, así que la ^<
puede ser innecesaria.
Para un proyecto, necesitaba el código HTML, pero también css y js. Por lo tanto, hice una variación de la respuesta de 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()
Puede usar un analizador HTML diferente ( como lxml o Beautiful Soup ), uno que ofrece funciones para extraer solo texto. O bien, puede ejecutar una expresión regular en su cadena de línea que elimina las etiquetas. Vea http://www.amk.ca/python/howto/regex/ para más información.
Puedes escribir tu propia función:
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
Si desea eliminar todas las etiquetas HTML, la forma más sencilla que encontré es usar 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))
Probé el código de la respuesta aceptada pero obtuve "RuntimeError: se excedió la profundidad máxima de recursión", lo que no sucedió con el bloque de código anterior.
Si necesita conservar las entidades HTML (es decir, &
), agregué el método "handle_entityref" a la respuesta de 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()
Siempre usé esta función para eliminar las etiquetas HTML, ya que solo requiere el stdlib de Python:
En 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()
Para 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()
Nota : esto funciona solo para 3.1. Para 3.2 o superior, debe llamar a la función init de la clase padre. Ver Usar HTMLParser en Python 3.2
Una adaptación de python 3 de la respuesta de søren-løvborg.
from html.parser import HTMLParser
from html.entities import html5
class HTMLTextExtractor(HTMLParser):
""" Adaption of http://.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()
Una solución basada en lxml.html (lxml es una biblioteca nativa y, por lo tanto, mucho más rápida que cualquier solución Python pura).
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
También vea http://lxml.de/lxmlhtml.html#cleaning-up-html para saber exactamente qué hace lxml.cleaner.
Si necesita más control sobre qué es exactamente el desinfectado antes de convertirlo en texto, puede utilizar el Limpiador lxml explícitamente al pasar las opciones que desea en el constructor, por ejemplo:
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)
Usando BeautifulSoup, html2text o el código de @Eloff, la mayoría de las veces, sigue siendo algunos elementos html, código javascript ...
Por lo tanto, puede usar una combinación de estas bibliotecas y eliminar el formato de reducción de marca (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
Funciona bien para mí, pero puede mejorarse, por supuesto ...