Извлечение текста из HTML-файла с помощью Python

Я хотел бы извлечь текст из HTML-файла с помощью Python. Я хочу получить тот же результат, что и при копировании текста из браузера и вставке его в блокнот.

Я хотел бы что-то более надежное, чем использование регулярных выражений, которые могут не работать на плохо сформированном HTML. Я видел, как многие люди рекомендуют красивый суп, но у меня было несколько проблем с его использованием. Во-первых, он взял нежелательный текст, такой как источник JavaScript. Кроме того, он не интерпретировал объекты HTML. Например, я ожидал бы, что " in HTML source будет преобразован в Апостроф в тексте, как если бы я вставил содержимое браузера в блокнот.

обновление html2text выглядит многообещающе. Он правильно обрабатывает HTML-объекты и игнорирует JavaScript. Однако это не совсем простой текст; он создает уценку, которая затем должна быть превращена в обычный текст. В нем нет примеров или документации, но код выглядит чистым.


обзоры вопросы:

  • отфильтровать HTML-теги и разрешить объекты в python
  • преобразование объектов XML / HTML в строку Unicode в Python

29 ответов


html2text Это программа Python, которая делает довольно хорошую работу в этом.


лучший кусок кода, который я нашел для извлечения текста без получения javascript или не хотел вещей:

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)

вам просто нужно установить BeautifulSoup перед :

pip install beautifulsoup4

Примечание: NTLK больше не поддерживает clean_html функции

оригинальный ответ ниже и альтернатива в разделах комментариев.


использовать в nltk

Я потратил свои 4-5 часов, исправляя проблемы с html2text. К счастью, я мог столкнуться с НЛТК.
Это работает волшебно.

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)

сегодня я столкнулся с той же проблемой. Я написал очень простой парсер HTML, чтобы очистить входящее содержимое от всех наценок, возвращая оставшийся текст с минимальным форматированием.

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

вот версия ответа xperroni, которая немного более полная. Он пропускает разделы сценария и стиля и переводит charrefs (например, ') и HTML-объекты (например, &).

Он также включает в себя тривиальный преобразователь простого текста в 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)

вы также можете использовать метод html2text в библиотеке stripogram.

from stripogram import html2text
text = html2text(your_html_string)

для установки stripogram запустите sudo easy_install stripogram


существует библиотека шаблонов для интеллектуального анализа данных.

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

вы даже можете решить, какие теги сохранять:

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

PyParsing делает большую работу. Pyparsing wiki был убит, поэтому вот еще одно место, где есть примеры использования PyParsing (пример ссылки). Одна из причин, по которой он потратил немного времени на pyparsing, заключается в том, что он также написал очень краткое, очень хорошо организованное руководство O'Reilly Short Cut, которое также является недорогим.

сказав это, я использую BeautifulSoup много, и это не так сложно справиться с проблемами сущностей, вы можете конвертировать их перед вами запустить BeautifulSoup.

Goodluck


Я знаю, что есть много ответов, но самый Ма и весть решение, которое я нашел, описано, в частности,здесь.

from bs4 import BeautifulSoup

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

обновление

основываясь на комментарии Фрейзера, вот более элегантное решение:

from bs4 import BeautifulSoup

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

это не совсем решение Python, но оно преобразует текст Javascript в текст, который я считаю важным (например google.com). Ссылки браузера (не Lynx) имеют движок Javascript и преобразуют источник в текст с опцией-dump.

таким образом, вы можете сделать что-то вроде:

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

вместо модуля HTMLParser проверьте htmllib. Он имеет аналогичный интерфейс, но делает больше работы для вас. (Он довольно древний, поэтому это не очень помогает с точки зрения избавления от javascript и css. Вы можете создать производный класс, но и добавить методы с именами, такими как start_script и end_style (см. документы python для деталей), но это трудно сделать надежно для искаженного html.) Во всяком случае, вот что-то простое, что печатает простой текст на консоль

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)

Если вам нужно больше скорости и меньше точности, то вы можете использовать raw 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()

установить html2text используя

pip установить html2text

затем,

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

красивый суп преобразует HTML-объекты. Это, вероятно, ваш лучший выбор, учитывая, что HTML часто глючит и заполнен проблемами кодирования unicode и html. Это код, который я использую для преобразования html в необработанный текст:

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

Я рекомендую пакет Python под названием goose-extractor Гусь попытается извлечь следующую информацию:

основной текст статьи Основное изображение статьи Любые фильмы Youtube/Vimeo, встроенные в статью метаописание Мета теги

еще:https://pypi.python.org/pypi/goose-extractor/


другой вариант-запустить html через текстовый веб-браузер и сбросить его. Например (с помощью Lynx):

lynx -dump html_to_convert.html > converted_html.txt

Это можно сделать в скрипте python следующим образом:

import subprocess

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

Он не даст вам точно только текст из HTML-файла, но в зависимости от вашего варианта использования он может быть предпочтительнее вывода html2text.


другое решение, отличное от python: Libre Office:

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

причина, по которой я предпочитаю этот вариант другим альтернативам, заключается в том, что каждый HTML-абзац преобразуется в одну текстовую строку (без разрывов строк), что я и искал. Другие методы требуют последующей обработки. Lynx производит хороший результат, но не совсем то, что я искал. Кроме того, Libre Office можно использовать для конвертации из любых форматов...


Я знаю, что здесь уже много ответов, но я думаю newspaper3k также заслуживает упоминания. Недавно мне нужно было выполнить аналогичную задачу извлечения текста из статей в интернете, и эта библиотека проделала отличную работу по достижению этого до сих пор в моих тестах. Он игнорирует текст, найденный в пунктах меню и боковых барах, а также любой JavaScript, который появляется на странице в качестве запросов OP.

from newspaper import Article

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

Если у вас уже есть HTML-файлы, загруженные вами можете сделать что-то вроде этого:

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

Он даже имеет несколько функций НЛП для обобщения тем статей:

article.nlp()
article.summary

простым способом

import re

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

этот код находит все части html_text, начинающиеся с " " и заменяют все найденные пустой строкой


@peyotil's ответ, используя BeautifulSoup и устраняя стиль и содержание сценария не работал для меня. Я попробовал использовать decompose вместо extract но это все равно не сработало. Поэтому я создал свой собственный, который также форматирует текст с помощью <p> теги и заменяет <a> теги со ссылкой href. Также справляется со ссылками внутри текста. Доступно по адресу в этом суть С встроенным тестовым документом.

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

кто-нибудь пробовал bleach.clean(html,tags=[],strip=True) с отбеливатель? это работает на меня.


В Python 3.x вы можете сделать это очень простым способом, импортировав пакеты "imaplib" и "email". Хотя это более старый пост, но, возможно, мой ответ может помочь новичкам на этом посту.

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

теперь вы можете распечатать переменную body, и она будет в текстовом формате :) если она достаточно хороша для вас, было бы неплохо выбрать ее в качестве принятого ответа.


У меня были хорошие результаты с Apache Tika. Его цель-извлечение метаданных и текста из контента, поэтому базовый парсер настроен соответствующим образом из коробки.

Tika можно запустить как сервер, тривиально запускать / развертывать в контейнере Docker, и оттуда можно получить доступ через Python-привязки.


вот код, который я использую на регулярной основе.

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

надеюсь, это поможет.


лучше всего для меня работает inscripts .

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)

результаты действительно хорошие


вы можете извлечь только текст из HTML с 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)

комментарий LibreOffice writer имеет достоинство, так как приложение может использовать макросы python. Кажется, что он предлагает множество преимуществ как для ответа на этот вопрос, так и для дальнейшего развития макроосновы LibreOffice. Если это решение является одноразовой реализацией, а не будет использоваться как часть большой производственной программы, открытие HTML в writer и сохранение страницы в виде текста, по-видимому, решит проблемы, обсуждаемые здесь.


Perl way (извините, мама, я никогда не буду делать это в производстве).

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

Я достигаю чего-то подобного.

>>> import requests
>>> url = "http://news.bbc.co.uk/2/hi/health/2284783.stm"
>>> res = requests.get(url)
>>> text = res.text