【问题标题】:Strip HTML from strings in Python从 Python 中的字符串中去除 HTML
【发布时间】:2010-10-19 16:16:13
【问题描述】:
from mechanize import Browser
br = Browser()
br.open('http://somewebpage')
html = br.response().readlines()
for line in html:
  print line

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

【问题讨论】:

  • 一个重要的考虑是如何处理 HTML 实体(例如&)。您可以 1) 将它们与标签一起删除(通常不需要,并且因为它们等同于纯文本,因此是不必要的),2) 保持它们不变(如果剥离的文本直接回到 HTML 上下文中,这是一个合适的解决方案)或 3 ) 将它们解码为纯文本(如果剥离的文本进入数据库或其他非 HTML 上下文,或者如果您的 Web 框架自动为您执行 HTML 文本转义)。
  • @SørenLøvborg 第 2 点):stackoverflow.com/questions/753052/…
  • 这里的最佳答案,在 2014 年 3 月之前一直被 Django 项目使用,has been found 对跨站点脚本不安全 - 请参阅该链接以获取使其通过的示例。我建议使用 Bleach.clean()、Markupsafe 的 striptags 或 RECENT Django 的 strip_tags。

标签: python html


【解决方案1】:

我就是这样做的,但我不知道我在做什么。我通过去除 HTML 标记从 HTML 表中获取数据。

这接受字符串“name”并返回不带 HTML 标记的字符串“name1”。

x = 0
anglebrackets = 0
name1 = ""
while x < len(name):
    
    if name[x] == "<":
        anglebrackets = anglebrackets + 1
    if name[x] == ">":
        anglebrackets = anglebrackets - 1
    if anglebrackets == 0:
        if name[x] != ">":
            name1 = name1 + name[x]
    x = x + 1

【讨论】:

    【解决方案2】:

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

    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 '
    
        Named entities are handled as per HTML 5.
        >>> html_to_text('&nosuchentity; &apos; ')
        "&nosuchentity; ' "
        """
        s = HTMLTextExtractor()
        s.feed(html)
        return s.get_text()
    

    快速测试:

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

    结果:

    'Demo (¬ Δημώ)'
    

    安全注意事项:不要将 HTML stripping(将 HTML 转换为纯文本)与 HTML 清理(将纯文本转换为 HTML)混淆。此答案将删除 HTML 并将实体解码为纯文本 - 这不会使结果在 HTML 上下文中安全使用。

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

    规则并不难:任何时候你在 HTML 输出中插入一个纯文本字符串,总是 HTML 转义它(使用html.escape(s)),即使你“知道”它不包含 HTML(例如,因为您剥离了 HTML 内容)。

    但是,OP 询问是否将结果打印到 控制台,在这种情况下不需要 HTML 转义。相反,您可能想要去除 ASCII 控制字符,因为它们会触发不需要的行为(尤其是在 Unix 系统上):

    import re
    text = html_to_text(untrusted_html_input)
    clean_text = re.sub(r'[\0-\x1f\x7f]+', '', text)
    # Alternatively, if you want to allow newlines:
    # clean_text = re.sub(r'[\0-\x09\x0b-\x1f\x7f]+', '', text)
    print(clean_text)
    

    【讨论】:

      【解决方案3】:

      基于lxml.html 的解决方案(lxml 是本机库,比纯 python 解决方案性能更高)。

      要安装lxml 模块,请使用pip install lxml

      删除所有标签

      from lxml import html
      
      
      ## from file-like object or URL
      tree = html.parse(file_like_object_or_url)
      
      ## from string
      tree = html.fromstring('safe <script>unsafe</script> safe')
      
      print(tree.text_content().strip())
      
      ### OUTPUT: 'safe unsafe safe'
      
      

      使用预清理 HTML 删除所有标签(删除一些标签)

      from lxml import html
      from lxml.html.clean import clean_html
      
      tree = html.fromstring("""<script>dangerous</script><span class="item-summary">
                                  Detailed answers to any questions you might have
                              </span>""")
      
      ## text only
      print(clean_html(tree).text_content().strip())
      
      ### OUTPUT: 'Detailed answers to any questions you might have'
      

      另请参阅http://lxml.de/lxmlhtml.html#cleaning-up-html,了解 lxml.cleaner 的具体作用。

      如果您需要在转换为文本之前更好地控制要清理的确切内容,那么您可能希望通过在构造函数中传递 options you want 来显式使用 lxml Cleaner,例如:

      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)
      

      如果您需要更多地控制纯文本的生成方式,那么您可以使用text_content() 而不是lxml.etree.tostring

      plain_bytes = tostring(tree, method='text', encoding='utf-8')
      print(plain.decode('utf-8'))
      
      

      【讨论】:

      • 我得到 AttributeError: 'HtmlElement' object has no attribute 'strip'
      • @aris: 那是旧版本的python和lxml,更新了。
      • 是否可以选择用空字符串替换已删除的标签,例如“”?
      【解决方案4】:

      有一个简单的方法:

      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 进行智能调试)我给你一个链接:http://www.udacity.com/overview/Course/cs259/CourseRev/1。免费!

      不客气! :)

      【讨论】:

      • 我想知道为什么这个答案被否决了。这是一种无需任何库即可解决问题的简单方法。只是纯python,它的工作原理如链接所示。
      • 可能人们更喜欢 libs 来保证他们的安全。我测试了你的代码并通过了,我总是更喜欢我理解的小代码,而不是使用 lib 并假设在出现错误之前没问题。对我来说,这就是我一直在寻找的东西,再次感谢。关于否决票,不要陷入那种心态。这里的人应该关心质量而不是选票。最近 SO 变成了一个人人都想要积分而不是知识的地方。
      • 这个解决方案的问题是错误处理。例如,如果您将&lt;b class="o'&gt;x&lt;/b&gt; 作为输入函数输出x。但实际上这个输入是无效的。我认为这就是人们喜欢 libs 的原因。
      • 它也适用于该输入。刚测试过。只要意识到在这些库中你会发现类似的代码。我知道,这不是很pythonic。看起来像 C 或 Java 代码。我认为它很高效,可以轻松移植到另一种语言。
      • 简单,Pythonic 并且似乎比讨论的任何其他方法都好或更好。它可能不适用于某些格式不正确的 HTML,但无法克服。
      【解决方案5】:

      2020 年更新

      使用Mozilla Bleach library,它确实可以让您自定义要保留的标签和要保留的属性,还可以根据值过滤掉属性

      这里有2个案例来说明

      1) 不允许任何 HTML 标签或属性

      以原始文本为例

      raw_text = """
      <p><img width="696" height="392" src="https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-768x432.jpg" class="attachment-medium_large size-medium_large wp-post-image" alt="Ethereum Classic 51% Attack: Okex Crypto Exchange Suffers $5.6 Million Loss, Contemplates Delisting ETC" style="float:left; margin:0 15px 15px 0;" srcset="https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-768x432.jpg 768w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-300x169.jpg 300w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-1024x576.jpg 1024w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-696x392.jpg 696w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-1068x601.jpg 1068w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-747x420.jpg 747w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-190x107.jpg 190w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-380x214.jpg 380w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-760x428.jpg 760w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc.jpg 1280w" sizes="(max-width: 696px) 100vw, 696px" />Cryptocurrency exchange Okex reveals it suffered the $5.6 million loss as a result of the double-spend carried out by the attacker(s) in Ethereum Classic 51% attack. Okex says it fully absorbed the loss as per its user-protection policy while insisting that the attack did not cause any loss to the platform&#8217;s users. Also as part [&#8230;]</p>
      <p>The post <a rel="nofollow" href="https://news.bitcoin.com/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc/">Ethereum Classic 51% Attack: Okex Crypto Exchange Suffers $5.6 Million Loss, Contemplates Delisting ETC</a> appeared first on <a rel="nofollow" href="https://news.bitcoin.com">Bitcoin News</a>.</p> 
      """
      

      2) 从原始文本中删除所有 HTML 标记和属性

      # DO NOT ALLOW any tags or any attributes
      from bleach.sanitizer import Cleaner
      cleaner = Cleaner(tags=[], attributes={}, styles=[], protocols=[], strip=True, strip_comments=True, filters=None)
      print(cleaner.clean(raw_text))
      

      输出

      Cryptocurrency exchange Okex reveals it suffered the $5.6 million loss as a result of the double-spend carried out by the attacker(s) in Ethereum Classic 51% attack. Okex says it fully absorbed the loss as per its user-protection policy while insisting that the attack did not cause any loss to the platform&#8217;s users. Also as part [&#8230;]
      The post Ethereum Classic 51% Attack: Okex Crypto Exchange Suffers $5.6 Million Loss, Contemplates Delisting ETC appeared first on Bitcoin News. 
      

      3 只允许带有 srcset 属性的 img 标签

      from bleach.sanitizer import Cleaner
      # ALLOW ONLY img tags with src attribute
      cleaner = Cleaner(tags=['img'], attributes={'img': ['srcset']}, styles=[], protocols=[], strip=True, strip_comments=True, filters=None)
      print(cleaner.clean(raw_text))
      

      输出

      <img srcset="https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-768x432.jpg 768w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-300x169.jpg 300w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-1024x576.jpg 1024w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-696x392.jpg 696w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-1068x601.jpg 1068w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-747x420.jpg 747w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-190x107.jpg 190w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-380x214.jpg 380w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc-760x428.jpg 760w, https://news.bitcoin.com/wp-content/uploads/2020/08/ethereum-classic-51-attack-okex-crypto-exchange-suffers-5-6-million-loss-contemplates-delisting-etc.jpg 1280w">Cryptocurrency exchange Okex reveals it suffered the $5.6 million loss as a result of the double-spend carried out by the attacker(s) in Ethereum Classic 51% attack. Okex says it fully absorbed the loss as per its user-protection policy while insisting that the attack did not cause any loss to the platform&#8217;s users. Also as part [&#8230;]
      The post Ethereum Classic 51% Attack: Okex Crypto Exchange Suffers $5.6 Million Loss, Contemplates Delisting ETC appeared first on Bitcoin News. 
      

      【讨论】:

        【解决方案6】:

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

        建议明确指定parser,例如BeautifulSoup(html_str, features="html.parser"),以使输出可重现。

        【讨论】:

        • 现在强制设置解析器
        【解决方案7】:

        我一直使用这个函数来去除 HTML 标签,因为它只需要 Python 标准库:

        对于 Python 3:

        from io import StringIO
        from html.parser import HTMLParser
        
        class MLStripper(HTMLParser):
            def __init__(self):
                super().__init__()
                self.reset()
                self.strict = False
                self.convert_charrefs= True
                self.text = StringIO()
            def handle_data(self, d):
                self.text.write(d)
            def get_data(self):
                return self.text.getvalue()
        
        def strip_tags(html):
            s = MLStripper()
            s.feed(html)
            return s.get_data()
        

        对于 Python 2:

        from HTMLParser import HTMLParser
        from StringIO import StringIO
        
        class MLStripper(HTMLParser):
            def __init__(self):
                self.reset()
                self.text = StringIO()
            def handle_data(self, d):
                self.text.write(d)
            def get_data(self):
                return self.text.getvalue()
        
        def strip_tags(html):
            s = MLStripper()
            s.feed(html)
            return s.get_data()
        

        【讨论】:

        • 两年后,面临同样的问题,这是一个更优雅的解决方案。我所做的唯一更改是将 self.fed 作为列表返回,而不是加入它,这样我就可以逐步浏览元素内容。
        • 请注意,这会去除 HTML 实体(例如 &amp;amp;)以及标签。
        • @surya 我确定你已经seen this
        • 感谢您的出色回答。对于那些使用较新版本的 Python(3.2+)的人来说,需要注意的一件事是,您需要调用父类的 __init__ 函数。见这里:stackoverflow.com/questions/11061058/….
        • 为了保留 html 实体(转换为 unicode),我在 strip_tags 函数的开头添加了两行:parser = HTMLParser()html = parser.unescape(html)
        【解决方案8】:

        这是一个简单的解决方案,它基于惊人的速度 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.
        

        【讨论】:

        • 截至 2020 年,这是对 HTML 内容进行条带化的最快和最佳方式。加上处理解码的好处。非常适合语言检测!
        • text_content() 返回lxml.etree._ElementUnicodeResult 所以你可能必须先将其转换为字符串
        • @Suzana 好点。对于+ 和索引[] 之类的字符串操作,它似乎会自动转换为str。无论如何,添加了一个演员阵容。
        【解决方案9】:

        您可以使用不同的 HTML 解析器(like lxmlBeautiful Soup) - 一个提供仅提取文本的功能的解析器。或者,您可以在去除标签的行字符串上运行正则表达式。请参阅Python docs 了解更多信息。

        【讨论】:

        • amk 链接已失效。有替代品吗?
        • Python 网站现在有很好的操作方法,这里是正则表达式操作方法:docs.python.org/howto/regex
        • 在 lxml 中:lxml.html.fromstring(s).text_content()
        • Bluu 的 lxml 示例将 HTML 实体(例如 &amp;amp;)解码为文本。
        【解决方案10】:
        # This is a regex solution.
        import re
        def removeHtml(html):
          if not html: return html
          # Remove comments first
          innerText = re.compile('<!--[\s\S]*?-->').sub('',html)
          while innerText.find('>')>=0: # Loop through nested Tags
            text = re.compile('<[^<>]+?>').sub('',innerText)
            if text == innerText:
              break
            innerText = text
        
          return innerText.strip()
        

        【讨论】:

          【解决方案11】:

          简单的代码!。这将删除其中的所有类型的标签和内容。

          def rm(s):
              start=False
              end=False
              s=' '+s
              for i in range(len(s)-1):
                  if i<len(s):
                      if start!=False:
                          if s[i]=='>':
                              end=i
                              s=s[:start]+s[end+1:]
                              start=end=False
                      else:
                          if s[i]=='<':
                              start=i
              if s.count('<')>0:
                  self.rm(s)
              else:
                  s=s.replace('&nbsp;', ' ')
                  return s
          

          但如果文本中包含 符号,则不会给出完整的结果。

          【讨论】:

            【解决方案12】:

            这是我对 python 3 的解决方案。

            import html
            import re
            
            def html_to_txt(html_text):
                ## unescape html
                txt = html.unescape(html_text)
                tags = re.findall("<[^>]+>",txt)
                print("found tags: ")
                print(tags)
                for tag in tags:
                    txt=txt.replace(tag,'')
                return txt
            

            不确定它是否完美,但解决了我的用例并且看起来很简单。

            【讨论】:

              【解决方案13】:

              这是一个类似于当前接受的答案 (https://stackoverflow.com/a/925630/95989) 的解决方案,只是它直接使用内部 HTMLParser 类(即没有子类化),从而使其更加简洁:

              def strip_html(文本): 部分= [] 解析器 = HTMLParser() parser.handle_data = part.append parser.feed(文本) 返回''.join(部分)

              【讨论】:

                【解决方案14】:

                如果你想去除所有 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)) 
                

                我尝试了接受答案的代码,但收到“RuntimeError: maximum recursion depth exceeded”,上面的代码块没有发生这种情况。

                【讨论】:

                • 我刚试过你的方法,因为它看起来更干净,效果很好,有点……它没有去除输入标签!
                • 我发现BeautifulSoup 的一个简单应用存在空格问题:''.join(BeautifulSoup('&lt;em&gt;he&lt;/em&gt;llo&lt;br&gt;world').find_all(text=True))。这里的输出是“helloworld”,而您可能希望它是“hello world”。 ' '.join(BeautifulSoup('&lt;em&gt;he&lt;/em&gt;llo&lt;br&gt;world').find_all(text=True)) 无济于事,因为它变成了“he llo world”。
                • @kustomrtr ,对不起我的无知,我在 self 参数中添加了什么? NameError: name 'self' 未定义
                • @Ian_De_Oliveira 你可以删除它,我认为它在一个类中,但它不是必需的。我还编辑了答案以将其删除
                • @Ian_De_Oliveira 你可以删除它,我认为它在一个类中,但它不是必需的。我还编辑了答案以将其删除
                【解决方案15】:

                对于一个项目,我需要去除 HTML,还需要去除 css 和 js。因此,我对 Eloffs 的回答做了一个变体:

                class MLStripper(HTMLParser):
                    def __init__(self):
                        self.reset()
                        self.strict = False
                        self.convert_charrefs= True
                        self.fed = []
                        self.css = False
                    def handle_starttag(self, tag, attrs):
                        if tag == "style" or tag=="script":
                            self.css = True
                    def handle_endtag(self, tag):
                        if tag=="style" or tag=="script":
                            self.css=False
                    def handle_data(self, d):
                        if not self.css:
                            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()
                

                【讨论】:

                  【解决方案16】:

                  使用 BeautifulSoup、html2text 或来自@Eloff 的代码,大多数时候,它仍然保留一些 html 元素、javascript 代码...

                  因此您可以使用这些库的组合并删除降价格式(Python 3):

                  import re
                  import html2text
                  from bs4 import BeautifulSoup
                  def html2Text(html):
                      def removeMarkdown(text):
                          for current in ["^[ #*]{2,30}", "^[ ]{0,30}\d\\\.", "^[ ]{0,30}\d\."]:
                              markdown = re.compile(current, flags=re.MULTILINE)
                              text = markdown.sub(" ", text)
                          return text
                      def removeAngular(text):
                          angular = re.compile("[{][|].{2,40}[|][}]|[{][*].{2,40}[*][}]|[{][{].{2,40}[}][}]|\[\[.{2,40}\]\]")
                          text = angular.sub(" ", text)
                          return text
                      h = html2text.HTML2Text()
                      h.images_to_alt = True
                      h.ignore_links = True
                      h.ignore_emphasis = False
                      h.skip_internal_links = True
                      text = h.handle(html)
                      soup = BeautifulSoup(text, "html.parser")
                      text = soup.text
                      text = removeAngular(text)
                      text = removeMarkdown(text)
                      return text
                  

                  它对我很有效,但当然可以增强...

                  【讨论】:

                    【解决方案17】:

                    Beautiful Soup 套餐会立即为您完成这项工作。

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

                    【讨论】:

                    • 来自审核队列:我可以请求您在您的答案周围添加更多上下文。仅代码的答案很难理解。如果您可以在帖子中添加更多信息,这将对提问者和未来的读者都有帮助。
                    【解决方案18】:

                    søren-løvborg 答案的 python 3 改编版

                    from html.parser import HTMLParser
                    from html.entities import html5
                    
                    class HTMLTextExtractor(HTMLParser):
                        """ Adaption of http://stackoverflow.com/a/7778368/196732 """
                        def __init__(self):
                            super().__init__()
                            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):
                            if name in html5:
                                self.result.append(unichr(html5[name]))
                    
                        def get_text(self):
                            return u''.join(self.result)
                    
                    def html_to_text(html):
                        s = HTMLTextExtractor()
                        s.feed(html)
                        return s.get_text()
                    

                    【讨论】:

                      【解决方案19】:

                      我正在解析 Github 自述文件,发现以下内容确实运行良好:

                      import re
                      import lxml.html
                      
                      def strip_markdown(x):
                          links_sub = re.sub(r'\[(.+)\]\([^\)]+\)', r'\1', x)
                          bold_sub = re.sub(r'\*\*([^*]+)\*\*', r'\1', links_sub)
                          emph_sub = re.sub(r'\*([^*]+)\*', r'\1', bold_sub)
                          return emph_sub
                      
                      def strip_html(x):
                          return lxml.html.fromstring(x).text_content() if x else ''
                      

                      然后

                      readme = """<img src="https://raw.githubusercontent.com/kootenpv/sky/master/resources/skylogo.png" />
                      
                                  sky is a web scraping framework, implemented with the latest python versions in mind (3.4+). 
                                  It uses the asynchronous `asyncio` framework, as well as many popular modules 
                                  and extensions.
                      
                                  Most importantly, it aims for **next generation** web crawling where machine intelligence 
                                  is used to speed up the development/maintainance/reliability of crawling.
                      
                                  It mainly does this by considering the user to be interested in content 
                                  from *domains*, not just a collection of *single pages*
                                  ([templating approach](#templating-approach))."""
                      
                      strip_markdown(strip_html(readme))
                      

                      正确删除所有 markdown 和 html。

                      【讨论】:

                        【解决方案20】:

                        短版!

                        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)
                        

                        Regex source: MarkupSafe。他们的版本也处理 HTML 实体,而这个快速的版本没有。

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

                        让人们远离&lt;i&gt;italicizing&lt;/i&gt; 的东西,而不让is 到处乱窜是一回事。但接受任意输入并使其完全无害是另一回事。此页面上的大多数技术将保留未封闭的 cmets (&lt;!--) 和不属于标签 (blah &lt;&lt;&lt;&gt;&lt;blah) 的尖括号等内容。 HTMLParser 版本甚至可以留下完整的标签,如果它们在一个未封闭的评论中。

                        如果您的模板是{{ firstname }} {{ lastname }},该怎么办? firstname = '&lt;a'lastname = 'href="http://evil.com/"&gt;' 将被此页面上的每个标签剥离器允许通过(@Medeiros 除外!),因为它们本身并不是完整的标签。去掉普通的 HTML 标签是不够的。

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

                        绝对不保证生成的字符串是 HTML 安全的。因此,切勿将strip_tags 调用的结果标记为安全而不先转义它,例如使用escape()

                        听从他们的建议!

                        要使用 HTMLParser 去除标签,您必须多次运行它。

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

                        看看这个字符串(source and discussion):

                        <img<!-- --> src=x onerror=alert(1);//><!-- -->
                        

                        HTMLParser 第一次看到它时,它无法判断&lt;img...&gt; 是一个标签。它看起来坏了,所以 HTMLParser 没有摆脱它。它只取出&lt;!-- comments --&gt;,留给你

                        <img src=x onerror=alert(1);//>
                        

                        这个问题在 2014 年 3 月被 Django 项目披露。他们的旧strip_tags 与这个问题的最高答案基本相同。 Their new version 基本上循环运行它,直到再次运行它不会改变字符串:

                        # _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
                        

                        当然,如果您总是逃避strip_tags() 的结果,这都不是问题。

                        2015 年 3 月 19 日更新:在 1.4.20、1.6.11、1.7.7 和 1.8c1 之前的 Django 版本中存在一个错误。这些版本可能会在 strip_tags() 函数中进入无限循环。上面复制了固定版本。 More details here.

                        好东西可以复制或使用

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

                        我的示例代码来自优秀的MarkupSafe 库,用于防止跨站点脚本。它方便快捷(使用 C 加速到其原生 Python 版本)。它包含在 Google App Engine 中,并被 Jinja2 (2.7 and up)、Mako、Pylons 等使用。它可以轻松地与 Django 1.7 中的 Django 模板一起使用。

                        Django 的 strip_tags 和其他 html 实用程序最近的版本都不错,但我发现它们不如 MarkupSafe 方便。它们非常独立,您可以从this file 复制您需要的内容。

                        如果您需要去除几乎所有标签,Bleach 库很好。你可以让它强制执行一些规则,比如“我的用户可以使用斜体,但他们不能制作 iframe。”

                        了解标签剥离器的属性!对其进行模糊测试! Here is the code我曾经为这个答案做过研究。

                        sheepish note - 问题本身是关于打印到控制台的,但这是“python 从字符串中剥离 html”的最高 Google 结果,这就是为什么这个答案是关于网络的 99% .

                        【讨论】:

                        • 我的“备用最后一行”示例代码无法处理 html 实体 - 这有多糟糕?
                        • 我只解析了一小段没有特殊标签的html,而你的短版本很好地完成了这项工作。感谢分享!
                        • re: ready_for_web = cgi.escape(no_tags) -- cgi.escape 是“自 3.2 版起已弃用:此函数不安全,因为默认情况下引用为 false,因此已弃用。请改用 html.escape()。”在 3.8 中删除。
                        【解决方案21】:

                        这是一个快速修复,可以进一步优化,但它会正常工作。此代码将用“”替换所有非空标签,并从给定的输入文本中去除所有 html 标签。您可以使用 ./file.py 输入输出运行它

                            #!/usr/bin/python
                        import sys
                        
                        def replace(strng,replaceText):
                            rpl = 0
                            while rpl > -1:
                                rpl = strng.find(replaceText)
                                if rpl != -1:
                                    strng = strng[0:rpl] + strng[rpl + len(replaceText):]
                            return strng
                        
                        
                        lessThanPos = -1
                        count = 0
                        listOf = []
                        
                        try:
                            #write File
                            writeto = open(sys.argv[2],'w')
                        
                            #read file and store it in list
                            f = open(sys.argv[1],'r')
                            for readLine in f.readlines():
                                listOf.append(readLine)         
                            f.close()
                        
                            #remove all tags  
                            for line in listOf:
                                count = 0;  
                                lessThanPos = -1  
                                lineTemp =  line
                        
                                    for char in lineTemp:
                        
                                    if char == "<":
                                        lessThanPos = count
                                    if char == ">":
                                        if lessThanPos > -1:
                                            if line[lessThanPos:count + 1] != '<>':
                                                lineTemp = replace(lineTemp,line[lessThanPos:count + 1])
                                                lessThanPos = -1
                                    count = count + 1
                                lineTemp = lineTemp.replace("&lt","<")
                                lineTemp = lineTemp.replace("&gt",">")                  
                                writeto.write(lineTemp)  
                            writeto.close() 
                            print "Write To --- >" , sys.argv[2]
                        except:
                            print "Help: invalid arguments or exception"
                            print "Usage : ",sys.argv[0]," inputfile outputfile"
                        

                        【讨论】:

                          【解决方案22】:

                          如果只运行一次,使用 HTML-Parser 的解决方案都是易碎的:

                          html_to_text('<<b>script>alert("hacked")<</b>/script>
                          

                          结果:

                          <script>alert("hacked")</script>
                          

                          您打算阻止的事情。如果您使用 HTML 解析器,请计算标签,直到替换为零:

                          from HTMLParser import HTMLParser
                          
                          class MLStripper(HTMLParser):
                              def __init__(self):
                                  self.reset()
                                  self.fed = []
                                  self.containstags = False
                          
                              def handle_starttag(self, tag, attrs):
                                 self.containstags = True
                          
                              def handle_data(self, d):
                                  self.fed.append(d)
                          
                              def has_tags(self):
                                  return self.containstags
                          
                              def get_data(self):
                                  return ''.join(self.fed)
                          
                          def strip_tags(html):
                              must_filtered = True
                              while ( must_filtered ):
                                  s = MLStripper()
                                  s.feed(html)
                                  html = s.get_data()
                                  must_filtered = s.has_tags()
                              return html
                          

                          【讨论】:

                          • 如果您调用一个名为html_to_text 的函数,并且将该函数输出的文本嵌入到 html 中而不转义该文本,那么这是缺少转义,这是一个安全漏洞,而不是html_to_text 函数。 html_to_text 函数从未向您保证输出将是文本。无论您是从 html_to_text 还是其他来源获取文本,将文本插入 html 而不转义都是一个潜在的安全漏洞。
                          • 你是对的,缺乏转义,但问题是从给定字符串中剥离 html 而不是转义给定字符串。如果较早的答案由于删除了一些 html 而使用他们的解决方案构建了新的 html,那么使用此解决方案是危险的。
                          【解决方案23】:

                          我没有过多考虑它会错过的情况,但你可以做一个简单的正则表达式:

                          re.sub('<[^<]+?>', '', text)
                          

                          对于不懂正则表达式的人,这会搜索字符串&lt;...&gt;,其中内部内容由一个或多个不是&lt; 的字符组成(+)。 ? 表示它将匹配它可以找到的最小字符串。例如给定&lt;p&gt;Hello&lt;/p&gt;,它将分别与? 匹配&lt;'p&gt;&lt;/p&gt;。没有它,它将匹配整个字符串&lt;..Hello..&gt;

                          如果非标签&lt; 出现在html 中(例如2 &lt; 3),它应该写成转义序列&amp;... 无论如何^&lt; 可能是不必要的。

                          【讨论】:

                          • 这几乎就是 Django 的 strip_tags 的做法。
                          • 请注意,这会使输出中的 HTML 实体(例如 &amp;amp;)保持不变。
                          • 人们仍然可以用这样的东西来欺骗这个方法:/script>
                          • 不要这样做!正如@Julio Garcia 所说,这不安全!
                          • 人们,不要混淆 HTML 剥离和 HTML 清理。是的,对于损坏或恶意输入,此答案可能会生成带有 HTML 标记的输出。它仍然是一种完全有效的去除 HTML 标签的方法。 然而,去除 HTML 标记并不是对正确的 HTML 清理的有效替代。规则并不难:任何时候你在 HTML 输出中插入一个纯文本字符串,你应该总是 HTML 转义它(使用cgi.escape(s, True)),即使你“知道”它不包含 HTML(例如,因为您剥离了 HTML 内容)。但是,这不是 OP 所要求的。
                          【解决方案24】:

                          您可以编写自己的函数:

                          def StripTags(text):
                               finished = 0
                               while not finished:
                                   finished = 1
                                   start = text.find("<")
                                   if start >= 0:
                                       stop = text[start:].find(">")
                                       if stop >= 0:
                                           text = text[:start] + text[start+stop+1:]
                                           finished = 0
                               return text
                          

                          【讨论】:

                          • 追加到字符串会创建字符串的新副本吗?
                          • @Nerdling - 确实如此,这会导致经常使用的函数(或者,就此而言,对大块文本起作用的不经常使用的函数)出现一些相当令人印象深刻的低效率。请参阅this详情页。 :D
                          • 它是否针对引用的字符串进行测试?没有。
                          【解决方案25】:

                          如果您需要保留 HTML 实体(即&amp;amp;),我在Eloff's answer 中添加了“handle_entityref”方法。

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

                          【讨论】:

                            【解决方案26】:

                            我已经成功地将 Eloff 的答案用于 Python 3.1 [非常感谢!]。

                            我升级到 Python 3.2.3,但遇到了错误。

                            感谢响应者 Thomas K 提供 here 的解决方案是将 super().__init__() 插入以下代码:

                            def __init__(self):
                                self.reset()
                                self.fed = []
                            

                            ...为了使它看起来像这样:

                            def __init__(self):
                                super().__init__()
                                self.reset()
                                self.fed = []
                            

                            ...它适用于 Python 3.2.3。

                            再次感谢 Thomas K 的修复以及上面提供的 Eloff 的原始代码!

                            【讨论】:

                              【解决方案27】:

                              这种方法对我来说完美无缺,不需要额外安装:

                              import re
                              import htmlentitydefs
                              
                              def convertentity(m):
                                  if m.group(1)=='#':
                                      try:
                                          return unichr(int(m.group(2)))
                                      except ValueError:
                                          return '&#%s;' % m.group(2)
                                      try:
                                          return htmlentitydefs.entitydefs[m.group(2)]
                                      except KeyError:
                                          return '&%s;' % m.group(2)
                              
                              def converthtml(s):
                                  return re.sub(r'&(#?)(.+?);',convertentity,s)
                              
                              html =  converthtml(html)
                              html.replace("&nbsp;", " ") ## Get rid of the remnants of certain formatting(subscript,superscript,etc).
                              

                              【讨论】:

                              • 这会将 HTML 实体解码为纯文本,但显然实际上并没有去除任何标签,这是最初的问题。 (另外,第二个 try-except 块需要取消缩进才能让代码做同样的事情)。
                              猜你喜欢
                              • 2014-11-16
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多