将网页数据传递给Beautiful Soup - 空列表

7 投票
4 回答
14990 浏览
提问于 2025-04-18 15:32

我检查了一遍我的代码,还对比了一些打开网址并把网页数据传给Beautiful Soup的操作,但不知道为什么我的代码就是不返回任何东西,尽管格式是正确的:

>>> from bs4 import BeautifulSoup

>>> from urllib3 import poolmanager

>>> connectBuilder = poolmanager.PoolManager()

>>> content = connectBuilder.urlopen('GET', 'http://www.crummy.com/software/BeautifulSoup/')

>>> content
<urllib3.response.HTTPResponse object at 0x00000000032EC390>

>>> soup = BeautifulSoup(content)

>>> soup.title
>>> soup.title.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'name'
>>> soup.p
>>> soup.get_text()
''

>>> content.data
a stream of data follows...

从上面可以看出,urlopen()返回了一个HTTP响应,这个响应被变量content捕获。这样理解是合理的,因为它可以读取响应的状态。但是,当这个响应传给Beautiful Soup后,网页数据并没有被转换成Beautiful Soup对象(变量soup)。你可以看到我尝试读取了一些标签和文本,get_text()却返回了一个空列表,这真是奇怪。

更奇怪的是,当我通过content.data访问网页数据时,数据是能显示出来的,但这没什么用,因为我无法用Beautiful Soup来解析它。我到底出了什么问题呢?谢谢。

4 个回答

0

我写的Beautiful Soup代码在一个环境下(我自己的电脑)运行得很好,但在另一个环境下(Ubuntu 14服务器)却返回了一个空列表。

我通过更改安装方式解决了这个问题。具体细节可以在另一个讨论中找到:

使用Beautiful Soup解析HTML返回空列表

2

如图所示,urlopen() 返回的是一个 HTTP 响应,这个响应被存储在变量 content 中……

你所称的 content 其实并不是内容,而是一个像文件一样的对象,你可以从中读取内容。BeautifulSoup 可以处理这种对象,但直接打印出来对调试并没有太大帮助。所以,我们来实际读取一下内容,这样调试会更简单:

>>> response = connectBuilder.urlopen('GET', 'http://www.crummy.com/software/BeautifulSoup/')
>>> response
<urllib3.response.HTTPResponse object at 0x00000000032EC390>
>>> content = response.read()
>>> content
b''

这样应该能清楚地说明 BeautifulSoup 不是问题所在。不过继续往下看:

……但是在传入 Beautiful Soup 后,网页数据并没有转换成 Beautiful Soup 对象(变量 soup)。

其实是转换了的。你看到 soup.title 返回 None 而不是抛出 AttributeError,这就很有说服力,但你也可以直接测试一下:

>>> type(soup)
bs4.BeautifulSoup

这绝对是一个 BeautifulSoup 对象。

当你给 BeautifulSoup 传入一个空字符串时,返回的内容会根据它使用的解析器而有所不同;如果它依赖于 Python 3.x 的标准库,你得到的将是一个 html 节点,里面有一个空的 head 和空的 body,其他什么都没有。所以,当你查找 title 节点时,它并不存在,因此你得到 None


那么,怎么解决这个问题呢?

正如文档所说,你正在使用“最低级别的请求调用,所以你需要指定所有的原始细节。”这些原始细节是什么呢?老实说,如果你还不知道这些,你就不应该使用这个方法。在你连基础都不懂的情况下教你如何处理 urllib3 的底层细节,并不是在帮你。

实际上,你在这里根本不需要 urllib3。只需使用 Python 自带的模块:

>>> # on Python 2.x, instead do: from urllib2 import urlopen 
>>> from urllib.request import urlopen
>>> r = urlopen('http://www.crummy.com/software/BeautifulSoup/')
>>> soup = BeautifulSoup(r)
>>> soup.title.text
'Beautiful Soup: We called him Tortoise because he taught us.'
12

urllib3会返回一个响应对象,这个对象里面有一个叫.data的部分,里面存的是已经加载好的内容。

根据上面这个快速入门的使用示例,我会这样做:

import urllib3
http = urllib3.PoolManager()
response = http.request('GET', 'http://www.crummy.com/software/BeautifulSoup/')

from bs4 import BeautifulSoup
soup = BeautifulSoup(response.data)  # Note the use of the .data property
...

其他部分应该能正常工作。

--

关于你原来代码出错的原因:

你传递了整个response对象,而不是里面的内容。通常这样做是没问题的,因为response对象像一个文件一样可以读取,在这个情况下,urllib3已经把响应内容全部读取并解析了,所以里面没有东西可以再用.read()去读取了。这就像你传递了一个已经读过的文件指针。而.data则可以访问已经读取的数据。

如果你想把urllib3的响应对象当作文件来用,你需要关闭内容的预加载,像这样:

response = http.request('GET', 'http://www.crummy.com/software/BeautifulSoup/', preload_content=False)
soup = BeautifulSoup(response)  # We can pass the original `response` object now.

现在应该能按你预期的那样工作了。

我知道这不是很明显的行为,作为urllib3的作者我对此表示歉意。:) 我们计划将preload_content=False设为默认选项,也许很快就会实现(我在这里开了个问题)。

--

关于.urlopen.request的小提示:

.urlopen假设你会自己处理请求中传递的参数编码。在这种情况下,使用.urlopen是可以的,因为你没有传递任何参数,但一般来说,.request会为你做更多的额外工作,所以使用起来更方便。

如果有人愿意帮助我们改进文档,那将非常感谢。:) 请向https://github.com/shazow/urllib3提交一个PR,并把自己添加为贡献者!

16

如果你只是想抓取网页内容,使用 requests 就可以获取你需要的内容:

from bs4 import BeautifulSoup

import requests
r = requests.get('http://www.crummy.com/software/BeautifulSoup/')
soup = BeautifulSoup(r.content)

In [59]: soup.title
Out[59]: <title>Beautiful Soup: We called him Tortoise because he taught us.</title>

In [60]: soup.title.name
Out[60]: 'title'

撰写回答