为什么BeautifulSoup无法正确读取/解析这个RSS(XML)文档?

9
YCombinator很友好地提供了一个RSS订阅和一个包含HackerNews热门文章的大型RSS订阅。我正在尝试编写一个Python脚本来访问RSS订阅文档,然后使用BeautifulSoup解析出特定的信息。然而,当BeautifulSoup尝试获取每个项目的内容时,我遇到了一些奇怪的行为。
以下是RSS订阅的几行示例:
<rss version="2.0">
<channel>
<title>Hacker News</title><link>http://news.ycombinator.com/</link><description>Links for the intellectually curious, ranked by readers.</description>
<item>
    <title>EFF Patent Project Gets Half-Million-Dollar Boost from Mark Cuban and &#39;Notch&#39;</title>
    <link>https://www.eff.org/press/releases/eff-patent-project-gets-half-million-dollar-boost-mark-cuban-and-notch</link>
    <comments>http://news.ycombinator.com/item?id=4944322</comments>
    <description><![CDATA[<a href="http://news.ycombinator.com/item?id=4944322">Comments</a>]]></description>
</item>
<item>
    <title>Two Billion Pixel Photo of Mount Everest (can you find the climbers?)</title>
    <link>https://s3.amazonaws.com/Gigapans/EBC_Pumori_050112_8bit_FLAT/EBC_Pumori_050112_8bit_FLAT.html</link>
    <comments>http://news.ycombinator.com/item?id=4943361</comments>
    <description><![CDATA[<a href="http://news.ycombinator.com/item?id=4943361">Comments</a>]]></description>
</item>
...
</channel>
</rss>

以下是我用Python编写的代码,用于访问此Feed并打印每个项目的标题链接评论

import sys
import requests
from bs4 import BeautifulSoup

request = requests.get('http://news.ycombinator.com/rss')
soup = BeautifulSoup(request.text)
items = soup.find_all('item')
for item in items:
    title = item.find('title').text
    link = item.find('link').text
    comments = item.find('comments').text
    print title + ' - ' + link + ' - ' + comments

然而,这个脚本输出的结果看起来像这样:
EFF Patent Project Gets Half-Million-Dollar Boost from Mark Cuban and &#39;Notch&#39; -  - http://news.ycombinator.com/item?id=4944322
Two Billion Pixel Photo of Mount Everest (can you find the climbers?) -  - http://news.ycombinator.com/item?id=4943361
...

正如您所看到的,中间的项目link被省略了。也就是说,link的结果值以某种方式成为空字符串。那是为什么呢?
当我深入挖掘中的内容时,我意识到它在解析XML时出现了问题。这可以通过查看中的第一个项目来看出:
>>> print items[0]
<item><title>EFF Patent Project Gets Half-Million-Dollar Boost from Mark Cuban and &#39;Notch&#39;</title></link>https://www.eff.org/press/releases/eff-patent-project-gets-half-million-dollar-boost-mark-cuban-and-notch<comments>http://news.ycombinator.com/item?id=4944322</comments><description>...</description></item>

你会注意到在`link`标签里出现了一些奇怪的东西。它只得到了关闭标签,然后是该标签后面的文本。这是一些非常奇怪的行为,特别是与`title`和`comments`不出问题相比较。这似乎是BeautifulSoup的问题,因为requests实际读入的内容没有任何问题。我认为这不仅限于BeautifulSoup,因为我也尝试使用xml.etree.ElementTree API,但是出现了同样的问题(BeautifulSoup是基于此API构建的吗?)。有人知道为什么会发生这种情况,或者我如何在不出错的情况下继续使用BeautifulSoup吗?注意:我最终能够通过xml.dom.minidom得到我想要的结果,但这似乎不是一个高度推荐的库。如果可能的话,我想继续使用BeautifulSoup。更新:我在使用Python 2.7.2和BS4 4.1.3的OSX 10.8 Mac上。更新2:我有lxml,并且使用pip安装。它的版本是3.0.2。至于libxml,在/usr/lib中检查,显示的是libxml2.2.dylib。不确定这是何时或如何安装的。

你确定这是实际的 RSS 吗?因为我刚用 ElementTreecElementTreelxml 实现进行了测试,它们都可以很好地获取到 link 节点。所以,要么你在使用 xml.etree.ElementTree 时出了问题,要么你没有提供正确的输入数据。 - abarnert
@abarnert 我已经使用了requests和urllib2直接从http://news.ycombinator.com/rss获取数据。所以,除非ycombinator提供的内容有问题,否则我不知道RSS的问题出在哪里。 - jbranchaud
另外,你在哪个平台上? - abarnert
感谢列出平台和BS4版本。但是lxml的问题呢?您是否有它,如果有,那么是什么版本,如何安装,以及您使用的是哪个libxml,如果不是Apple内置的/usr/lib/libxml2.2.dylib,那么那个版本是多少,如何安装的? - abarnert
看起来您从未找到答案?我最近遇到了类似的问题,通过使用html5lib解析器解决了这个问题。我不知道为什么,但如果您还没有尝试过,可以尝试一下。 - maxliving
显示剩余5条评论
4个回答

7

哇,好问题。这让我觉得BeautifulSoup有一个bug。你不能使用soup.find_all('item').link来访问链接的原因是,当你最初将html加载到BeautifulSoup中时,它会对HTML进行一些奇怪的处理:

>>> from bs4 import BeautifulSoup as BS
>>> BS(html)
<html><body><rss version="2.0">
<channel>
<title>Hacker News</title><link/>http://news.ycombinator.com/<description>Links
for the intellectually curious, ranked by readers.</description>
<item>
<title>EFF Patent Project Gets Half-Million-Dollar Boost from Mark Cuban and 'No
tch'</title>
<link/>https://www.eff.org/press/releases/eff-patent-project-gets-half-million-d
ollar-boost-mark-cuban-and-notch
    <comments>http://news.ycombinator.com/item?id=4944322</comments>
<description>Comments]]&gt;</description>
</item>
<item>
<title>Two Billion Pixel Photo of Mount Everest (can you find the climbers?)</ti
tle>
<link/>https://s3.amazonaws.com/Gigapans/EBC_Pumori_050112_8bit_FLAT/EBC_Pumori_
050112_8bit_FLAT.html
    <comments>http://news.ycombinator.com/item?id=4943361</comments>
<description>Comments]]&gt;</description>
</item>
...
</channel>
</rss></body></html>

请仔细观察 - 它实际上已经将第一个 <link> 标签更改为 <link/>,然后删除了 </link> 标签。我不确定为什么会这样做,但如果不修复 BeautifulSoup.BeautifulSoup 类初始化中的问题,您现在将无法使用它。
更新: 我认为你现在最好的(虽然有点投机取巧)选择是使用以下代码来代替link标签:
>>> soup.find('item').link.next_sibling
u'http://news.ycombinator.com/'

@jdotjdot:你使用的BS4版本是什么?正如我在问题的评论中提到的,对于全新安装的BS4 4.1.3,在干净的Python.org 3.3.0和Apple预安装的2.7.2上,我没有看到这个错误。 - abarnert
@abarnert 我正在使用BS4 4.1.3版本。 - jdotjdot
我问过Treebranch了,你是否已经安装了lxml?如果是的话,libxml和lxml的版本分别是什么(以及它们是如何安装的)?因为如果可用的话,BS会尝试使用lxml。哦,还有你在哪个平台上。 - abarnert
@jdotjdot:等一下...你从哪里得到2.3.6的?http://lxml.de列出2.3.5作为最后一个2.x版本! - abarnert
@jdotjdot:至于不是一直在SO上,这正是我希望你一次回答所有问题的原因,因为我看到你的答案和提出下一个问题之间的时间从15分钟到16小时不等,同样也适用于你的回答... - abarnert
显示剩余9条评论

3

@Yan Hudon是正确的。我用soup = BeautifulSoup(request.text, 'xml')解决了这个问题。


3
实际上,问题似乎与您使用的解析器有关。默认情况下,使用HTML解析器。安装lxml模块后,请尝试使用soup = BeautifulSoup(request.text,'xml')。

然后它将使用XML解析器而不是HTML解析器,一切都应该没问题了。

更多信息请参见http://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser


我有一种感觉是lxml解析器在做这件事。 - FlipMcF

1

我认为这里的BeautifulSoup没有错误。

我在OS X 10.8.2上安装了一个干净的BS4 4.1.3版本,使用了苹果自带的2.7.2版本,一切都按预期工作。它不会将<link>误解析为</link>,因此它不会出现item.find('link')的问题。

我还尝试使用2.7.2中的原始xml.etree.ElementTreexml.etree.cElementTree,以及python.org 3.3.0中的xml.etree.ElementTree来解析相同的内容,结果也正常。以下是代码:

import xml.etree.ElementTree as ET

rss = ET.fromstring(x)
for channel in rss.findall('channel'):
  for item in channel.findall('item'):
    title = item.find('title').text
    link = item.find('link').text
    comments = item.find('comments').text
    print(title)
    print(link)
    print(comments)

然后我安装了lxml 3.0.2(我相信如果可用,BS会使用lxml),使用苹果内置的/usr/lib/libxml2.2.dylib(根据xml2-config --version,版本为2.7.8),并使用其etree进行了相同的测试,使用BS进行了测试,一切正常。

除了搞砸了<link>之外,jdotjdot的输出显示BS4以奇怪的方式搞砸了<description>。原始内容如下:

<description><![CDATA[<a href="http://news.ycombinator.com/item?id=4944322">Comments</a>]]></description>

他的输出是:

<description>Comments]]&gt;</description>

我运行完全相同的代码后的输出结果是:

<description><![CDATA[<a href="http://news.ycombinator.com/item?id=4944322">Comments</a>]]></description>

所以,似乎这里存在一个更大的问题。奇怪的是,当最新版本的任何东西没有发生时,它会发生在两个不同的人身上。

这意味着要么它是一个已经修复的错误,我只是拥有一个有错误的更新版本,要么就是他们都安装了某些奇怪的东西。

BS4本身可以排除,因为至少Treebranch和我一样都有4.1.3。虽然,如果不知道他如何安装它,可能会出现安装问题。

Python及其内置的etree也可以排除,因为至少Treebranch和我都使用来自OS X 10.8的相同的Apple 2.7.2。

这很可能是 lxml 或底层的 libxml 的错误,或者它们安装的方式。我知道 jdotjdot 有 lxml 2.3.6,所以这可能是一个已经在 2.3.6 和 3.0.2 之间的某个版本中修复的错误。事实上,根据 lxml 网站 和 2.3.5 之后任何版本的更改记录,确实没有 2.3.6,所以他拥有的可能是一些早期取消分支的有缺陷的发布版或其他什么东西...... 我不知道他的 libxml 版本,也不知道它们是如何安装的,也不知道他使用的平台,所以很难猜测,但至少这是可以调查的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接