python - beautifulsoup example
Extraer texto de un archivo HTML usando Python (29)
Me gustaría extraer el texto de un archivo HTML usando Python. Quiero esencialmente la misma salida que obtendría si copiara el texto desde un navegador y lo pegara en el bloc de notas.
Me gustaría algo más robusto que usar expresiones regulares que puedan fallar en HTML mal formado. He visto a muchas personas recomendar Beautiful Soup, pero he tenido algunos problemas al usarlo. Por un lado, recogió texto no deseado, como fuente de JavaScript. Además, no interpretó las entidades HTML. Por ejemplo, yo esperaría que & # 39; en la fuente HTML para convertirla en un apóstrofe en el texto, como si hubiera pegado el contenido del navegador en el bloc de notas.
Actualizar html2text
parece prometedor. Maneja las entidades HTML correctamente e ignora JavaScript. Sin embargo, no produce exactamente texto plano; produce una rebaja que luego tendría que convertirse en texto plano. Viene sin ejemplos o documentación, pero el código se ve limpio.
Preguntas relacionadas:
¿Alguien ha probado bleach.clean(html,tags=[],strip=True)
con bleach ? esta funcionando para mi
Aquí está el código que utilizo de forma regular.
from bs4 import BeautifulSoup
import urllib.request
def processText(webpage):
# EMPTY LIST TO STORE PROCESSED TEXT
proc_text = []
try:
news_open = urllib.request.urlopen(webpage.group())
news_soup = BeautifulSoup(news_open, "lxml")
news_para = news_soup.find_all("p", text = True)
for item in news_para:
# SPLIT WORDS, JOIN WORDS TO REMOVE EXTRA SPACES
para_text = ('' '').join((item.text).split())
# COMBINE LINES/PARAGRAPHS INTO A LIST
proc_text.append(para_text)
except urllib.error.HTTPError:
pass
return proc_text
Espero que eso ayude.
Aquí hay una versión de la respuesta de xperroni que es un poco más completa. Omite las secciones de estilo y script y traduce las charrefs (por ejemplo, & # 39;) y las entidades HTML (por ejemplo, & amp;).
También incluye un convertidor inverso trivial de texto plano a html.
"""
HTML <-> text conversions.
"""
from HTMLParser import HTMLParser, HTMLParseError
from htmlentitydefs import name2codepoint
import re
class _HTMLToText(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self._buf = []
self.hide_output = False
def handle_starttag(self, tag, attrs):
if tag in (''p'', ''br'') and not self.hide_output:
self._buf.append(''/n'')
elif tag in (''script'', ''style''):
self.hide_output = True
def handle_startendtag(self, tag, attrs):
if tag == ''br'':
self._buf.append(''/n'')
def handle_endtag(self, tag):
if tag == ''p'':
self._buf.append(''/n'')
elif tag in (''script'', ''style''):
self.hide_output = False
def handle_data(self, text):
if text and not self.hide_output:
self._buf.append(re.sub(r''/s+'', '' '', text))
def handle_entityref(self, name):
if name in name2codepoint and not self.hide_output:
c = unichr(name2codepoint[name])
self._buf.append(c)
def handle_charref(self, name):
if not self.hide_output:
n = int(name[1:], 16) if name.startswith(''x'') else int(name)
self._buf.append(unichr(n))
def get_text(self):
return re.sub(r'' +'', '' '', ''''.join(self._buf))
def html_to_text(html):
"""
Given a piece of HTML, return the plain text it contains.
This handles entities and char refs, but not javascript and stylesheets.
"""
parser = _HTMLToText()
try:
parser.feed(html)
parser.close()
except HTMLParseError:
pass
return parser.get_text()
def text_to_html(text):
"""
Convert the given text to html, wrapping what looks like URLs with <a> tags,
converting newlines to <br> tags and converting confusing chars into html
entities.
"""
def f(mo):
t = mo.group()
if len(t) == 1:
return {''&'':''&'', "''":''''', ''"'':''"'', ''<'':''<'', ''>'':''>''}.get(t)
return ''<a href="%s">%s</a>'' % (t, t)
return re.sub(r''https?://[^] ()"/';]+|[&/'"<>]'', f, text)
El comentario del escritor de LibreOffice tiene mérito ya que la aplicación puede emplear macros de Python. Parece ofrecer múltiples beneficios tanto para responder esta pregunta como para promover la base macro de LibreOffice. Si esta resolución es una implementación única, en lugar de ser utilizada como parte de un programa de producción mayor, abrir el HTML en el escritor y guardar la página como texto parece resolver los problemas que se analizan aquí.
En Python 3.x puedes hacerlo de una manera muy sencilla importando paquetes ''imaplib'' y ''correo electrónico''. Aunque esta es una publicación más antigua, pero tal vez mi respuesta pueda ayudar a los recién llegados en esta publicación.
status, data = self.imap.fetch(num, ''(RFC822)'')
email_msg = email.message_from_bytes(data[0][1])
#email.message_from_string(data[0][1])
#If message is multi part we only want the text version of the body, this walks the message and gets the body.
if email_msg.is_multipart():
for part in email_msg.walk():
if part.get_content_type() == "text/plain":
body = part.get_payload(decode=True) #to control automatic email-style MIME decoding (e.g., Base64, uuencode, quoted-printable)
body = body.decode()
elif part.get_content_type() == "text/html":
continue
Ahora puede imprimir la variable del cuerpo y estará en formato de texto simple :) Si es lo suficientemente bueno para usted, sería bueno seleccionarlo como respuesta aceptada.
En lugar del módulo HTMLParser, echa un vistazo a htmllib. Tiene una interfaz similar, pero hace más del trabajo por ti. (Es bastante antiguo, por lo que no es de mucha ayuda para deshacerse de javascript y css. Podría crear una clase derivada, pero agregue métodos con nombres como start_script y end_style (consulte la documentación de Python para obtener más información), pero es difícil para hacer esto de manera confiable para html malformado.) De todos modos, aquí hay algo simple que imprime el texto sin formato en la consola
from htmllib import HTMLParser, HTMLParseError
from formatter import AbstractFormatter, DumbWriter
p = HTMLParser(AbstractFormatter(DumbWriter()))
try: p.feed(''hello<br>there''); p.close() #calling close is not usually needed, but let''s play it safe
except HTMLParseError: print '':('' #the html is badly malformed (or you found a bug)
Esto no es exactamente una solución de Python, pero convertirá el texto que Javascript generaría en texto, lo que creo que es importante (por ejemplo, google.com). Los enlaces del navegador (no Lynx) tienen un motor de Javascript y convertirán la fuente al texto con la opción -dump.
Así que podrías hacer algo como:
fname = os.tmpnam()
fname.write(html_source)
proc = subprocess.Popen([''links'', ''-dump'', fname],
stdout=subprocess.PIPE,
stderr=open(''/dev/null'',''w''))
text = proc.stdout.read()
Hay una biblioteca de patrones para la minería de datos.
http://www.clips.ua.ac.be/pages/pattern-web
Incluso puedes decidir qué etiquetas guardar:
s = URL(''http://www.clips.ua.ac.be'').download()
s = plaintext(s, keep={''h1'':[], ''h2'':[], ''strong'':[], ''a'':[''href'']})
print s
He tenido buenos resultados con Apache Tika . Su propósito es la extracción de metadatos y texto del contenido, por lo tanto, el analizador subyacente se sintoniza en consecuencia fuera de la caja.
Tika se puede ejecutar como un server , es trivial de ejecutar / implementar en un contenedor Docker, y desde allí se puede acceder a través de enlaces Python .
Hermosa sopa convierte las entidades html. Probablemente sea su mejor apuesta considerando que HTML a menudo está lleno de errores y está lleno de problemas de codificación html y Unicode. Este es el código que utilizo para convertir html a texto sin formato:
import BeautifulSoup
def getsoup(data, to_unicode=False):
data = data.replace(" ", " ")
# Fixes for bad markup I''ve seen in the wild. Remove if not applicable.
masssage_bad_comments = [
(re.compile(''<!-([^-])''), lambda match: ''<!--'' + match.group(1)),
(re.compile(''<!WWWAnswer T[=/w/d/s]*>''), lambda match: ''<!--'' + match.group(0) + ''-->''),
]
myNewMassage = copy.copy(BeautifulSoup.BeautifulSoup.MARKUP_MASSAGE)
myNewMassage.extend(masssage_bad_comments)
return BeautifulSoup.BeautifulSoup(data, markupMassage=myNewMassage,
convertEntities=BeautifulSoup.BeautifulSoup.ALL_ENTITIES
if to_unicode else None)
remove_html = lambda c: getsoup(c, to_unicode=True).getText(separator=u'' '') if c else ""
La mejor pieza de código que encontré para extraer texto sin obtener javascript o cosas no deseadas:
import urllib
from bs4 import BeautifulSoup
url = "http://news.bbc.co.uk/2/hi/health/2284783.stm"
html = urllib.urlopen(url).read()
soup = BeautifulSoup(html)
# kill all script and style elements
for script in soup(["script", "style"]):
script.extract() # rip it out
# get text
text = soup.get_text()
# break into lines and remove leading and trailing space on each
lines = (line.strip() for line in text.splitlines())
# break multi-headlines into a line each
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
# drop blank lines
text = ''/n''.join(chunk for chunk in chunks if chunk)
print(text)
Solo tienes que instalar BeautifulSoup antes:
pip install beautifulsoup4
La respuesta de @ PeYoTIL usando BeautifulSoup y eliminando el estilo y el contenido del script no me funcionó. Lo intenté usando decompose
lugar de extract
pero todavía no funcionó. Así que creé el mío que también formatea el texto usando las etiquetas <p>
y reemplaza las etiquetas <a>
con el enlace href. También hace frente a los enlaces dentro del texto. Disponible en esta esencia con un documento de prueba integrado.
from bs4 import BeautifulSoup, NavigableString
def html_to_text(html):
"Creates a formatted text email message as a string from a rendered html template (page)"
soup = BeautifulSoup(html, ''html.parser'')
# Ignore anything in head
body, text = soup.body, []
for element in body.descendants:
# We use type and not isinstance since comments, cdata, etc are subclasses that we don''t want
if type(element) == NavigableString:
# We use the assumption that other tags can''t be inside a script or style
if element.parent.name in (''script'', ''style''):
continue
# remove any multiple and leading/trailing whitespace
string = '' ''.join(element.string.split())
if string:
if element.parent.name == ''a'':
a_tag = element.parent
# replace link text with the link
string = a_tag[''href'']
# concatenate with any non-empty immediately previous string
if ( type(a_tag.previous_sibling) == NavigableString and
a_tag.previous_sibling.string.strip() ):
text[-1] = text[-1] + '' '' + string
continue
elif element.previous_sibling and element.previous_sibling.name == ''a'':
text[-1] = text[-1] + '' '' + string
continue
elif element.parent.name == ''p'':
# Add extra paragraph formatting newline
string = ''/n'' + string
text += [string]
doc = ''/n''.join(text)
return doc
Lo estoy logrando algo así.
>>> import requests
>>> url = "http://news.bbc.co.uk/2/hi/health/2284783.stm"
>>> res = requests.get(url)
>>> text = res.text
Lo mejor que he trabajado para mí son las inscripciones.
https://github.com/weblyzard/inscriptis
import urllib.request
from inscriptis import get_text
url = "http://www.informationscience.ch"
html = urllib.request.urlopen(url).read().decode(''utf-8'')
text = get_text(html)
print(text)
Los resultados son realmente buenos.
Me encontré enfrentando el mismo problema hoy. Escribí un analizador HTML muy simple para eliminar el contenido entrante de todas las marcas, devolviendo el texto restante con un mínimo de formato.
from HTMLParser import HTMLParser
from re import sub
from sys import stderr
from traceback import print_exc
class _DeHTMLParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.__text = []
def handle_data(self, data):
text = data.strip()
if len(text) > 0:
text = sub(''[ /t/r/n]+'', '' '', text)
self.__text.append(text + '' '')
def handle_starttag(self, tag, attrs):
if tag == ''p'':
self.__text.append(''/n/n'')
elif tag == ''br'':
self.__text.append(''/n'')
def handle_startendtag(self, tag, attrs):
if tag == ''br'':
self.__text.append(''/n/n'')
def text(self):
return ''''.join(self.__text).strip()
def dehtml(text):
try:
parser = _DeHTMLParser()
parser.feed(text)
parser.close()
return parser.text()
except:
print_exc(file=stderr)
return text
def main():
text = r''''''
<html>
<body>
<b>Project:</b> DeHTML<br>
<b>Description</b>:<br>
This small script is intended to allow conversion from HTML markup to
plain text.
</body>
</html>
''''''
print(dehtml(text))
if __name__ == ''__main__'':
main()
Otra opción es ejecutar el html a través de un navegador web basado en texto y volcarlo. Por ejemplo (usando Lynx):
lynx -dump html_to_convert.html > converted_html.txt
Esto se puede hacer dentro de un script de python de la siguiente manera:
import subprocess
with open(''converted_html.txt'', ''w'') as outputFile:
subprocess.call([''lynx'', ''-dump'', ''html_to_convert.html''], stdout=testFile)
No le dará exactamente el texto del archivo HTML, pero dependiendo de su caso de uso puede ser preferible a la salida de html2text.
Otra solución que no es python: Libre Office:
soffice --headless --invisible --convert-to txt input1.html
La razón por la que prefiero esta opción por sobre otras alternativas es que cada párrafo HTML se convierte en una sola línea de texto (sin saltos de línea), que es lo que estaba buscando. Otros métodos requieren post-procesamiento. Lynx produce una buena salida, pero no es exactamente lo que estaba buscando. Además, Libre Office se puede utilizar para convertir desde todo tipo de formatos ...
Perl manera (lo siento mamá, nunca lo haré en producción).
import re
def html2text(html):
res = re.sub(''<.*?>'', '' '', html, flags=re.DOTALL | re.MULTILINE)
res = re.sub(''/n+'', ''/n'', res)
res = re.sub(''/r+'', '''', res)
res = re.sub(''[/t ]+'', '' '', res)
res = re.sub(''/t+'', ''/t'', res)
res = re.sub(''(/n )+'', ''/n '', res)
return res
PyParsing hace un gran trabajo. La wiki de PyParsing fue eliminada, así que aquí hay otra ubicación donde hay ejemplos del uso de PyParsing ( enlace de ejemplo ). Una de las razones para invertir un poco de tiempo en la reproducción es que también ha escrito un breve manual de O''Reilly Short Cut muy bien organizado que también es económico.
Habiendo dicho eso, uso BeautifulSoup mucho y no es tan difícil lidiar con los problemas de las entidades, puede convertirlos antes de ejecutar BeautifulSoup.
Buena suerte
Recomiendo un paquete de Python llamado goose-extractor Goose intentará extraer la siguiente información:
Texto principal de un artículo Imagen principal del artículo Cualquier película de Youtube / Vimeo incrustada en el artículo Meta descripción Etiquetas meta
Sé que ya hay muchas respuestas aquí, pero creo que newspaper3k también merece una mención. Hace poco tuve que completar una tarea similar para extraer el texto de los artículos en la web y esta biblioteca ha hecho un excelente trabajo para lograr esto hasta ahora en mis pruebas. Ignora el texto que se encuentra en los elementos del menú y las barras laterales, así como cualquier JavaScript que aparezca en la página cuando el OP lo solicite.
from newspaper import Article
article = Article(url)
article.download()
article.parse()
article.text
Si ya tiene los archivos HTML descargados, puede hacer algo como esto:
article = Article('''')
article.set_html(html)
article.parse()
article.text
Incluso tiene algunas características de PNL para resumir los temas de los artículos:
article.nlp()
article.summary
Sé que ya hay muchas respuestas, pero la solución más elegante y pitónica que he encontrado se describe, en parte, here .
from bs4 import BeautifulSoup
text = ''''.join(BeautifulSoup(some_html_string, "html.parser").findAll(text=True))
Actualizar
Basado en el comentario de Fraser, aquí hay una solución más elegante:
from bs4 import BeautifulSoup
clean_text = ''''.join(BeautifulSoup(some_html_string, "html.parser").stripped_strings)
Si necesita más velocidad y menos precisión, entonces podría usar lxml en bruto.
import lxml.html as lh
from lxml.html.clean import clean_html
def lxml_to_text(html):
doc = lh.fromstring(html)
doc = clean_html(doc)
return doc.text_content()
También puede utilizar el método html2text en la biblioteca de stripogram.
from stripogram import html2text
text = html2text(your_html_string)
Para instalar stripogram ejecuta sudo easy_install stripogram
de una manera simple
import re
html_text = open(''html_file.html'').read()
text_filtered = re.sub(r''<(.*?)>'', '''', html_text)
este código encuentra todas las partes del texto html que comenzaron con ''<'' y terminaron con ''>'' y reemplazan todas las encontradas por una cadena vacía
instale html2text usando
pip instalar html2text
entonces,
>>> import html2text
>>>
>>> h = html2text.HTML2Text()
>>> # Ignore converting links from HTML
>>> h.ignore_links = True
>>> print h.handle("<p>Hello, <a href=''http://earth.google.com/''>world</a>!")
Hello, world!
solo puedes extraer texto de HTML con BeautifulSoup
url = "https://www.geeksforgeeks.org/extracting-email-addresses-using-regular-expressions-python/"
con = urlopen(url).read()
soup = BeautifulSoup(con,''html.parser'')
texts = soup.get_text()
print(texts)
html2text es un programa de Python que hace un buen trabajo en esto.
NOTA: NTLK ya no admite la función clean_html
Respuesta original a continuación, y una alternativa en las secciones de comentarios.
Utilizar NLTK
Perdí mis 4-5 horas arreglando los problemas con html2text. Por suerte pude encontrarme con NLTK.
Funciona mágicamente.
import nltk
from urllib import urlopen
url = "http://news.bbc.co.uk/2/hi/health/2284783.stm"
html = urlopen(url).read()
raw = nltk.clean_html(html)
print(raw)