使用Python将HTML转换为文本

82
我正在尝试使用Python将HTML块转换为文本。 输入:
<div class="body"><p><strong></strong></p>
<p><strong></strong>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Consectetuer adipiscing elit. <a href="http://example.com/" target="_blank" class="source">Some Link</a> Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Aenean massa.Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p></div>

预期输出:

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

Consectetuer adipiscing elit. Some Link Aenean commodo ligula eget dolor. Aenean massa

Aenean massa.Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

Consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

我尝试使用html2text模块,但并没有取得太大的成效:

#!/usr/bin/env python

import urllib2
import html2text
from BeautifulSoup import BeautifulSoup

soup = BeautifulSoup(urllib2.urlopen('http://example.com/page.html').read())

txt = soup.find('div', {'class' : 'body'})

print(html2text.html2text(txt))

我想将txt对象转换为文本,并在屏幕上打印出来。


1
你必须使用Python吗?可以使用lynx -dump filename.html来完成此操作。http://lynx.browser.org/ 另外,您还可以使用XPath表达式和http://www.w3.org/Tools/HTML-XML-utils/。 - Dave Jarvis
18个回答

140

soup.get_text() 输出你想要的内容:

from bs4 import BeautifulSoup
soup = BeautifulSoup(html)
print(soup.get_text())

输出:

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa
Consectetuer adipiscing elit. Some Link Aenean commodo ligula eget dolor. Aenean massa
Aenean massa.Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa
Consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

保留换行:

print(soup.get_text('\n'))

为了与您的示例完全相同,您可以将一个换行符替换为两个换行符:

soup.get_text().replace('\n','\n\n')

3
soup.get_text()正是我所需要的。谢谢! - Aaron Bandelli
1
只能使用BeautifulSoup 4,很遗憾。 - palswim
3
这拯救了我的一天!我更新了响应以适用于Python3,并整合了@t-8ch的换行符想法。 - caram

38

使用Python标准库html.parser可以实现:

from html.parser import HTMLParser

class HTMLFilter(HTMLParser):
    text = ""
    def handle_data(self, data):
        self.text += data

f = HTMLFilter()
f.feed(data)
print(f.text)

3
这个答案非常好,没有第三方包的依赖!我的PyCharm编辑器提示我需要使用ABC mixin,以便摆脱所有抽象方法需要实现的错误。https://gist.github.com/ye/050e898fbacdede5a6155da5b3db078d - Devy
3
注意,初始化text类属性并分配self.text实例属性是不符合Pythonic的,但由于重新分配而起作用。例如,如果使用可变列表(pieces=[]self.pieces.append(data)),则该类的所有实例将共享相同的列表对象。 - akaihola
2
很棒的答案!尽管html不是Python2标准库的一部分。因此,这个解决方案只适用于Python3。 - David Ross
我在返回的文本中得到了一些JavaScript,但标准库仍然很好。 - ndemou
我现在遇到了“TypeError: can only concatenate str (not "AttribAccessDict") to str”的问题。 - yuletide
这种方法在HTML标签的关闭无效时不起作用,尽管BeautifulSoup对于这种情况表现得很好。 - discover

10
主要问题是如何保留基本的格式。以下是我自己的最小保留换行和项目符号的方法。我相信这不是您想要保留的所有内容的解决方案,但这是一个起点:
from bs4 import BeautifulSoup

def parse_html(html):
    elem = BeautifulSoup(html, features="html.parser")
    text = ''
    for e in elem.descendants:
        if isinstance(e, str):
            text += e.strip()
        elif e.name in ['br',  'p', 'h1', 'h2', 'h3', 'h4','tr', 'th']:
            text += '\n'
        elif e.name == 'li':
            text += '\n- '
    return text



上述代码为 'br', 'p', 'h1', 'h2', 'h3', 'h4', 'tr', 'th' 标签添加了一个新行,并为列表项 (li) 元素添加了在文本前面的 - 符号。

最佳答案。 - Chrisjan

8

您可以使用正则表达式,但不建议这样做。以下代码可删除数据中的所有HTML标记,仅保留文本:

import re

data = """<div class="body"><p><strong></strong></p>
<p><strong></strong>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Consectetuer adipiscing elit. <a href="http://example.com/" target="_blank" class="source">Some Link</a> Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Aenean massa.Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p></div>"""

data = re.sub(r'<.*?>', '', data)

print(data)

输出

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa
Consectetuer adipiscing elit. Some Link Aenean commodo ligula eget dolor. Aenean massa
Aenean massa.Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa
Consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

2
请参考此链接:https://dev59.com/X3I-5IYBdhLWcg3wq6do ;-) - Dave Jarvis
3
@DaveJarvis 哈哈... 这里的 OP 并不想与 HTML 有任何关系,他只是想将 HTML 完全剔除。 - ATOzTOA
3
仍然,https://dev59.com/X3I-5IYBdhLWcg3wq6do#1732454 仍然非常相关。/<.*?>/ 的问题比我在这里能列举的600个字符还要多。 - Tobia

5
'\n'会在段落之间添加换行符。
from bs4 import Beautifulsoup

soup = Beautifulsoup(text)
print(soup.get_text('\n'))

1
如果您有例如"<p>That's <strong>not</strong> what I want</p>",则还会在句子中间插入换行符。 - remram
我仍然在输出中看到XML元素(尽管这些不是严格的HTML元素,例如[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG></o:AllowPNG>...)。我如何过滤掉它们? - Csaba Toth
我还想转换任何HTML字符,比如 &nbsp;&copy; - Csaba Toth
与@CsabaToth相同的问题。 - caram

5

我非常喜欢@FrBrGeorge的无依赖答案,所以我将其扩展为仅提取标签,并添加了一个方便的方法,使HTML转换为文本成为一行:

from abc import ABC
from html.parser import HTMLParser


class HTMLFilter(HTMLParser, ABC):
    """
    A simple no dependency HTML -> TEXT converter.
    Usage:
          str_output = HTMLFilter.convert_html_to_text(html_input)
    """
    def __init__(self, *args, **kwargs):
        self.text = ''
        self.in_body = False
        super().__init__(*args, **kwargs)

    def handle_starttag(self, tag: str, attrs):
        if tag.lower() == "body":
            self.in_body = True

    def handle_endtag(self, tag):
        if tag.lower() == "body":
            self.in_body = False

    def handle_data(self, data):
        if self.in_body:
            self.text += data

    @classmethod
    def convert_html_to_text(cls, html: str) -> str:
        f = cls()
        f.feed(html)
        return f.text.strip()           

请参阅用法注释。

这将转换 body 内的所有文本,理论上可能包括 stylescript 标签。通过扩展类似于 body 的模式来设置实例变量 in_stylein_script,可以实现进一步的过滤。


3

有一个叫做 inscripts 的库非常简单轻便,可以从文件或直接从URL获取输入:

from inscriptis import get_text
text = get_text(html)
print(text)

输出结果为:

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

Consectetuer adipiscing elit. Some Link Aenean commodo ligula eget dolor. Aenean massa

Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

Consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

注意:原文中的HTML标签已保留。

3

这里有一些好的点子,我也可以分享我的解决方案:

from html.parser import HTMLParser
def _handle_data(self, data):
    self.text += data + '\n'

HTMLParser.handle_data = _handle_data

def get_html_text(html: str):
    parser = HTMLParser()
    parser.text = ''
    parser.feed(html)

    return parser.text.strip()

2
"

gazpacho might be a good choice for this!

"

输出:


"

西班牙冷汤可能是一个不错的选择!

"
from gazpacho import Soup

html = """\
<div class="body"><p><strong></strong></p>
<p><strong></strong>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Consectetuer adipiscing elit. <a href="http://example.com/" target="_blank" class="source">Some Link</a> Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Aenean massa.Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p>
<p>Consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa</p></div>
"""

输出:

text = Soup(html).strip(whitespace=False) # to keep "\n" characters intact
print(text)
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa
Consectetuer adipiscing elit. Some Link Aenean commodo ligula eget dolor. Aenean massa
Aenean massa.Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa
Consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa

1
可以使用BeautifulSoup移除不需要的脚本等内容,但您可能需要尝试几个不同的网站以确保您已涵盖了希望排除的不同类型的内容。请尝试以下方法:
from requests import get
from bs4 import BeautifulSoup as BS
response = get('http://news.bbc.co.uk/2/hi/health/2284783.stm')
soup = BS(response.content, "html.parser")
for child in soup.body.children:
   if child.name == 'script':
       child.decompose() 
print(soup.body.get_text())

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接