【问题标题】:Using Beautiful Soup Python module to replace tags with plain text使用 Beautiful Soup Python 模块将标签替换为纯文本
【发布时间】:2011-01-04 22:12:50
【问题描述】:

我正在使用Beautiful Soup 从网页中提取“内容”。我知道有些人之前问过这个question,他们都被指向美丽的汤,这就是我开始使用它的方式。

我能够成功获取大部分内容,但我遇到了作为内容一部分的标签的一些挑战。 (我从一个基本策略开始:如果一个节点中有多个 x-chars,那么它就是内容)。我们以下面的html代码为例:

<div id="abc">
    some long text goes <a href="/"> here </a> and hopefully it 
    will get picked up by the parser as content
</div>

results = soup.findAll(text=lambda(x): len(x) > 20)

当我使用上面的代码获取长文本时,它会在标签处中断(识别的文本将从“并且希望..”开始)。所以我尝试用纯文本替换标签如下:

anchors = soup.findAll('a')

for a in anchors:
  a.replaceWith('plain text')

上述方法不起作用,因为 Beautiful Soup 将字符串作为 NavigableString 插入,当我将 findAll 与 len(x) > 20 一起使用时,这会导致同样的问题。我可以先使用正则表达式将 html 解析为纯文本,清除所有不需要的标签,然后调用 Beautiful Soup。但我想避免两次处理相同的内容——我试图解析这些页面,以便我可以显示给定链接的内容的 sn-p(非常像 Facebook 共享)——如果一切都用 Beautiful汤,我想它会更快。

所以我的问题是:有没有办法使用 Beautiful Soup 来“清除标签”并用“纯文本”替换它们。如果没有,最好的方法是什么?

感谢您的建议!

更新: Alex 的代码在示例示例中运行良好。我还尝试了各种边缘情况,它们都运行良好(通过下面的修改)。所以我在一个真实的网站上试了一下,遇到了令我困惑的问题。

import urllib
from BeautifulSoup import BeautifulSoup

page = urllib.urlopen('http://www.engadget.com/2010/01/12/kingston-ssdnow-v-dips-to-30gb-size-lower-price/')

anchors = soup.findAll('a')
i = 0
for a in anchors:
    print str(i) + ":" + str(a)
    for a in anchors:
        if (a.string is None): a.string = ''
        if (a.previousSibling is None and a.nextSibling is None):
            a.previousSibling = a.string
        elif (a.previousSibling is None and a.nextSibling is not None):
            a.nextSibling.replaceWith(a.string + a.nextSibling)
        elif (a.previousSibling is not None and a.nextSibling is None):
            a.previousSibling.replaceWith(a.previousSibling + a.string)
        else:
            a.previousSibling.replaceWith(a.previousSibling + a.string + a.nextSibling)
            a.nextSibling.extract()
    i = i+1

当我运行上面的代码时,我得到以下错误:

0:<a href="http://www.switched.com/category/ces-2010">Stay up to date with 
Switched's CES 2010 coverage</a>
Traceback (most recent call last):
  File "parselink.py", line 44, in <module>
  a.previousSibling.replaceWith(a.previousSibling + a.string + a.nextSibling)
 TypeError: unsupported operand type(s) for +: 'Tag' and 'NavigableString'

当我查看 HTML 代码时,“保持最新..”没有任何以前的兄弟姐妹(在我看到 Alex 的代码之前,我没有以前的兄弟姐妹如何工作,并且根据我的测试,它看起来像是在寻找'text'标签之前)。所以,如果没有前一个兄弟,我很惊讶它没有通过a.previousSibling是None和a;nextSibling是None的if逻辑。

你能告诉我我做错了什么吗?

-ecognium

【问题讨论】:

    标签: python html-content-extraction


    【解决方案1】:

    当我尝试 flatten 文档中的标签时,标签的整个内容将被拉到其父节点的位置(我想减少 的内容p 标签内包含所有子段落、列表、divspan 等,但去掉了 style 和 font 标签和一些可怕的 word-to-html 生成器残余),我发现使用 BeautifulSoup 本身相当复杂,因为 extract() 也会删除内容并 replaceWith () 不幸的是不接受 None 作为参数。经过一些疯狂的递归实验,我最终决定在使用 BeautifulSoup 处理文档之前或之后使用正则表达式,方法如下:

    import re
    def flatten_tags(s, tags):
       pattern = re.compile(r"<(( )*|/?)(%s)(([^<>]*=\\\".*\\\")*|[^<>]*)/?>"%(isinstance(tags, basestring) and tags or "|".join(tags)))
       return pattern.sub("", s)
    

    tags 参数可以是单个标签,也可以是要展平的标签列表。

    【讨论】:

      【解决方案2】:

      适用于您的具体示例的方法是:

      from BeautifulSoup import BeautifulSoup
      
      ht = '''
      <div id="abc">
          some long text goes <a href="/"> here </a> and hopefully it 
          will get picked up by the parser as content
      </div>
      '''
      soup = BeautifulSoup(ht)
      
      anchors = soup.findAll('a')
      for a in anchors:
        a.previousSibling.replaceWith(a.previousSibling + a.string)
      
      results = soup.findAll(text=lambda(x): len(x) > 20)
      
      print results
      

      会发光

      $ python bs.py
      [u'\n    some long text goes  here ', u' and hopefully it \n    will get picked up by the parser as content\n']
      

      当然,您可能需要多加注意,即,如果没有a.string,或者如果a.previousSiblingNone,您将需要合适的if 语句来处理照顾这种极端情况。但我希望这个一般的想法可以帮助你。 (事实上​​,如果 next 是一个字符串,您可能还想合并兄弟 - 不确定它如何与您的启发式 len(x) &gt; 20 一起使用,但例如说您有两个 9 字符的字符串,&lt;a&gt; 在中间包含一个 5 字符的字符串,也许你想把它选为“23 字符的字符串”?我不知道,因为我不知道了解启发式的动机)。

      我想除了&lt;a&gt; 标签之外,您还想删除其他标签,例如&lt;b&gt;&lt;strong&gt;,可能是&lt;p&gt; 和/或&lt;br&gt;,等等...?我想这也取决于你的启发式背后的实际想法是什么!

      【讨论】:

      • 非常感谢,亚历克斯。您的代码非常适合我发布的示例的许多组合。然而,当我在一个真实的网站上运行它时,我得到了奇怪的结果。我不确定我做错了什么!我只是用我的新代码更新帖子。非常感谢您的帮助。你是对的,我想将所有文本合并成一个巨大的字符串。我基本上是在尝试获取页面的“内容”部分,以便向其展示摘要。你也是正确的,我最终将不得不处理所有其他标签,如 等。
      • @Ecognium,您遇到的具体问题是上一个或下一个兄弟确实存在但立即是一个标签,而不是一个字符串 - 在这种情况下,您不能将它与字符串连接(所以在这种情况下,您基本上应该跳过,即不进行任何更改!)。要处理多个标签,请确保按顺序遍历它们(使用选择器函数,该函数为您要删除的所有标签返回 True,并且仅适用于那些标签)。
      • @Alex,再次感谢。这就说得通了。如果前一个兄弟是标签,我添加了一些实例检查来忽略,但即使这样也会导致问题。我将进行更多调试并尝试找出问题所在。非常感谢您的宝贵时间。
      猜你喜欢
      • 1970-01-01
      • 2013-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-18
      • 2019-01-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多