Extraer texto de un archivo HTML usando Python

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 la fuente de JavaScript. Además, no interpretó las entidades HTML. Por ejemplo, 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 sin formato; produce un descuento que luego debería ser convertido en texto plano. Viene sin ejemplos o documentación, pero el código se ve limpio.


Preguntas relacionadas:

  • Filtra las tags HTML y resuelve las entidades en Python
  • Convierta entidades XML / HTML en cadenas Unicode en Python

html2text es un progtwig de Python que hace un buen trabajo en esto.

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 

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 encontrar 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) 

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'''   Project: DeHTML
Description:
This small script is intended to allow conversion from HTML markup to plain text. ''' print(dehtml(text)) if __name__ == '__main__': main()

Aquí hay una versión de la respuesta de xperroni que es un poco más completa. Omite las secciones de estilo y escritura 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  tags, converting newlines to 
tags and converting confusing chars into html entities. """ def f(mo): t = mo.group() if len(t) == 1: return {'&':'&', "'":''', '"':'"', '<':'<', '>':'>'}.get(t) return '
https://stackoverflow.com/questions/328356/extracting-text-from-html-file-using-python/%s' % (t, t) return re.sub(r'https?://[^] ()"\';]+|[&\'"<>]', f, text)

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

Hay una biblioteca de patrones para la minería de datos.

http://www.clips.ua.ac.be/pages/pattern-web

Incluso puedes decidir qué tags guardar:

 s = URL('http://www.clips.ua.ac.be').download() s = plaintext(s, keep={'h1':[], 'h2':[], 'strong':[], 'a':['href']}) print s 

Sé que ya hay muchas respuestas, pero la solución más elegante y pythonica que he encontrado se describe, en parte, aquí .

 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) 

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

Esta 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() 

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

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() 

instale html2text usando

pip instalar html2text

entonces,

 >>> import html2text >>> >>> h = html2text.HTML2Text() >>> # Ignore converting links from HTML >>> h.ignore_links = True >>> print h.handle("

Hello, world!") Hello, world!

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 encoding html y Unicode. Este es el código que utilizo para convertir HTML en 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: ''), ] 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 "" 

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 incluida en el artículo Meta descripción Etiquetas meta

Más: https://pypi.python.org/pypi/goose-extractor/

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 realizar conversiones de todo tipo de formatos …

¿Alguien ha probado bleach.clean(html,tags=[],strip=True) con bleach ? esta funcionando para mi

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 

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 servidor , es trivial de ejecutar / implementar en un contenedor Docker, y desde allí se puede acceder a través de enlaces Python .

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

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 tags

y reemplaza las tags 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 

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 quizás 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 (eg, 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.

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) 

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.

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.

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 progtwig de producción mayor, abrir el HTML en el escritor y guardar la página como texto parece resolver los problemas que se tratan aquí.

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 

Si bien muchas personas mencionaron el uso de expresiones regulares para quitar las tags html, hay muchas desventajas.

por ejemplo:

 

hello world

I love you

Debería ser analizado a:

 Hello world I love you 

Aquí hay un fragmento que se me ocurrió, puede personalizarlo según sus necesidades específicas, y funciona a la perfección.

 import re import html def html2text(htm): ret = html.unescape(htm) ret = ret.translate({ 8209: ord('-'), 8220: ord('"'), 8221: ord('"'), 160: ord(' '), }) ret = re.sub(r"\s", " ", ret, flags = re.MULTILINE) ret = re.sub("
|
|||", "\n", ret, flags = re.IGNORECASE) ret = re.sub('<.*?>', ' ', ret, flags=re.DOTALL) ret = re.sub(r" +", " ", ret) return ret

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