使用 Python 将 html 转换为文本

我正在尝试使用 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>

预期输出:

< p > Lorem 爱因斯坦的精英们 埃涅阿斯马萨。 有一些 连接埃尼亚康莫多里古拉。埃尼亚马萨。埃尼亚 主人。罗姆以我的名义受苦受难,连续受苦的精英。埃涅阿斯 (咒语) 埃涅安语意大利语意大利语意大利语意大利语意大利语意大利语意大利语意大利语意大利语意大利语意大利语意大利语。 埃涅阿斯马萨埃涅阿斯穆多 (咒语)

我尝试了 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对象生成上面的 html 块。我想把它转换成文本并在屏幕上打印出来。

146074 次浏览

可以使用正则表达式,但不推荐这样做。下面的代码删除数据中的所有 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

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

'\n'在段落之间放置换行符。

from bs4 import Beautifulsoup


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

我需要一种在客户机系统上完成这项工作的方法,而不必下载额外的库。我从来没有找到一个好的解决方案,所以我创造了我自己的。如果你喜欢,可以用这个。

import urllib


def html2text(strText):
str1 = strText
int2 = str1.lower().find("<body")
if int2>0:
str1 = str1[int2:]
int2 = str1.lower().find("</body>")
if int2>0:
str1 = str1[:int2]
list1 = ['<br>',  '<tr',  '<td', '</p>', 'span>', 'li>', '</h', 'div>' ]
list2 = [chr(13), chr(13), chr(9), chr(13), chr(13),  chr(13), chr(13), chr(13)]
bolFlag1 = True
bolFlag2 = True
strReturn = ""
for int1 in range(len(str1)):
str2 = str1[int1]
for int2 in range(len(list1)):
if str1[int1:int1+len(list1[int2])].lower() == list1[int2]:
strReturn = strReturn + list2[int2]
if str1[int1:int1+7].lower() == '<script' or str1[int1:int1+9].lower() == '<noscript':
bolFlag1 = False
if str1[int1:int1+6].lower() == '<style':
bolFlag1 = False
if str1[int1:int1+7].lower() == '</style':
bolFlag1 = True
if str1[int1:int1+9].lower() == '</script>' or str1[int1:int1+11].lower() == '</noscript>':
bolFlag1 = True
if str2 == '<':
bolFlag2 = False
if bolFlag1 and bolFlag2 and (ord(str2) != 10) :
strReturn = strReturn + str2
if str2 == '>':
bolFlag2 = True
if bolFlag1 and bolFlag2:
strReturn = strReturn.replace(chr(32)+chr(13), chr(13))
strReturn = strReturn.replace(chr(9)+chr(13), chr(13))
strReturn = strReturn.replace(chr(13)+chr(32), chr(13))
strReturn = strReturn.replace(chr(13)+chr(9), chr(13))
strReturn = strReturn.replace(chr(13)+chr(13), chr(13))
strReturn = strReturn.replace(chr(13), '\n')
return strReturn




url = "http://www.theguardian.com/world/2014/sep/25/us-air-strikes-islamic-state-oil-isis"
html = urllib.urlopen(url).read()
print html2text(html)

可以使用 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())

可以使用 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)

我非常喜欢@FrBrGeorge 的 没有依赖性答案,所以我扩展了它,只提取 body标签,并添加了一个方便的方法,使 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内部的所有文本,从理论上讲,body可以包括 stylescript标记。进一步的过滤可以通过扩展 body所示的模式来实现——即设置实例变量 in_stylein_script

这里有些不错的东西,我不妨加上我的解决方案:

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

主要问题是如何保持一些基本的格式。下面是我自己的保留新行和子弹的最小方法。我知道这不是你想要保留的所有东西的解决方案,但它是一个起点:

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元素的文本前面带有 -的新行

基于 强 > lxml的两步方法,在转换为纯文本之前对标记进行消毒。

该脚本接受 HTML 文件的路径或管道标准输入。

将删除脚本块和所有可能不需要的文本。您可以配置 Lxml 清洁器实例以满足您的需要。

#!/usr/bin/env python3


import sys
from lxml import html
from lxml.html import tostring
from lxml.html.clean import Cleaner




def sanitize(dirty_html):
cleaner = Cleaner(page_structure=True,
meta=True,
embedded=True,
links=True,
style=True,
processing_instructions=True,
inline_style=True,
scripts=True,
javascript=True,
comments=True,
frames=True,
forms=True,
annoying_tags=True,
remove_unknown_tags=True,
safe_attrs_only=True,
safe_attrs=frozenset(['src','color', 'href', 'title', 'class', 'name', 'id']),
remove_tags=('span', 'font', 'div')
)


return cleaner.clean_html(dirty_html)




if len(sys.argv) > 1:
fin = open(sys.argv[1], encoding='utf-8')
else:
fin = sys.stdin


source = fin.read()
source = sanitize(source)
source = source.replace('<br>', '\n')


tree = html.fromstring(source)
plain = tostring(tree, method='text', encoding='utf-8')


print(plain.decode('utf-8'))

我个人喜欢使用 呕吐物的 Gazpacho 解决方案,但它只使用正则表达式来过滤掉标记。没有魔法了。这意味着解决方案将文本保存在 < style > 和 < script > 中。

因此,我宁愿实现一个基于正则表达式的简单解决方案,并使用标准 Python 3.4库来实现 unescape HTML 实体:

import re
from html import unescape


def html_to_text(html):


# use non-greedy for remove scripts and styles
text = re.sub("<script.*?</script>", "", html, flags=re.DOTALL)
text = re.sub("<style.*?</style>", "", text, flags=re.DOTALL)


# remove other tags
text = re.sub("<[^>]+>", " ", text)


# strip whitespace
text = " ".join(text.split())


# unescape html entities
text = unescape(text)


return text

当然,这不能作为 BeautifulSoup 或其他解析器解决方案来证明错误。但你不需要任何第三方包裹。

from html.parser import HTMLParser


class HTMLFilter(HTMLParser):
text = ''
def handle_data(self, data):
self.text += f'{data}\n'


def html2text(html):
filter = HTMLFilter()
filter.feed(html)


return filter.text


content = html2text(content_temp)

我在使用 Scrapy 时遇到了同样的问题,您可以尝试将其添加到 setings.py

#settings.py
FEED_EXPORT_ENCODING = 'utf-8'

有一个名为 铭文的库非常简单、轻便,可以从文件或直接从 URL 获取输入:

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

输出结果是:

[拉丁语] (咒语)

一些连接埃尼亚的商标 痛苦,埃涅阿斯大屠杀

(咒语) 精英,埃尼亚人,意大利人,埃尼亚人

[拉丁语] (咒语)

(咒语)。 埃涅阿斯大帝