【问题标题】:IncompleteRead using httplib使用 httplib 读取不完整
【发布时间】:2012-12-18 10:00:18
【问题描述】:

我一直在从特定网站获取 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 有这个问题,我很惊讶在 mechanize 和 requests 中也会遇到这个错误。 feedparser 模块甚至不会抛出错误,因此检查错误取决于是否存在“bozo_exception”键。

编辑:我只想提一下 wget 和 curl 都完美地执行了该功能,每次都能正确检索完整的有效负载。我还没有找到一个纯 python 方法来工作,除了我丑陋的 hack,我很想知道 httplib 后端发生了什么。巧的是,前几天我决定用斜纹布也试试这个,得到了同样的 httplib 错误。

附:还有一件事也让我觉得很奇怪。 IncompleteRead 始终发生在有效负载中的两个断点之一。似乎 feedparser 和 requests 在读取 926 个字节后失败,但 mechanize 和 urllib2 在读取 1854 个字节后失败。这种行为是一致的,我没有解释或理解。

【问题讨论】:

    标签: python feedparser httplib


    【解决方案1】:

    在一天结束时,所有其他模块(feedparsermechanizeurllib2)都会调用 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 表明这是服务器的故障,并描述了如何使用上面的 try..except 块对 httplib.HTTPResponse.read() 方法进行猴子补丁以处理幕后的事情:

    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 是否错误处理了边缘情况。

    【讨论】:

    • 虽然我同意这不是一种很好的做事方式,但它肯定比我使用的方法好得多。 (我真的需要更频繁地练习使用装饰器)。我也不是 HTTP 协议方面的专家,也不是 httplib 是否正确处理这个问题,这就是为什么我觉得这可能是一个很好的问题。 FWIW,此站点上的所有其他页面都运行良好,只有在访问 rss url 时,此问题才会在其 http 服务器上发生。
    • @umeboshi - 也许它与响应的内容类型有关,即服务器的配置方式 text/html 响应工作正常,但 text/xml 不行?如果没有更全面的答案出现,您可以随时尝试将此问题发布到 Python 邮件列表,看看那里是否有人可以给出诊断。
    【解决方案2】:

    我发现在我的情况下,发送一个 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'
    

    【讨论】:

    • 也为我工作!非常感谢!你知道为什么会这样吗?对于不完整的读取,1.0 有什么特别之处?
    • 你强制使用旧的连接类型,你强制不使用一个 http 1.1 功能,比如分块读取,当你尝试下载更大的文件时应该经常发生......
    • 并非所有服务器都接受 http 1.0 - 我从其中一个服务器获得 404。
    【解决方案3】:

    我已经通过使用 HTTPS 而不是 HTTP 解决了这个问题,并且它工作正常。无需更改代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-19
      • 1970-01-01
      • 2011-09-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多