web-dev-qa-db-fra.com

Extraire le texte d'un fichier HTML à l'aide de Python

J'aimerais extraire le texte d'un fichier HTML à l'aide de Python. Je veux essentiellement le même résultat que si je copiais le texte depuis un navigateur et que je le collais dans le bloc-notes. 

Je voudrais quelque chose de plus robuste que d'utiliser des expressions régulières pouvant échouer avec du HTML mal formé. J'ai vu beaucoup de gens recommander Beautiful Soup, mais j'ai eu quelques problèmes pour l'utiliser. D'une part, il a ramassé du texte indésirable, tel que la source JavaScript. En outre, il n'a pas interprété les entités HTML. Par exemple, je m'attendrais à ce que 'in HTML source soit converti en apostrophe en texte, comme si j'avais collé le contenu du navigateur dans le bloc-notes.

Mise à jourhtml2text semble prometteur. Il gère correctement les entités HTML et ignore JavaScript. Cependant, cela ne produit pas exactement du texte brut; il produit un démarquage qui devrait ensuite être transformé en texte brut. Il ne contient aucun exemple ni documentation, mais le code est propre.


Questions connexes:

195
John D. Cook

html2text est un programme Python qui fait un très bon travail à cet égard.

110
RexE

Le meilleur morceau de code que j'ai trouvé pour extraire du texte sans obtenir de javascript ou ne pas vouloir des choses:

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)

Il vous suffit d'installer BeautifulSoup avant:

pip install beautifulsoup4
110
PeYoTlL

NOTE: NTLK ne prend plus en charge la fonction clean_html

Réponse originale ci-dessous, et une alternative dans les sections commentaires.


Utilisez NLTK

J'ai perdu mes 4-5 heures à résoudre les problèmes liés à html2text. Heureusement, j'ai pu rencontrer NLTK.
Cela fonctionne comme par magie. 

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

Je me suis retrouvé face au même problème aujourd'hui. J'ai écrit un analyseur HTML très simple pour supprimer le contenu entrant de toutes les annotations, renvoyant le texte restant avec un minimum de formatage.

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 __== '__main__':
    main()
52
xperroni

Voici une version de la réponse de xperroni qui est un peu plus complète. Il ignore les sections de script et de style et traduit les charrefs (par exemple, ') et les entités HTML (par exemple, & amp;).

Il inclut également un convertisseur inverse trivial de texte brut en 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)
14
bit4

Vous pouvez également utiliser la méthode html2text dans la bibliothèque de stripogrammes.

from stripogram import html2text
text = html2text(your_html_string)

Pour installer le stripogramme, lancez le stripogramme Sudo easy_install

8
GeekTantra

Il existe une bibliothèque de motifs pour l'exploration de données.

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

Vous pouvez même décider quelles balises conserver:

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

PyParsing fait un excellent travail. Le wiki PyParsing a été tué, voici donc un autre emplacement où il existe des exemples d'utilisation de PyParsing ( example link ). Une des raisons pour investir un peu de temps avec les pyparses est qu’il a également écrit un très bref manuel très bien organisé de O'Reilly Short Cut qui est également peu coûteux.

Cela dit, j'utilise beaucoup BeautifulSoup et il n'est pas difficile de traiter les problèmes d'entités, vous pouvez les convertir avant de lancer BeautifulSoup. 

Bonne chance 

6
PyNEwbie

Je sais qu’il ya déjà beaucoup de réponses, mais la solution la plus elegent et Pythonic que j’ai trouvée est décrite, en partie, ici .

from bs4 import BeautifulSoup

text = ''.join(BeautifulSoup(some_html_string, "html.parser").findAll(text=True))

Mettre à jour

D'après le commentaire de Fraser, voici une solution plus élégante:

from bs4 import BeautifulSoup

clean_text = ''.join(BeautifulSoup(some_html_string, "html.parser").stripped_strings)
5
Floyd

si vous avez besoin de plus de rapidité et de moins de précision, vous pouvez utiliser le format brut LXML.

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()
4
Anton Shelin

installer html2text using 

pip installer html2text

puis,

>>> 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!
4
Pravitha V

Au lieu du module HTMLParser, consultez htmllib. Il a une interface similaire, mais fait plus de travail pour vous. (C'est assez ancien, donc ce n'est pas très utile pour se débarrasser de javascript et de css. Vous pouvez créer une classe dérivée, mais vous pouvez aussi ajouter des méthodes avec des noms comme start_script et end_style de le faire de manière fiable pour le HTML malformé.) Quoi qu'il en soit, voici quelque chose de simple qui affiche le texte brut sur la 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)
4
Mark

Ce n'est pas exactement une solution Python, mais cela convertira du texte que Javascript générerait en texte, ce qui, à mon avis, est important (par exemple, google.com). Le navigateur Links (pas Lynx) a un moteur Javascript et convertira le source en texte avec l’option -dump.

Pour que vous puissiez faire quelque chose comme:

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

Je recommande un paquet Python appelé goose-extractor Goose essaiera d'extraire les informations suivantes:

Texte principal d'un article Image principale de l'article Tous les films Youtube/Vimeo incorporés à l'article Description méta.

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

3
Li Yingjun

Une belle soupe convertit les entités html. C'est probablement votre meilleur choix étant donné que HTML est souvent bogué et rempli de problèmes d'encodage unicode et html. C’est le code que j’utilise pour convertir le code HTML en texte brut:

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 ""
3
speedplane

Je sais qu'il y a déjà beaucoup de réponses ici, mais je pense que journal3k mérite également une mention. J'ai récemment eu besoin de compléter une tâche similaire d'extraction du texte à partir d'articles sur le Web et cette bibliothèque a fait un excellent travail pour atteindre cet objectif dans mes tests. Il ignore le texte trouvé dans les éléments de menu et les barres latérales, ainsi que tout code JavaScript qui apparaît sur la page lorsque le PO demande. 

from newspaper import Article

article = Article(url)
article.download()
article.parse()
article.text

Si vous avez déjà téléchargé les fichiers HTML, vous pouvez faire quelque chose comme ceci:

article = Article('')
article.set_html(html)
article.parse()
article.text

Il a même quelques fonctionnalités de la PNL pour résumer les sujets des articles:

article.nlp()
article.summary
2
spatel4140

Autre solution non python: Libre Office:

soffice --headless --invisible --convert-to txt input1.html

La raison pour laquelle je préfère celle-ci aux autres alternatives est que chaque paragraphe HTML est converti en une seule ligne de texte (sans saut de ligne), ce que je recherchais. D'autres méthodes nécessitent un post-traitement. Lynx produit bien Nice, mais pas exactement ce que je cherchais. En outre, Libre Office peut être utilisé pour convertir à partir de toutes sortes de formats ...

2
YakovK

Une autre option consiste à exécuter le code HTML via un navigateur Web textuel et à le vider. Par exemple (en utilisant Lynx):

lynx -dump html_to_convert.html > converted_html.txt

Cela peut être fait dans un script python comme suit:

import subprocess

with open('converted_html.txt', 'w') as outputFile:
    subprocess.call(['lynx', '-dump', 'html_to_convert.html'], stdout=testFile)

Il ne vous donnera pas exactement le texte du fichier HTML, mais selon votre cas d'utilisation, il peut être préférable à la sortie de html2text. 

2
John Lucas

Quelqu'un a essayé bleach.clean(html,tags=[],strip=True) avec eau de Javel ? ça marche pour moi.

2
rox

La réponse de @ PeYoTIL à l'aide de BeautifulSoup et l'élimination du style et du contenu des scripts ne m'ont pas fonctionné. Je l'ai essayé en utilisant decompose au lieu de extract mais cela ne fonctionnait toujours pas. J'ai donc créé le mien qui formate également le texte en utilisant les balises <p> et remplace les balises <a> par le lien href. Copie également les liens dans le texte. Disponible sur this Gist avec une documentation de test intégrée.

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
1
racitup

En Python 3.x, vous pouvez le faire très facilement en important des packages "imaplib" et "email". Bien que ce soit un article plus ancien, ma réponse peut peut-être aider les nouveaux arrivants.

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

Vous pouvez maintenant imprimer une variable de corps et elle sera en texte brut :) Si cela vous convient, alors il serait agréable de le sélectionner comme réponse acceptée.

1
Wahib Ul Haq

J'ai eu de bons résultats avec Apache Tika . Son but est d’extraire les métadonnées et le texte du contenu. L’analyseur sous-jacent est donc réglé en conséquence.

Tika peut être exécuté en tant que serveur , est simple à exécuter/déployer dans un conteneur Docker, et il est accessible depuis celui-ci via Python bindings .

1
u-phoria

de manière simple

import re

html_text = open('html_file.html').read()
text_filtered = re.sub(r'<(.*?)>', '', html_text)

ce code trouve toutes les parties de html_text commencées par '<' et se terminant par '>' et remplace toutes celles trouvées par une chaîne vide

1
David Fraga

Bien que de nombreuses personnes aient mentionné l'utilisation de regex pour supprimer les balises HTML, il existe de nombreux inconvénients.

par exemple:

<p>hello&nbsp;world</p>I love you

Devrait être analysé à:

Hello world
I love you

Voici un extrait que j'ai créé, vous pouvez le personnaliser selon vos besoins spécifiques, et cela fonctionne comme un charme

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
0
Uri Goren

Un autre exemple d'utilisation de BeautifulSoup4 dans Python 2.7.9+

comprend:

import urllib2
from bs4 import BeautifulSoup

Code:

def read_website_to_text(url):
    page = urllib2.urlopen(url)
    soup = BeautifulSoup(page, 'html.parser')
    for script in soup(["script", "style"]):
        script.extract() 
    text = soup.get_text()
    lines = (line.strip() for line in text.splitlines())
    chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
    text = '\n'.join(chunk for chunk in chunks if chunk)
    return str(text.encode('utf-8'))

Expliqué:

Lisez les données url au format HTML (avec BeautifulSoup), supprimez tous les éléments de script et de style et récupérez simplement le texte avec .get_text (). Découpez des lignes et supprimez les espaces de début et de fin sur chacune d'elles, puis séparez plusieurs titres en une ligne, chaque fragment = (phrase.strip () pour ligne à ligne pour phrase à line.split ("")). Ensuite, en utilisant text = '\ n'.join, supprimez les lignes vierges, puis retournez comme sanctionné utf-8.

Notes:

  • Certains systèmes sur lesquels il est exécuté échoueront avec les connexions https: // en raison d'un problème SSL, vous pouvez désactiver la vérification pour résoudre ce problème. Exemple de correction: http://blog.pengyifan.com/how-to-fix-python-ssl-certificate_verify_failed/

  • Python <2.7.9 peut avoir un problème d’exécution

  • text.encode ('utf-8') peut laisser un encodage bizarre, peut vouloir simplement renvoyer str (text) à la place.

0
Mike Q

Perl way (désolée maman, je ne le ferai jamais en production).

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
0
brunql

Voici le code que j'utilise régulièrement.

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

J'espère que ça aide.

0
troymyname00

Le commentaire du rédacteur LibreOffice a du mérite car l’application peut utiliser des macros python. Il semble offrir de multiples avantages, à la fois pour répondre à cette question et pour renforcer la base macro de LibreOffice. Si cette résolution est une implémentation unique, plutôt que d'être utilisée dans le cadre d'un programme de production plus étendu, ouvrir le code HTML dans l'écriture et enregistrer la page en tant que texte semblerait résoudre les problèmes abordés ici.

0
1of7

Le mieux travaillé pour moi est les inscriptions. 

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)

Les résultats sont vraiment bons

vous pouvez extraire uniquement du texte HTML avec 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)
0
saigopi