Python代码:从字符串中删除HTML标签

208

我有这样一个文本:

text = """<div>
<h1>Title</h1>
<p>A long text........ </p>
<a href=""> a link </a>
</div>"""

我想要使用纯Python,没有外部模块,实现以下功能:

>>> print remove_tags(text)
Title A long text..... a link
我知道可以使用 lxml.html.fromstring(text).text_content() 来完成,但我需要在纯Python中使用内置或标准库来实现2.6+版本的相同功能。如何才能做到?

2
你不想使用外部模块的特定原因是什么? - RanRag
1
无法在服务器上安装模块的权限... - Bruno Rocha - rochacbruno
5个回答

428

使用正则表达式

通过正则表达式,你可以清除所有在<>内的内容:

import re
# as per recommendation from @freylis, compile once only
CLEANR = re.compile('<.*?>') 

def cleanhtml(raw_html):
  cleantext = re.sub(CLEANR, '', raw_html)
  return cleantext

有些HTML文本中可能还包含未封闭在括号内的实体,例如'&nsbm'。如果是这种情况,您可能希望将正则表达式编写为

CLEANR = re.compile('<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});')

这个链接包含更多关于这个的细节。

使用BeautifulSoup

你也可以使用BeautifulSoup库来查找所有原始文本。

在调用BeautifulSoup时,你需要明确地设置一个解析器。我推荐使用如此被提及的替代方案中的"lxml"(比默认的解析器html.parser 更强大(即无需额外安装))。

from bs4 import BeautifulSoup
cleantext = BeautifulSoup(raw_html, "lxml").text

但这并不能防止你使用外部库,因此我建议采用第一种解决方案。

编辑:要使用lxml,需要执行pip install lxml命令。


15
如果您想编译正则表达式,最好的方法是在函数外部进行编译。在您的示例中,每次调用 cleanhtml 都必须重新编译正则表达式。 - freylis
7
当标记语言较重时,使用BeautifulSoup效果很好;否则,尽量避免使用,因为它的速度很慢。 - Ethan
1
很好的回答。不过你忘记在 def cleanhtml(raw_html) 的末尾加上冒号了 :) - bjesus
4
好的回答。你可能想在BeautifulSoup中明确设置解析器,使用cleantext = BeautifulSoup(raw_html, "html.parser").text - Zemogle
1
这个答案的前半部分应该被删除,因为尝试这样做是非常错误的。HTML需要被解析成树形结构,并且理解<script>和其他标签可以包含任何内容。我以最礼貌的方式说这句话,c24b也承认了这一点。 - ldmtwo
显示剩余10条评论

50

Python内置了几个XML模块。对于你已经有完整HTML字符串的情况,最简单的是使用xml.etree,它的工作方式(有点)类似于你提到的lxml示例:

def remove_tags(text):
    return ''.join(xml.etree.ElementTree.fromstring(text).itertext())

3
这对我有用,但要小心自动关闭类型的HTML标签。例如:</br>我遇到了“ParseError:mismatched tag:line 1,column 9”的问题,因为这个标签在未开启的情况下被关闭。所有自动关闭的HTML标签都是如此。 - 1ronmat

40

请注意,这并不完美,如果您有类似于<a title=">">的内容,它会出现错误。 但是,在非库Python中,这是最接近您能够得到而不需要使用真正复杂的函数的方法:

import re

TAG_RE = re.compile(r'<[^>]+>')

def remove_tags(text):
    return TAG_RE.sub('', text)

然而,正如lvc所提到的那样,xml.etree在Python标准库中可用,因此您可以很可能将其改编为类似于现有的lxml版本:

def remove_tags(text):
    return ''.join(xml.etree.ElementTree.fromstring(text).itertext())

2
我喜欢你的正则表达式方法,如果性能是一个重要因素,也许它会更好。 - Douglas Camata
@SlaterTyranus 这取决于XML解析器和正则表达式实现。我猜两者都使用C扩展...但你有任何基准测试结果可以让我们看到吗? - Douglas Camata
3
值得注意的是,如果您的文档中有一个文本 <,那么这将会引起问题。 - Slater Victoroff
1
@PatrickT 你需要将它导出 - import xml.etree - Amber
@Amber,谢谢!我误解了“在Python标准库中可用”的含义,以为它总是可用。 - PatrickT
显示剩余5条评论

9
在任何类C语言中都有一种简单的方法。这种风格不是Pythonic,但在纯Python中可以使用:
def remove_html_markup(s):
    tag = False
    quote = False
    out = ""

    for c in s:
            if c == '<' and not quote:
                tag = True
            elif c == '>' and not quote:
                tag = False
            elif (c == '"' or c == "'") and tag:
                quote = not quote
            elif not tag:
                out = out + c

    return out

这个想法基于一个简单的有限状态机,并在这里详细解释:http://youtu.be/2tu9LTDujbw 你可以在这里看到它的工作原理:http://youtu.be/HPkNPcYed9M?t=35s PS-如果你对使用Python进行智能调试课程感兴趣,我给你提供一个链接:https://www.udacity.com/course/software-debugging--cs259。它是免费的!

8
这段代码会因为引号不匹配而出错,并且由于每个字符都要逐一输出,所以速度相当慢。但它足以说明一个基本的逐字符解析器并不难写。 - Tomasz Gandor
1
这个答案非常适合教授HTML或Python,但对于生产使用来说缺少一个关键点:符合标准很难,而使用一个得到良好支持的库可以避免在本来健康的期限内进行数周的研究和/或错误调试。 - jpaugh

-13
global temp

temp =''

s = ' '

def remove_strings(text):

    global temp 

    if text == '':

        return temp

    start = text.find('<')

    end = text.find('>')

    if start == -1 and end == -1 :

        temp = temp + text

    return temp

newstring = text[end+1:]

fresh_start = newstring.find('<')

if newstring[:fresh_start] != '':

    temp += s+newstring[:fresh_start]

remove_strings(newstring[fresh_start:])

return temp

30
你的回答存在以下问题:a)格式非常不规范(例如违反了PEP 8),b)过于复杂,因为有可以完成同样任务的工具,c)容易出错(如果HTML某个属性中包含">"字符会怎么处理?),d)在21世纪,在这种简单情况下全局操作是否有必要? - Drachenfels

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