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:

È stato utile?

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 {'&':'&amp;', "'":'&#39;', '"':'&quot;', '<':'&lt;', '>':'&gt;'}.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("&nbsp;", " ")
    # 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

Altro: https://pypi.python.org/pypi/goose-extractor/

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&nbsp;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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top