使用Python的requests库获取HTML?

45

我正在尝试自学一些基本的网络爬虫。使用Python的requests模块,我能够抓取各种网站的HTML,直到我尝试了这个:

>>> r = requests.get('http://www.wrcc.dri.edu/WRCCWrappers.py?sodxtrmts+028815+por+por+pcpn+none+mave+5+01+F')

我得到的不是这个页面的基本HTML源码,而是:

>>> r.text
'\x1f\ufffd\x08\x00\x00\x00\x00\x00\x00\x03\ufffd]o\u06f8\x12\ufffd\ufffd\ufffd+\ufffd]...

>>> r.content
b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xed\x9d]o\xdb\xb8\x12\x86\xef\xfb+\x88]\x14h...
我已经尝试了许多get/post的组合方式,根据文档和SO以及其他示例猜测了各种语法。我不理解我上面看到的内容,也无法将其转化为可读的内容,也无法弄清楚如何获取我真正想要的内容。我的问题是,如何获取上述页面的HTML代码?

似乎在这里可以工作,我刚刚在Python 2.7上尝试了确切的URL。 - Kroltan
1
我强烈推荐使用BeautifulSoup进行网络爬虫 http://beautiful-soup-4.readthedocs.org/en/latest/#。它会让你的生活变得轻松得多。 - Ron
你可以使用urllib3,它与requests相同。 - aibotnet
@vikasdumca:requests是基于urllib3构建的。然而,问题在于这里的服务器。 - Martijn Pieters
使用requests库对我来说很好用,你使用的是哪个版本的requests? - Padraic Cunningham
显示剩余4条评论
4个回答

29

所涉及的服务器给你提供了一个压缩响应。此服务器也非常有问题,它发送以下标头:

$ curl -D - -o /dev/null -s -H 'Accept-Encoding: gzip, deflate' http://www.wrcc.dri.edu/WRCCWrappers.py?sodxtrmts+028815+por+por+pcpn+none+mave+5+01+F
HTTP/1.1 200 OK
Date: Tue, 06 Jan 2015 17:46:49 GMT
Server: Apache
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd"><html xmlns="http: //www.w3.org/1999/xhtml" lang="en-US">
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 3659
Content-Type: text/html

<!DOCTYPE..> 不是有效的 HTTP 头部信息。因此,过了 Server 后面的其余头部信息会被忽略。为什么服务器会插入这段内容还不清楚;很可能是因为 WRCCWrappers.py 是一个 CGI 脚本,它不输出头部信息,但在 doctype 行之后包含了一个双换行符,欺骗 Apache 服务器在那里插入额外的头部信息。

因此,requests 也无法检测到数据是否经过 gzip 编码。数据都在那里,你只需要解码它。但如果数据不完整,你就无法解码。

解决方法是告诉服务器不要压缩:

headers = {'Accept-Encoding': 'identity'}
r = requests.get(url, headers=headers)

并且将返回未压缩的响应。

顺便提一下,在Python 2上,HTTP头解析器不是那么严格,可以将doctype声明为一个头部:

>>> pprint(dict(r.headers))
{'<!doctype html public "-//w3c//dtd xhtml 1.0 transitional//en" "dtd/xhtml1-transitional.dtd"><html xmlns="http': '//www.w3.org/1999/xhtml" lang="en-US">',
 'connection': 'Keep-Alive',
 'content-encoding': 'gzip',
 'content-length': '3659',
 'content-type': 'text/html',
 'date': 'Tue, 06 Jan 2015 17:42:06 GMT',
 'keep-alive': 'timeout=5, max=100',
 'server': 'Apache',
 'vary': 'Accept-Encoding'}

并且 content-encoding 信息会保留,所以 requests 会为您解码内容,如预期的那样。


是的,这是一个Python 3的问题。在使用Python 2时每次都能完美运行。 - Padraic Cunningham
@PadraicCunningham:不,这是服务器问题。Python 2只是碰巧不能正确验证头文件。它在Python 2中可以工作,但你会得到<!DOCTYPE...>行作为头文件。 - Martijn Pieters
@MartijnPieters:事实证明,当我使用这个解决方法时,响应内容会因为偶尔添加的额外字符而损坏,这些字符从1934年的数据开始。根据您的解释,我改为使用zlib.decompress(r.content, 16+zlib.MAX_WBITS)对响应内容进行解压缩,这似乎处理了所有问题。 - Rich Thompson
请注意,此URL的HTTP标头现已修复。对于错误我深表歉意。 - Grant
@Grant::-D 不过不用向我道歉。 - Martijn Pieters

13

此URL的HTTP标头现已修复。

>>> import requests
>>> print requests.__version__
2.5.1
>>> r = requests.get('http://www.wrcc.dri.edu/WRCCWrappers.py?sodxtrmts+028815+por+por+pcpn+none+mave+5+01+F')
>>> r.text[:100]
u'\n<!DOCTYPE html>\n<HTML>\n<HEAD><TITLE>Monthly Average of Precipitation, Station id: 028815</TITLE></H'
>>> r.headers
{'content-length': '3672', 'content-encoding': 'gzip', 'vary': 'Accept-Encoding', 'keep-alive': 'timeout=5, max=100', 'server': 'Apache', 'connection': 'Keep-Alive', 'date': 'Thu, 12 Feb 2015 18:59:37 GMT', 'content-type': 'text/html; charset=utf-8'}

9
这里是一个使用BeautifulSoup库的例子,它能够“轻松地从网页中爬取信息”。BeautifulSoup
from bs4 import BeautifulSoup

import requests

# request web page
resp = requests.get("http://example.com")

# get the response text. in this case it is HTML
html = resp.text

# parse the HTML
soup = BeautifulSoup(html, "html.parser")

# print the HTML as text
print(soup.body.get_text().strip())

还有结果

Example Domain
This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.
More information...

9

我会以更简单的方式解决这个问题。只需导入html库以解码HTML特殊字符:

import html

r = requests.get('http://www.wrcc.dri.edu/WRCCWrappers.py?sodxtrmts+028815+por+por+pcpn+none+mave+5+01+F')

print(html.unescape(r.text))

3
+rep 八年后 - Padua

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