我一直在从特定网站获取RSS订阅源时遇到问题。我最后编写了一个相当丑陋的程序来执行此功能,但我很好奇为什么会发生这种情况以及是否有任何更高级别的接口正确处理此问题。这个问题实际上不是一个显示限制因素,因为我不需要经常检索提要。
我已经阅读了一个捕获异常并返回部分内容的解决方案,但由于不完整的读取实际检索的字节数不同,我不确定这样的解决方案是否真的有效。
#!/usr/bin/env python
import os
import sys
import feedparser
from mechanize import Browser
import requests
import urllib2
from httplib import IncompleteRead
url = 'http://hattiesburg.legistar.com/Feed.ashx?M=Calendar&ID=543375&GUID=83d4a09c-6b40-4300-a04b-f88884048d49&Mode=2013&Title=City+of+Hattiesburg%2c+MS+-+Calendar+(2013)'
content = feedparser.parse(url)
if 'bozo_exception' in content:
print content['bozo_exception']
else:
print "Success!!"
sys.exit(0)
print "If you see this, please tell me what happened."
# try using mechanize
b = Browser()
r = b.open(url)
try:
r.read()
except IncompleteRead, e:
print "IncompleteRead using mechanize", e
# try using urllib2
r = urllib2.urlopen(url)
try:
r.read()
except IncompleteRead, e:
print "IncompleteRead using urllib2", e
# try using requests
try:
r = requests.request('GET', url)
except IncompleteRead, e:
print "IncompleteRead using requests", e
# this function is old and I categorized it as ...
# "at least it works darnnit!", but I would really like to
# learn what's happening. Please help me put this function into
# eternal rest.
def get_rss_feed(url):
response = urllib2.urlopen(url)
read_it = True
content = ''
while read_it:
try:
content += response.read(1)
except IncompleteRead:
read_it = False
return content, response.info()
content, info = get_rss_feed(url)
feed = feedparser.parse(content)
正如已经说过的,这不是一个关键任务问题,但是好奇心,即使我可以期待urllib2有这个问题,我很惊讶在机械化和请求中也遇到了这个错误。 feedparser模块甚至不会抛出错误,因此检查错误取决于是否存在'bozo_exception'键。
编辑:我只想提及wget和curl都能完美地执行该功能,每次都能正确检索完整的有效负载。我还没有找到一个纯粹的python方法,除了我丑陋的黑客,我很好奇知道httplib后端发生了什么。在百灵鸟上,我决定在前几天用斜纹试试这个,并得到相同的httplib错误。
P.S。有一件事让我觉得很奇怪。 IncompleteRead始终在有效负载中的两个断点之一处发生。看起来feedsparser和请求在读取926个字节后失败,但机械化和urllib2在读取1854个字节后失败。这种行为是有害的,我没有解释或理解。
答案 0 :(得分:25)
在一天结束时,所有其他模块(feedparser
,mechanize
和urllib2
)都会调用httplib
,这是抛出异常的地方。
现在,首先,我还用wget下载了这个,结果文件是1854字节。接下来,我尝试使用urllib2
:
>>> import urllib2
>>> url = 'http://hattiesburg.legistar.com/Feed.ashx?M=Calendar&ID=543375&GUID=83d4a09c-6b40-4300-a04b-f88884048d49&Mode=2013&Title=City+of+Hattiesburg%2c+MS+-+Calendar+(2013)'
>>> f = urllib2.urlopen(url)
>>> f.headers.headers
['Cache-Control: private\r\n',
'Content-Type: text/xml; charset=utf-8\r\n',
'Server: Microsoft-IIS/7.5\r\n',
'X-AspNet-Version: 4.0.30319\r\n',
'X-Powered-By: ASP.NET\r\n',
'Date: Mon, 07 Jan 2013 23:21:51 GMT\r\n',
'Via: 1.1 BC1-ACLD\r\n',
'Transfer-Encoding: chunked\r\n',
'Connection: close\r\n']
>>> f.read()
< Full traceback cut >
IncompleteRead: IncompleteRead(1854 bytes read)
所以它正在读取所有1854个字节,但后来认为还有更多。如果我们明确地告诉它只读取1854个字节就可以了:
>>> f = urllib2.urlopen(url)
>>> f.read(1854)
'\xef\xbb\xbf<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">...snip...</rss>'
显然,这只有在我们提前知道确切长度的情况下才有用。我们可以使用部分读取作为异常的属性返回以捕获整个内容的事实:
>>> try:
... contents = f.read()
... except httplib.IncompleteRead as e:
... contents = e.partial
...
>>> print contents
'\xef\xbb\xbf<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">...snip...</rss>'
This blog post表明这是服务器的错误,并描述了如何使用上面的httplib.HTTPResponse.read()
块对try..except
方法进行修补以处理幕后的事情:
import httplib
def patch_http_response_read(func):
def inner(*args):
try:
return func(*args)
except httplib.IncompleteRead, e:
return e.partial
return inner
httplib.HTTPResponse.read = patch_http_response_read(httplib.HTTPResponse.read)
我应用了补丁,然后feedparser
工作了:
>>> import feedparser
>>> url = 'http://hattiesburg.legistar.com/Feed.ashx?M=Calendar&ID=543375&GUID=83d4a09c-6b40-4300-a04b-f88884048d49&Mode=2013&Title=City+of+Hattiesburg%2c+MS+-+Calendar+(2013)'
>>> feedparser.parse(url)
{'bozo': 0,
'encoding': 'utf-8',
'entries': ...
'status': 200,
'version': 'rss20'}
这不是最好的做事方式,但似乎有效。我在HTTP协议方面不够专业,无法确定服务器是否出错,或者httplib
是否错误处理边缘情况。
答案 1 :(得分:6)
我发现在我的情况下,发送HTTP / 1.0请求,解决问题,只需将其添加到代码中:
import httplib
httplib.HTTPConnection._http_vsn = 10
httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0'
在我提出请求后:
req = urllib2.Request(url, post, headers)
filedescriptor = urllib2.urlopen(req)
img = filedescriptor.read()
我回到http 1.1后(对于支持1.1的连接):
httplib.HTTPConnection._http_vsn = 11
httplib.HTTPConnection._http_vsn_str = 'HTTP/1.1'
答案 2 :(得分:0)
我通过使用HTTPS而不是HTTP来解决此问题,并且可以正常工作。无需更改代码。