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

我能够成功获取大部分内容,但我在内容中的标签方面遇到了一些挑战。(我从以下基本策略开始:如果节点中有超过 x 个字符,则它是内容)。我们以下面的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。但我想避免两次处理相同的内容 - 我正在尝试解析这些页面,以便我可以显示给定链接的内容片段(非常像 Facebook Share) - 如果一切都是用 Beautiful Soup 完成的,我想它会更快。

所以我的问题是:有没有办法使用 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 的代码,并且根据我的测试,它看起来像是在寻找“文本”标签之前)。因此,如果没有先前的兄弟姐妹,我会感到惊讶的是,它没有经历 a.previousSibling is None 和 a;nextSibling is None 的 if 逻辑。

你能让我知道我做错了什么吗?

-ecognium

有帮助吗?

解决方案

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

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 处理此类极端情况的声明。但我希望这个总体想法可以帮助你。(事实上​​你可能想要 合并 下一个 兄弟姐妹,如果它是一个字符串 - 不确定它如何与你的启发法一起发挥作用 len(x) > 20, ,但是假设您有两个 9 个字符的字符串,其中一个 <a> 中间包含 5 个字符的字符串,也许您想将该批次作为“23 个字符的字符串”来选取?我无法判断,因为我不明白你的启发式的动机)。

我想除了 <a> 您还需要删除其他标签,例如 <b> 或者 <strong>, , 或许 <p> 和/或 <br>, , ETC...?我想这也取决于你的启发法背后的实际想法是什么!

其他提示

当我尝试 压扁 文档中的标签,这样,标签的整个内容将被拉到其父节点(我想减少 p 带有所有子段落、列表的标签, 分区跨度, , ETC。里面但摆脱 风格字体 标签和一些可怕的单词到 html 生成器残留),我发现使用 BeautifulSoup 本身相当复杂,因为 提炼() 还删除内容和 用。。。来代替() 不幸的是不接受 没有任何 作为论证。经过一些疯狂的递归实验,我最终决定在使用 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)

标签 参数是单个标签或要扁平化的标签列表。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top