【问题标题】:Converting html to text with Python使用 Python 将 html 转换为文本
【发布时间】:2013-01-19 14:37:46
【问题描述】:

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

期望的输出:

洛雷姆 ipsum dolor sit amet,consectetuer adipiscing elit。埃涅斯将军 ligula eget dolor。 Aenean massa

Consectetuer adipiscing elit. 一些 Link Aenean commodo ligula eget dolor。埃涅阿马萨

埃涅阿 massa.Lorem ipsum dolor sit amet,consectetuer adipiscing elit。埃涅阿 commodo ligula eget dolor。 Aenean massa

Lorem ipsum dolor sit amet, consectetuer adipiscing elit。 Aenean commodo ligula eget dolor。 Aenean massa

Consectetuer adipiscing elit.埃涅斯将军 ligula eget dolor。埃涅马萨

我尝试了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 块。我想将其转换为文本并打印在屏幕上。

【问题讨论】:

标签: python html web-scraping text beautifulsoup


【解决方案1】:

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

【讨论】:

【解决方案2】:

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

【讨论】:

  • soup.get_text() 正是我所需要的。谢谢!
  • BeautifulSoup 4,很遗憾。
  • 这拯救了我的一天!我更新了对 Python3 的响应并结合了@t-8ch 的换行理念。
【解决方案3】:

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

from bs4 import Beautifulsoup

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

【讨论】:

  • In 还会在句子中间放置换行符,例如"&lt;p&gt;That's &lt;strong&gt;not&lt;/strong&gt; what I want&lt;/p&gt;"
  • 我仍然在输出中看到 XML 元素(尽管这些不是严格的 HTML 元素,例如 [if gte mso 9]&gt;&lt;xml&gt;&lt;o:OfficeDocumentSettings&gt;&lt;o:AllowPNG&gt;&lt;/o:AllowPNG&gt;... 。我怎样才能过滤掉这些元素呢?
  • 我还想转换任何 HTML 字符,例如 &amp;nbsp;&amp;copy;
  • 这里的问题与@CsabaToth 相同。
【解决方案4】:

我需要一种在客户端系统上执行此操作而无需下载其他库的方法。我从来没有找到一个好的解决方案,所以我创建了自己的解决方案。如果您愿意,请随意使用它。

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)

【讨论】:

  • downvote 的原因是缩进不当。由于代码具有中等复杂性。修复起来有点困难。
【解决方案5】:

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

【讨论】:

    【解决方案6】:

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

    【讨论】:

    • 这个答案在没有第三方包依赖的情况下效果很好!我的 PyCharm 编辑器暗示我将需要使用 ABC mixin,以便摆脱所有需要实现的抽象方法错误。 gist.github.com/ye/050e898fbacdede5a6155da5b3db078d
    • 请注意,初始化 text 类属性并分配 self.text 实例属性是非 Pythonic 的,但由于重新分配,这里确实有效。如果要例如改用可变列表(pieces = []self.pieces.append(data)),该类的所有实例将共享同一个列表对象。
    • 很好的答案!尽管html 不能作为python2 标准库的一部分使用。所以这个解决方案只适用于python3。
    【解决方案7】:

    我非常喜欢@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 中的所有文本,理论上可以包括stylescript 标记。进一步的过滤可以通过扩展body 所示的模式来实现——即设置实例变量in_stylein_script

    【讨论】:

      【解决方案8】:

      这里有一些不错的东西,我不妨提出我的解决方案:

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

      【讨论】:

        【解决方案9】:

        gazpacho 可能是个不错的选择!

        输入:

        from gazpacho import Soup
        
        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>
        """
        

        输出:

        text = Soup(html).strip(whitespace=False) # to keep "\n" characters intact
        print(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
        

        【讨论】:

          【解决方案10】:

          主要问题是如何保留一些基本格式。这是我自己保留新行和项目符号的最小方法。我确信这不是您想要保留的所有内容的解决方案,但它是一个起点:

          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 元素的文本前添加一个带有 - 的新行

          【讨论】:

            【解决方案11】:

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

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

            将删除脚本块和所有可能不需要的文本。您可以配置lxml Cleaner 实例以满足您的需求。

            #!/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'))
            

            【讨论】:

              【解决方案12】:

              我个人喜欢emehex 的 Gazpacho 解决方案,但它只使用正则表达式来过滤掉标签。没有魔法了。这意味着解决方案将文本保留在

              所以我宁愿实现一个基于正则表达式的简单解决方案,并使用标准 Python 3.4 库来转义 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 或其他解析器解决方案的错误。但是您不需要任何 3rd 方包。

              【讨论】:

                【解决方案13】:
                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)
                

                【讨论】:

                • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2012-02-08
                • 1970-01-01
                • 2011-01-25
                • 2022-01-25
                • 1970-01-01
                • 2013-10-14
                • 2016-01-02
                相关资源
                最近更新 更多