2013-01-03 28 views
21

我一直有一個持續的問題,從一個特定的網站獲取RSS源。我最終寫了一個相當醜陋的過程來執行這個功能,但我很好奇爲什麼會發生這種情況,以及是否有更高級別的接口能夠正確處理這個問題。這個問題並不是真正的表現障礙,因爲我不需要經常檢索feed。使用httplib的IncompleteRead

我已閱讀捕獲該異常和返回部分內容,但因爲不完全讀出在被實際檢索的字節數不同,我沒有肯定,這樣的解決方案將實際工作的解決方案。

#!/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和捲曲完美執行的功能,每次正確檢索全部有效載荷。我還沒有找到一種純粹的python方法來工作,除了我的醜陋的黑客攻擊之外,我很想知道httplib的後端發生了什麼。在雲雀,我決定也嘗試這與twill有一天,並得到相同的httplib錯誤。

P.S.有一件事讓我覺得很奇怪。 IncompleteRead始終發生在有效負載的兩個斷點之一處。看起來feedparser和請求在讀取926個字節後失敗,但在讀取1854個字節後機械化和urllib2失敗。這種行爲是一致的,我沒有解釋或理解。

回答

23

在一天結束時,所有其它模塊(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表明,這是服務器的故障,並介紹瞭如何猴子修補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是否處理邊緣情況。

+0

雖然我同意這不是一個好的做事方式,但它肯定是很多比我使用的方法更好。 (我真的需要更經常地使用裝飾器)。我不是HTTP協議的專家,也不是httplib是否正確處理這個問題,這就是爲什麼我覺得這可能是一個很好的問題。 FWIW,本網站上的其他所有網頁都能正常運行,並且只有在訪問rss網址時纔會在其http服務器上發生此問題。 – umeboshi

+0

@umeboshi - 也許它與響應的內容類型有關,即服務器被配置的方式'text/html'響應正常工作,但是'text/xml'不響應?如果沒有更全面的答案出現,你總是可以嘗試將這個問題發佈到Python郵件列表,看看有沒有人可以給出診斷。 – Blair

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' 
+0

也適合我!非常感謝! 你知道爲什麼會發生這種情況嗎?對於不完整的讀取,1.0有什麼特別之處? –

+0

你強制舊的連接類型,你強制不使用一個http 1.1的能力,就像讀取塊,應該經常發生,當你嘗試下載較大的文件... –

+0

並非所有的服務器接受http 1.0 - 我從其中之一獲得404。 –