从Python中的字符串中删除HTML

dir*_*ion 254 html python

from mechanize import Browser
br = Browser()
br.open('http://somewebpage')
html = br.response().readlines()
for line in html:
  print line
Run Code Online (Sandbox Code Playgroud)

在HTML文件中打印一行时,我试图找到一种方法来只显示每个HTML元素的内容而不是格式本身.如果找到'<a href="whatever.com">some text</a>',它只会打印"一些文字",'<b>hello</b>'打印"你好"等等.怎么会这样做呢?

小智 397

我总是使用这个函数去除HTML标签,因为它只需要Python stdlib:

在Python 2上

from HTMLParser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
    def handle_data(self, d):
        self.fed.append(d)
    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()
Run Code Online (Sandbox Code Playgroud)

对于Python 3

from html.parser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.strict = False
        self.convert_charrefs= True
        self.fed = []
    def handle_data(self, d):
        self.fed.append(d)
    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()
Run Code Online (Sandbox Code Playgroud)

注意:这仅适用于3.1.对于3.2或更高版本,您需要调用父类的init函数.请参阅在Python 3.2中使用HTMLParser

  • 请注意,这会剥离HTML实体(例如`&amp;`)以及标签. (45认同)
  • @surya我相信你已经[看过这个](http://www.codinghorror.com/blog/2009/11/parsing-html-the-cthulhu-way.html) (30认同)
  • 为了保持html实体(转换为unicode),我在strip_tags函数的开头添加了两行:`parser = HTMLParser()`和`html = parser.unescape(html)`. (10认同)
  • 谢谢你的回答.对于那些使用较新版本的Python(3.2+)的人来说,有一点需要注意的是你需要调用父类的`__init__`函数.请参见此处:http://stackoverflow.com/questions/11061058/using-htmlparser-in-python-3-2. (8认同)
  • 两年+之后,面临同样的问题,这是一个更优雅的解决方案.只有改变我做的是将self.fed作为列表返回,而不是加入它,所以我可以逐步完成元素内容. (3认同)
  • 我的一位同事发现了 `&lt;&lt;sc&lt;script&gt;script&gt;alert(1)&lt;&lt;/sc&lt;/script&gt;/script&gt;`。如果你通过这段代码传递它,输出将是`&lt;script&gt;alert(1)&lt;/script&gt;`。可以肯定的是,我用 `html.escape()` 包装了您的解决方案,以确保输出中没有留下任何标签。 (3认同)
  • 该代码删除了我输入文本中的 &amp;s,这不是我想要它做的。我只希望它删除 html 标签。 (2认同)
  • 这对我根本不起作用。 (2认同)
  • 如何在 HTML 元素之间保留空格?防止“&lt;p&gt;...&lt;/p&gt;&lt;p&gt;...&lt;/p&gt;”被混在一起? (2认同)

mmm*_*reg 149

我没有想过它会错过的案例,但你可以做一个简单的正则表达式:

re.sub('<[^<]+?>', '', text)
Run Code Online (Sandbox Code Playgroud)

对于那些不了解正则表达式的人,这将搜索一个字符串<...>,其中内部内容由一个或多个(+)字符组成,而不是a <.这?意味着它将匹配它可以找到的最小字符串.例如给定<p>Hello</p>,它将匹配<'p></p>单独与?.没有它,它将匹配整个字符串<..Hello..>.

如果非标签<出现在html(例如2 < 3)中,则&...无论如何都应将其写为转义序列,因此^<可能没有必要.

  • 人们仍然可以用这样的方法欺骗这个方法:<script <script >> alert("嗨!")<</script>/script> (35认同)
  • 不要这样做!正如@Julio Garcia所说,这不安全! (19认同)
  • 人们,不要混淆HTML剥离和HTML清理.是的,对于破坏或恶意输入,此答案可能会生成包含HTML标记的输出.它仍然是剥离HTML标记的完美有效方法._However_,剥离HTML标记不是正确HTML清理的有效替代.规则并不难:_Any time_你将一个纯文本字符串插入到HTML输出中,你应该_always_ HTML转义它(使用`cgi.escape(s,True)`),即使你"知道"它不是包含HTML(例如,因为您剥离了HTML内容).但是,这不是OP所询问的. (16认同)
  • 这几乎就是Django的[strip_tags](http://code.djangoproject.com/browser/django/trunk/django/utils/html.py)所做的. (10认同)
  • 请注意,这会使输出中的HTML实体(例如`&amp;`)保持不变. (9认同)
  • @rescdsk你可以使用该正则表达式去除"非恶意"标签,然后将`<`编码为`&lt;`以确保它实际上是安全的. (3认同)
  • 你需要`import re` (2认同)

Ami*_*ini 57

为什么你们所有人都这么做?您可以使用BeautifulSoup get_text()功能.

from bs4 import BeautifulSoup

html_str = '''
<td><a href="http://www.fakewebsite.com">Please can you strip me?</a>
<br/><a href="http://www.fakewebsite.com">I am waiting....</a>
</td>
'''
soup = BeautifulSoup(html_str)

print(soup.get_text()) 
#or via attribute of Soup Object: print(soup.text)
Run Code Online (Sandbox Code Playgroud)


res*_*dsk 31

精简版!

import re, cgi
tag_re = re.compile(r'(<!--.*?-->|<[^>]*>)')

# Remove well-formed tags, fixing mistakes by legitimate users
no_tags = tag_re.sub('', user_input)

# Clean up anything else by escaping
ready_for_web = cgi.escape(no_tags)
Run Code Online (Sandbox Code Playgroud)

正则表达式来源:MarkupSafe.他们的版本也处理HTML实体,而这个快速版本没有.

为什么我不能剥离标签并留下它?

保持人们远离<i>italicizing</i>事物是一回事,而不会让人i流连忘返.但是接受任意输入并使其完全无害是另一回事.此页面上的大多数技术都会保留未封闭的comments(<!--)和不包含tags(blah <<<><blah)的尖括号等内容.HTMLParser版本甚至可以保留完整的标签,如果它们在未公开的评论中.

如果您的模板是{{ firstname }} {{ lastname }}什么? firstname = '<a'并将lastname = 'href="http://evil.com/">'通过此页面上的每个标记剥离器(@Medeiros除外)通过,因为它们不是自己的完整标记.剥离普通的HTML标签是不够的.

Django是strip_tags这个问题的最佳答案的改进版(见下一个标题),给出了以下警告:

绝对不保证所得到的字符串是HTML安全的.因此,永远不要在strip_tags没有首先逃避呼叫的情况下标记呼叫结果,例如使用escape().

听从他们的建议!

要使用HTMLParser剥离标记,您必须多次运行它.

很容易绕过这个问题的最佳答案.

看看这个字符串(来源和讨论):

<img<!-- --> src=x onerror=alert(1);//><!-- -->
Run Code Online (Sandbox Code Playgroud)

HTMLParser第一次看到它时,它无法分辨出它<img...>是一个标签.它看起来很破碎,所以HTMLParser并没有摆脱它.它只取出<!-- comments -->,留下你

<img src=x onerror=alert(1);//>
Run Code Online (Sandbox Code Playgroud)

这个问题是在2014年3月向Django项目披露的.他们的旧版strip_tags基本上与这个问题的最佳答案相同. 他们的新版本基本上在循环中运行它,直到再次运行它不会更改字符串:

# _strip_once runs HTMLParser once, pulling out just the text of all the nodes.

def strip_tags(value):
    """Returns the given HTML with all tags stripped."""
    # Note: in typical case this loop executes _strip_once once. Loop condition
    # is redundant, but helps to reduce number of executions of _strip_once.
    while '<' in value and '>' in value:
        new_value = _strip_once(value)
        if len(new_value) >= len(value):
            # _strip_once was not able to detect more tags
            break
        value = new_value
    return value
Run Code Online (Sandbox Code Playgroud)

当然,如果你总是逃避结果,这一切都不是问题strip_tags().

2015年3月19日更新:Django版本在1.4.20,1.6.11,1.7.7和1.8c1之前有一个错误.这些版本可以在strip_tags()函数中进入无限循环.固定版本在上面复制. 更多细节在这里.

复制或使用的好东西

我的示例代码不处理HTML实体 - Django和MarkupSafe打包版本.

我的示例代码是从优秀的MarkupSafe库中提取的,用于防止跨站点脚本编写.它方便快捷(C加速到其原生Python版本).它包含在Google App Engine中,并由Jinja2(2.7及更高版本),Mako,Pylons等使用.它可以轻松地与Django 1.7的Django模板一起使用.

Django的strip_tags和最新版本的其他html实用程序都很好,但我发现它们不如MarkupSafe方便.它们非常独立,您可以从此文件中复制所需内容.

如果您需要剥离几乎所有标签,Bleach库就是好的.你可以让它强制执行诸如"我的用户可以使事情变为斜体,但却无法制作iframe"的规则.

了解标签剥离器的属性!对它进行模糊测试! 这是我用来为这个答案做研究的代码.

懦弱的注意事项 - 问题本身就是打印到控制台,但这是"python strip html from string"的谷歌搜索结果,所以这就是为什么这个答案是关于网络的99%.


Sør*_*org 29

我需要一种方法来剥离标签并将 HTML实体解码为纯文本.以下解决方案基于Eloff的答案(我无法使用,因为它剥离了实体).

from HTMLParser import HTMLParser
import htmlentitydefs

class HTMLTextExtractor(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.result = [ ]

    def handle_data(self, d):
        self.result.append(d)

    def handle_charref(self, number):
        codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number)
        self.result.append(unichr(codepoint))

    def handle_entityref(self, name):
        codepoint = htmlentitydefs.name2codepoint[name]
        self.result.append(unichr(codepoint))

    def get_text(self):
        return u''.join(self.result)

def html_to_text(html):
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()
Run Code Online (Sandbox Code Playgroud)

快速测试:

html = u'<a href="#">Demo <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>'
print repr(html_to_text(html))
Run Code Online (Sandbox Code Playgroud)

结果:

u'Demo (\xac \u0394\u03b7\u03bc\u03ce)'
Run Code Online (Sandbox Code Playgroud)

错误处理:

  • 无效的HTML结构可能会导致HTMLParseError.
  • 无效的命名HTML实体(例如&#apos;,在XML和XHTML中有效,但不是纯HTML)将导致ValueError异常.
  • 指定Python可接受的Unicode范围之外的代码点的数字HTML实体(例如,在某些系统上,基本多语言平面之外的字符)将导致ValueError异常.

安全说明:不要将HTML剥离(将HTML转换为纯文本)与HTML清理(将纯文本转换为HTML)混淆.此答案将删除HTML并将实体解码为纯文本 - 这不会使结果在HTML上下文中安全使用.

示例:&lt;script&gt;alert("Hello");&lt;/script&gt;将转换为<script>alert("Hello");</script>,这是100%正确的行为,但如果生成的纯文本按原样插入HTML页面,显然是不够的.

规则并不难:每当您将纯文本字符串插入HTML输出时,即使您"知道"它不包含HTML(例如,因为您剥离了HTML内容),您也应始终将其转义(使用cgi.escape(s, True))HTML .

(但是,OP询问是否将结果打印到控制台,在这种情况下不需要HTML转义.)

Python 3.4+版本:(带doctest!)

import html.parser

class HTMLTextExtractor(html.parser.HTMLParser):
    def __init__(self):
        super(HTMLTextExtractor, self).__init__()
        self.result = [ ]

    def handle_data(self, d):
        self.result.append(d)

    def get_text(self):
        return ''.join(self.result)

def html_to_text(html):
    """Converts HTML to plain text (stripping tags and converting entities).
    >>> html_to_text('<a href="#">Demo<!--...--> <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>')
    'Demo (\xac \u0394\u03b7\u03bc\u03ce)'

    "Plain text" doesn't mean result can safely be used as-is in HTML.
    >>> html_to_text('&lt;script&gt;alert("Hello");&lt;/script&gt;')
    '<script>alert("Hello");</script>'

    Always use html.escape to sanitize text before using in an HTML context!

    HTMLParser will do its best to make sense of invalid HTML.
    >>> html_to_text('x < y &lt z <!--b')
    'x < y < z '

    Unrecognized named entities are included as-is. '&apos;' is recognized,
    despite being XML only.
    >>> html_to_text('&nosuchentity; &apos; ')
    "&nosuchentity; ' "
    """
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()
Run Code Online (Sandbox Code Playgroud)

请注意,HTMLParser在Python 3中得到了改进(意味着代码更少,错误处理更好).


Med*_*ros 18

有一个简单的方法:

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
Run Code Online (Sandbox Code Playgroud)

这个想法在这里解释:http://youtu.be/2tu9LTDujbw

你可以在这里看到它:http://youtu.be/HPkNPcYed9M?t = 35s

PS - 如果你对这个课程感兴趣(关于使用python进行智能调试),我会给你一个链接:http://www.udacity.com/overview/Course/cs259/CourseRev/1.免费!

别客气!:)

  • 我想知道为什么这个答案只是被低估了.这是一种无需任何lib即可解决问题的简单方法.只是纯python,它的工作原理如链接所示. (2认同)
  • 可能人们更喜欢libs给他们安全.我测试了你的代码并通过了,我总是喜欢我理解的小代码而不是使用lib,并且假设在弹出错误之前它没问题.对我来说,这就是我所寻找的,再次感谢.关于downvotes,不要陷入那种心态.这里的人应该关心质量而不是投票.最近SO已成为每个人都想要点而不是知识的地方. (2认同)
  • 此解决方案的问题是错误处理.例如,如果你给`<b class ="o'> x </ b>`作为输入函数输出`x`.但实际上这个输入是无效的.我认为这就是人们喜欢libs的原因. (2认同)
  • 简单、Pythonic 并且似乎比所讨论的任何其他方法效果好或更好。它可能不适用于某些格式错误的 HTML,但无法克服这一点。 (2认同)

Rob*_*ert 16

如果您需要保留HTML实体(即&amp;),我将" handle_entityref "方法添加到Eloff的答案中.

from HTMLParser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
    def handle_data(self, d):
        self.fed.append(d)
    def handle_entityref(self, name):
        self.fed.append('&%s;' % name)
    def get_data(self):
        return ''.join(self.fed)

def html_to_text(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()
Run Code Online (Sandbox Code Playgroud)


Rob*_*nse 16

这是一个简单的解决方案,它基于惊人的快速lxml库去除 HTML 标签并解码 HTML 实体:

from lxml import html

def strip_html(s):
    return str(html.fromstring(s).text_content())

strip_html('Ein <a href="">sch&ouml;ner</a> Text.')  # Output: Ein schöner Text.
Run Code Online (Sandbox Code Playgroud)

  • 截至 2020 年,这是条带化 HTML 内容最快、最好的方法。加上处理解码的好处。非常适合语言检测! (4认同)
  • `text_content()` 返回 `lxml.etree._ElementUnicodeResult` 因此您可能必须先将其转换为字符串 (2认同)

Vas*_*lis 12

如果要删除所有HTML标记,我发现的最简单方法是使用BeautifulSoup:

from bs4 import BeautifulSoup  # Or from BeautifulSoup import BeautifulSoup

def stripHtmlTags(htmlTxt):
    if htmlTxt is None:
            return None
        else:
            return ''.join(BeautifulSoup(htmlTxt).findAll(text=True)) 
Run Code Online (Sandbox Code Playgroud)

我尝试了接受的答案的代码,但我得到了"RuntimeError:超出最大递归深度",这在上面的代码块中没有发生.


ccp*_*zza 9

一个基于lxml.html的解决方案(lxml是一个本机库,因此比任何纯python解决方案都快得多).

from lxml import html
from lxml.html.clean import clean_html

tree = html.fromstring("""<span class="item-summary">
                            Detailed answers to any questions you might have
                        </span>""")

print(clean_html(tree).strip())

# >>> Detailed answers to any questions you might have
Run Code Online (Sandbox Code Playgroud)

另请参阅http://lxml.de/lxmlhtml.html#cleaning-up-html以了解lxml.cleaner的确切内容.

如果在转换为文本之前需要更多地控制清理的内容,那么您可能希望通过在构造函数中传递所需的选项来显式使用lxml清除程序,例如:

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')
                  )
sanitized_html = cleaner.clean_html(unsafe_html)
Run Code Online (Sandbox Code Playgroud)


run*_*kid 7

美丽的汤包立即为您做到这一点.

from bs4 import BeautifulSoup

soup = BeautifulSoup(html)
text = soup.get_text()
print(text)
Run Code Online (Sandbox Code Playgroud)

  • **来自评论队列:**我可以请求您在答案中添加更多背景信息.仅代码的答案很难理解.如果您可以在帖子中添加更多信息,它将帮助提问者和未来的读者. (3认同)