Estrazione di testo da file HTML utilizzando Python
-
11-07-2019 - |
Domanda
Vorrei estrarre il testo da un file HTML utilizzando Python.Voglio essenzialmente lo stesso risultato che otterrei se copiassi il testo da un browser e lo incollassi nel blocco note.
Mi piacerebbe qualcosa di più robusto rispetto all'utilizzo di espressioni regolari che potrebbero fallire su HTML mal formato.Ho visto molte persone consigliare Beautiful Soup, ma ho avuto qualche problema nell'usarlo.Per prima cosa, ha rilevato testo indesiderato, come il codice sorgente JavaScript.Inoltre, non interpretava le entità HTML.Ad esempio, mi aspetterei 'nel sorgente HTML da convertire in un apostrofo nel testo, proprio come se avessi incollato il contenuto del browser nel blocco note.
Aggiornamento html2text
sembra promettente.Gestisce correttamente le entità HTML e ignora JavaScript.Tuttavia, non produce esattamente testo semplice;produce un ribasso che dovrebbe poi essere trasformato in testo semplice.Non viene fornito con esempi o documentazione, ma il codice sembra pulito.
Domande correlate:
Soluzione
html2text è un programma Python che fa un ottimo lavoro in questo.
Altri suggerimenti
Il miglior pezzo di codice che ho trovato per estrarre testo senza ottenere javascript o cose non volute:
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)
Devi solo installare BeautifulSoup prima di:
pip install beautifulsoup4
NOTA: NTLK non supporta più la funzione clean_html
Risposta originale di seguito e un'alternativa nelle sezioni dei commenti.
Utilizza NLTK
Ho perso le mie 4-5 ore a risolvere i problemi con html2text. Per fortuna ho potuto incontrare NLTK.
Funziona magicamente.
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)
Oggi mi sono trovato ad affrontare lo stesso problema. Ho scritto un parser HTML molto semplice per eliminare il contenuto in arrivo di tutti i markup, restituendo il testo rimanente con solo un minimo di formattazione.
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()
Ecco una versione della risposta di xperroni che è un po 'più completa. Salta le sezioni di script e stile e traduce charrefs (ad es. & Amp; # 39;) ed entità HTML (ad es. & Amp; amp;).
Include anche un banale convertitore inverso da semplice testo 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)
Puoi usare il metodo html2text anche nella libreria stripogram.
from stripogram import html2text
text = html2text(your_html_string)
Per installare stripogram eseguire sudo easy_install stripogram
So che ci sono già molte risposte, ma la soluzione più elegante e pythonic che ho trovato è descritta, in parte, here .
from bs4 import BeautifulSoup
text = ''.join(BeautifulSoup(some_html_string, "html.parser").findAll(text=True))
Aggiornamento
Sulla base del commento di Fraser, ecco una soluzione più elegante:
from bs4 import BeautifulSoup
clean_text = ''.join(BeautifulSoup(some_html_string, "html.parser").stripped_strings)
Esiste una libreria di pattern per il data mining.
http://www.clips.ua.ac.be/pages/ pattern-web
Puoi persino decidere quali tag conservare:
s = URL('http://www.clips.ua.ac.be').download()
s = plaintext(s, keep={'h1':[], 'h2':[], 'strong':[], 'a':['href']})
print s
PyParsing fa un ottimo lavoro. La wiki di PyParsing è stata uccisa, quindi ecco un'altra posizione in cui ci sono esempi dell'uso di PyParsing ( link di esempio ). Uno dei motivi per investire un po 'di tempo con il pyparsing è che ha anche scritto un breve manuale O'Reilly Short Cut molto ben organizzato che è anche economico.
Detto questo, uso BeautifulSoup molto e non è così difficile gestire i problemi delle entità, puoi convertirli prima di eseguire BeautifulSoup.
Goodluck
Questa non è esattamente una soluzione Python, ma convertirà il testo che Javascript genererebbe in testo, che penso sia importante (ad esempio google.com). Il browser Links (non Lynx) ha un motore Javascript e convertirà l'origine in testo con l'opzione -dump.
Quindi potresti fare qualcosa del tipo:
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()
Invece del modulo HTMLParser, controlla htmllib. Ha un'interfaccia simile, ma fa più lavoro per te. (È piuttosto antico, quindi non è di grande aiuto in termini di eliminazione di JavaScript e CSS. Potresti creare una classe derivata, ma aggiungere metodi con nomi come start_script e end_style (vedi i documenti di Python per i dettagli), ma è difficile per farlo in modo affidabile per HTML non valido.) Comunque, ecco qualcosa di semplice che stampa il testo normale sulla console
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)
se hai bisogno di più velocità e meno precisione, puoi usare lxml non elaborato.
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()
installa html2text usando
pip install html2text
poi,
>>> 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!
La bella zuppa converte le entità html. È probabilmente la soluzione migliore, considerando che l'HTML è spesso difettoso e pieno di problemi di codifica Unicode e HTML. Questo è il codice che uso per convertire HTML in testo non elaborato:
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 ""
Consiglio un pacchetto Python chiamato goose-extractor Goose tenterà di estrarre le seguenti informazioni:
Testo principale di un articolo Immagine principale dell'articolo Qualsiasi film su Youtube / Vimeo incorporato nell'articolo Meta Description Meta tag
Un'altra opzione è quella di eseguire l'html attraverso un browser Web basato su testo e scaricarlo. Ad esempio (utilizzando Lynx):
lynx -dump html_to_convert.html > converted_html.txt
Questo può essere fatto all'interno di uno script Python come segue:
import subprocess
with open('converted_html.txt', 'w') as outputFile:
subprocess.call(['lynx', '-dump', 'html_to_convert.html'], stdout=testFile)
Non ti darà esattamente solo il testo del file HTML, ma a seconda del tuo caso d'uso potrebbe essere preferibile all'output di html2text.
Un'altra soluzione non python: Libre Office:
soffice --headless --invisible --convert-to txt input1.html
Il motivo per cui preferisco questo ad altre alternative è che ogni paragrafo HTML viene convertito in una singola riga di testo (nessuna interruzione di riga), che è quello che stavo cercando. Altri metodi richiedono la post-elaborazione. Lynx produce un output piacevole, ma non esattamente quello che stavo cercando. Inoltre, Libre Office può essere utilizzato per convertire da tutti i tipi di formati ...
Qualcuno ha provato bleach.clean(html,tags=[],strip=True)
con candeggina ? funziona per me.
So che ci sono già molte risposte qui, ma penso newspaper3k merita anche una menzione. Di recente ho dovuto completare un compito simile per estrarre il testo dagli articoli sul Web e questa libreria ha svolto un ottimo lavoro nel raggiungere questo obiettivo finora nei miei test. Ignora il testo trovato nelle voci di menu e nelle barre laterali nonché qualsiasi JavaScript che appare sulla pagina come richiesto dall'OP.
from newspaper import Article
article = Article(url)
article.download()
article.parse()
article.text
Se hai già scaricato i file HTML puoi fare qualcosa del genere:
article = Article('')
article.set_html(html)
article.parse()
article.text
Ha anche alcune funzionalità di PNL per riassumere gli argomenti degli articoli:
article.nlp()
article.summary
Ho avuto buoni risultati con Apache Tika . Il suo scopo è l'estrazione di metadati e testo dal contenuto, quindi il parser sottostante è sintonizzato di conseguenza fuori dalla scatola.
Tika può essere eseguito come server , è banale da eseguire / distribuire in un Docker container, e da lì è possibile accedere tramite Binding Python .
in modo semplice
import re
html_text = open('html_file.html').read()
text_filtered = re.sub(r'<(.*?)>', '', html_text)
questo codice trova tutte le parti del testo html che iniziano con '<' e termina con '>' e sostituisci tutto trovato da una stringa vuota
La risposta di @ PeYoTIL usando BeautifulSoup ed eliminando lo stile e il contenuto dello script non ha funzionato per me. L'ho provato usando decompose
invece di extract
ma non ha ancora funzionato. Così ho creato il mio che formatta anche il testo usando i tag <p>
e sostituisce i tag <a>
con il collegamento href. Gestisce anche i collegamenti all'interno del testo. Disponibile su questa sintesi con un documento di prova incorporato.
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
In Python 3.x puoi farlo in modo molto semplice importando i pacchetti 'imaplib' e 'email'.Anche se questo è un post più vecchio, forse la mia risposta può aiutare i nuovi arrivati in questo post.
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
Ora puoi stampare la variabile body e sarà in formato testo normale :) Se è abbastanza buono per te, sarebbe carino selezionarlo come risposta accettata.
I lavori migliori per me sono gli script.
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)
I risultati sono davvero buoni
puoi estrarre solo testo dall'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)
Mentre molte persone hanno menzionato l'uso di regex per eliminare i tag html, ci sono molti aspetti negativi.
ad esempio:
<p>hello world</p>I love you
Dovrebbe essere analizzato su:
Hello world
I love you
Ecco uno snippet che ho ideato, puoi personalizzarlo in base alle tue esigenze specifiche e funziona come un fascino
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("<br>|<br />|</p>|</div>|</h\d>", "\n", ret, flags = re.IGNORECASE)
ret = re.sub('<.*?>', ' ', ret, flags=re.DOTALL)
ret = re.sub(r" +", " ", ret)
return ret
Ecco il codice che uso regolarmente.
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
Spero che sia d'aiuto.
Il commento dello scrittore di LibreOffice ha valore poiché l'applicazione può utilizzare macro Python. Sembra offrire molteplici vantaggi sia per rispondere a questa domanda sia per migliorare la base macro di LibreOffice. Se questa risoluzione è un'implementazione unica, anziché essere utilizzata come parte di un programma di produzione più ampio, l'apertura dell'HTML in scrittura e il salvataggio della pagina come testo sembrano risolvere i problemi discussi qui.
Perl way (scusa mamma, non lo farò mai in produzione).
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
Lo sto realizzando in questo modo.
>>> import requests
>>> url = "http://news.bbc.co.uk/2/hi/health/2284783.stm"
>>> res = requests.get(url)
>>> text = res.text