2013-01-03 8 views
21

Ho avuto un problema persistente ottenere un feed RSS da un sito Web particolare. Ho finito per scrivere una procedura piuttosto brutta per eseguire questa funzione, ma sono curioso di sapere perché questo accade e se le interfacce di livello superiore gestiscono correttamente questo problema. Questo problema non è in realtà uno stopper, dal momento che non ho bisogno di recuperare il feed molto spesso.IncompleteRead using httplib

Ho letto una soluzione che intercetta l'eccezione e restituisce il contenuto parziale, eppure poiché le letture incomplete differiscono nella quantità di byte che vengono effettivamente recuperati, non ho la certezza che tale soluzione funzioni effettivamente.

#!/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) 

Come già detto, questo non è un problema mission critical, ma una curiosità, come anche se posso aspettarmi urllib2 ad avere questo problema, sono sorpreso che si incontra questo errore in mechanize e le richieste pure . Il modulo feedparser non lancia nemmeno un errore, quindi il controllo degli errori dipende dalla presenza di una chiave "bozo_exception".

Modifica: volevo solo dire che sia wget che curl eseguono perfettamente la funzione, recuperando il payload completo correttamente ogni volta. Devo ancora trovare un metodo Python puro per funzionare, tranne il mio brutto trucco, e sono molto curioso di sapere cosa sta succedendo sul backend di httplib. Su un'allodola, ho deciso di provare anche questo con twill l'altro giorno e ho ottenuto lo stesso errore httplib.

P.S. C'è anche una cosa che mi sembra molto strana. IncompleteRead si verifica in modo coerente in uno dei due punti di interruzione nel payload. Sembra che feedparser e le richieste non riescano dopo aver letto 926 byte, eppure meccanize e urllib2 falliscono dopo aver letto 1854 byte. Questo comportamento è coerente, e sono rimasto senza spiegazione o comprensione.

risposta

23

Alla fine della giornata, tutti gli altri moduli (feedparser, mechanize e urllib2) chiama httplib che è dove viene generata l'eccezione.

Ora, per prima cosa, ho scaricato anche questo con wget e il file risultante era 1854 byte. Poi, ho provato con 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) 

Così sta leggendo tutti i 1854 byte, ma poi pensa che ci sia più a venire. Se noi diciamo esplicitamente a leggere solo 1854 bytes funziona:

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

Ovviamente, questo è utile solo se sappiamo sempre la lunghezza esatta prima del tempo. Possiamo usare il fatto che la lettura parziale viene restituito come un attributo eccezione per catturare l'intero contenuto:

>>> 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 suggerisce che questo è un difetto del server, e descrive come scimmia-patch metodo httplib.HTTPResponse.read() con il blocco try..except sopra per gestire le cose dietro le quinte:

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) 

ho applicato la patch e poi feedparser lavorato:

>>> 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'} 

Questo non è il modo più bello di fare le cose, ma sembra funzionare. Non sono abbastanza esperto nei protocolli HTTP per dire con certezza se il server sta facendo le cose sbagliate, o se httplib sta gestendo male un caso limite.

+0

Mentre sono d'accordo che non è un bel modo di fare le cose, è certamente molto meglio del metodo che stavo usando. (Ho davvero bisogno di esercitarmi con i decoratori più spesso). Non sono nemmeno un esperto dei protocolli HTTP, né se httplib lo tratti correttamente o meno, motivo per cui ho ritenuto che questa potesse essere una buona domanda da porre. FWIW, ogni altra pagina di questo sito funziona bene, ed è solo quando si accede all'url di rss che questo problema si verifica sul proprio server http. – umeboshi

+0

@umeboshi - forse è qualcosa a che fare con il tipo di contenuto della risposta, cioè il modo in cui il server è configurato per le risposte 'text/html' funzionano bene ma' text/xml' no? Se non vengono visualizzate risposte più complete, puoi sempre provare a postare questa domanda alla mailing list di Python e vedere se qualcuno può dare una diagnosi. – Blair

6

scopro nel mio caso, inviare un/1.0 richiesta HTTP, risolvere il problema, semplicemente aggiungendo questo codice:

import httplib 
httplib.HTTPConnection._http_vsn = 10 
httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0' 

dopo faccio la domanda:

req = urllib2.Request(url, post, headers) 
filedescriptor = urllib2.urlopen(req) 
img = filedescriptor.read() 

dopo mi torna a HTTP 1.1 con (per le connessioni che supportano 1.1):

httplib.HTTPConnection._http_vsn = 11 
httplib.HTTPConnection._http_vsn_str = 'HTTP/1.1' 
+0

Ha funzionato anche per me! Molte grazie! Hai idea del perché questo sta accadendo? Cosa c'è di così speciale in 1.0 per letture incomplete? –

+0

si forza il vecchio tipo di connessione, si forza non utilizzare una capacità http 1.1 come leggere in blocchi, dovrebbe accadere spesso quando si tenta di scaricare file più grandi ... –

+0

Non tutti i server accettano http 1.0 - Sto ottenendo 404 da uno di essi. –