strip tags python

8
我希望您能提供以下功能。
input : this is test <b> bold text </b> normal text
expected output: this is test normal text

即删除指定标签的内容。

你需要这个用于任何标签还是只是给出的示例? - Robert Christie
1
@cb160 我想要使用任何标签,例如通用标签。 - developer
你想要它清除所有标签还是只有你列出的标签? - Mike DeSimone
9个回答

9
使用 BeautifulSoup 的解决方案:
from BeautifulSoup import BeautifulSoup
def removeTag(soup, tagname):
    for tag in soup.findAll(tagname):
        contents = tag.contents
        parent = tag.parent
        tag.extract()

s = BeautifulSoup("abcd <b> btag </b> hello <d>dtag</d>")

removeTag(s,"b")
print s
removeTag(s, "d")
print s

返回:

>>>
abcd  hello <d>dtag</d>
abcd  hello

5

@Brain 我已经尝试过这个方法了,但它只是删除标签而不是其中的内容。 - developer
@user283405。嗯,确保您使用正确的措辞。我猜您需要删除元素的内容(基本上是从开标签开始到闭合标签结束的内容)。对吗? - Grzegorz Oledzki
1
嗯,是的,你的例子让情况更加清晰了。zoli2k的方法也使用了BeautifulSoup,并且更接近于你的意图。 - Brian
1
使用BeautifulSoup4,可以这样写:BeautifulSoup(page).get_text() - Honza Javorek
其实我认为正则表达式对于这个问题来说是可以的。通常情况下,正则表达式不能解析HTML的原因是因为你不知道标签可能会嵌套多深。但在这个问题中,我们并不关心这一点——对于任何嵌套的标签,我们只匹配最外层的开放和关闭标签。最大嵌套级别为1。 - Jonathan Hartley
@JonathanHartley:不完全正确,因为您可能会匹配一个注释的闭合标签而不是一个真正的标签。 - Brian

5

如果你不介意使用Python(尽管正则表达式是通用的),你可以从Django的strip_tags过滤器中获取一些灵感,详情请查看这里

为了完整起见,这里重现一下-

def strip_tags(value):
    """Returns the given HTML with all tags stripped."""
    return re.sub(r'<[^>]*?>', '', force_unicode(value))

注:如果您正在使用此方法或其他正则表达式解决方案,请记住,它可以允许一些精心制作的HTML(请参见评论),以及HTML注释,因此不应与不受信任的输入一起使用。相反,考虑使用beautifulsoup、html5lib或lxml等答案来处理不可信的输入。


Django的strip_tags过滤器存在问题,几乎无用。在属性值中放置>字符是完全有效的,而且它甚至不尝试处理其他标记构造,如注释。 - bobince
同意(我在StackOverflow上看到过一个疯狂的问题,试图设计一个正则表达式来解析HTML :)。就个人网站而言,我使用lxml来完成相同的任务(以及剥禁止的标签和属性)。我认为上面的答案在性能重要时仍然具有一些有效的用途,但应该只在考虑到这些严重限制的情况下实施。 - Sam

2

尝试使用以下步骤:

import re
input = 'this is test <b> bold text </b> normal text'
output = re.compile(r'<[^<]*?/?>').sub('', input)
print output

2
正则表达式 + HTML = 错误。实际上,由于HTML(大部分)是嵌套的,它不是一个正则语言,因此无法通过正则表达式正确解析。这就是SOlore:https://dev59.com/questions/X3I-5IYBdhLWcg3wq6do - Phil H
你说得没错;反正它在 OP 的例子中起作用了 ;)。 - systempuntoout
1
就这个问题而言,我们不解析HTML(即我们不关心文本的哪些部分位于“打开”和“关闭”标记之间),也不关心标记嵌套的深度,因此正则表达式是一个完全可以使用的工具。(尽管由于其他答案中提到的“<”字符出现而失败) - Jonathan Hartley

2

看起来你需要 HTMLParser。(在Python 3中是html.parser。)

from HTMLParser import HTMLParser
from sys import stdout
class Filter(HTMLParser):
    def __init__(self, ignored_tags):
        super(Filter, self).__init__()
        self.ignorelevel = 0
        self. ignored_tags = ignored_tags
    def handle_starttag(self, tag, attrs):
        if self.ignorelevel > 0:
            self.ignorelevel += 1
        elif tag in self.ignored_tags:
            self.ignorelevel = 1
        else:
            # One of these two.  Test and see.
            stdout.write(self.get_starttag_text())
            #stdout.write('<' + self.get_starttag_text() + '>')
    def handle_startendtag(self, tag, attrs):
        if self.ignorelevel == 0 and tag not in self.ignored_tags:
            # One of these two.  Test and see.
            stdout.write(self.get_starttag_text())
            #stdout.write('<' + self.get_starttag_text() + '/>')
    def handle_endtag(self, tag):
        if self.ignorelevel > 0:
            self.ignorelevel -= 1
            if self.ignorelevel > 0:
                return
        stdout.write('</' + tag + '>')
    def handle_data(self, data):
        stdout.write(data)
    def handle_charref(self, name):
        stdout.write('&#' + name + ';')
    def handle_entityref(self, name):
        stdout.write('&' + name + ';')
    def handle_comment(self, data):
        stdout.write('<!-- ' + data + ' -->')
    def handle_decl(self, data):
        stdout.write('<!' + data + '>')
    def handle_pi(self, data):
        stdout.write('<?' + data + '>')

0

据我所知,Sam的答案应该可以很好地完成所需的工作,但最好确保任何剩余的<>字符都被替换为&lt;和&gt;,以防止误用/无效的HTML。

这种方法的优点是它可以接受极其畸形的HTML引用/标签。BeautifulSoup也可以很好地处理畸形标签,但html5lib、sgmllib和htmllib可能会在无效代码上出现问题,如果我没记错的话,有些更多。

以下代码还验证了& HTML引用:

import re
from htmlentitydefs import name2codepoint, codepoint2name

S = '1234567890ABCDEF'
DHex = {}
for i in S:
    DHex[i.lower()] = None
    DHex[i.upper()] = None

def IsHex(S):
    if not S: return False
    for i in S: 
        if i not in DHex:
            return False
    return True

def UnEscape(S, LReEscape=None):
    # Converts HTML character references into a unicode string to allow manipulation
    #
    # If LUnEscape is provided, then the positions of the escaped characters will be 
    # added to allow turning the result back into HTML with ReEscape below, validating 
    # the references and escaping all the rest
    # 
    # This is needed to prevent browsers from stripping out e.g. &#32; (spaces) etc
    re = LReEscape != None

    LRtn = []
    L = S.split('&')
    xx = 0
    yy = 0
    for iS in L:
        if xx:
            LSplit = iS.split(';')
            if LSplit[0].lower() in name2codepoint:
                # A character reference, e.g. '&amp;'
                a = unichr(name2codepoint[LSplit[0].lower()])
                LRtn.append(a+';'.join(LSplit[1:]))
                if re: LReEscape.append((yy, a))

            elif LSplit[0] and LSplit[0][0] == '#' and LSplit[0][1:].isdigit():
                # A character number e.g. '&#52;'
                a = unichr(int(LSplit[0][1:]))
                LRtn.append(a+';'.join(LSplit[1:]))
                if re: LReEscape.append((yy, a))

            elif LSplit[0] and LSplit[0][0] == '#' and LSplit[0][1:2].lower() == 'x' and IsHex(LSplit[0][2:]):
                # A hexadecimal encoded character
                a = unichr(int(LSplit[0][2:].lower(), 16)) # Hex -> base 16
                LRtn.append(a+';'.join(LSplit[1:]))
                if re: LReEscape.append((yy, a))

            else: LRtn.append('&%s' % ';'.join(LSplit))
        else: LRtn.append(iS)
        xx += 1
        yy += len(LRtn[-1])
    return ''.join(LRtn)

def ReEscape(LReEscape, S, EscFn):
    # Re-escapes the output of UnEscape to HTML, ensuring e.g. &#32; 
    # is turned back again and isn't stripped at a browser level
    L = []
    prev = 0
    for x, c in LReEscape:
        if x != prev:
            L.append(EscFn(S[prev:x]))

        o = ord(c)
        if o in codepoint2name:
            L.append('&%s;' % codepoint2name[o])
        else: L.append('&#%s;' % o)
        prev = x+len(c)
    L.append(EscFn(S[prev:]))
    return ''.join(L)

def escape(value):
    # Escape left over <>& tags
    value = value.replace('&', '&amp;')
    value = value.replace('>', '&gt;')
    value = value.replace('<', '&lt;')
    return value

def strip_tags(value):
    # Strip HTML tags
    value = re.sub(r'<[^>]*?>', '', value)
    print 'No Tags:', value

    # Validate & references
    LReEscape = []
    value = UnEscape(value, LReEscape)
    value = ReEscape(LReEscape, value, EscFn=escape)
    print 'References Validated:', value
    return value

if __name__ == '__main__':
    # Outputs:
    #  No Tags: this is test  bold text  normal text >< &blah &amp; &amp
    #  References Validated: this is test  bold text  normal text &gt;&lt; &amp;blah &amp; &amp;
    strip_tags('this is test <b> bold text </b> normal text >< &blah &amp; &amp')

0

0

这是我项目中提取的工作代码Supybot,因此经过了相当充分的测试:

class HtmlToText(sgmllib.SGMLParser):
    """从c.l.p.的一些eff-bot代码中提取。"""
    entitydefs = htmlentitydefs.entitydefs.copy()
    entitydefs['nbsp'] = ' '
    def __init__(self, tagReplace=' '):
        self.data = []
        self.tagReplace = tagReplace
        sgmllib.SGMLParser.__init__(self)
def unknown_starttag(self, tag, attr): self.data.append(self.tagReplace)
def unknown_endtag(self, tag): self.data.append(self.tagReplace)
def handle_data(self, data): self.data.append(data)
def getText(self): text = ''.join(self.data).strip() return normalizeWhitespace(text)
def htmlToText(s, tagReplace=' '): """将HTML转换为文本。tagReplace是用于替换HTML标记的字符串。 """ x = HtmlToText(tagReplace) x.feed(s) return x.getText()

正如文档字符串所述,它起源于Fredrik Lundh而不是我。就像他们说的那样,伟大的作者会窃取 :)


0
使用webob.exc模块:
from webob.exc import strip_tags

然后使用它:

print strip_tags('a<br/>b')
>> ab

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