2013-01-03 11 views
21

Mam stały problem z uzyskaniem kanału RSS z konkretnej witryny. Powstaje dość brzydka procedura wykonywania tej funkcji, ale jestem ciekawy, dlaczego tak się dzieje i czy jakiekolwiek interfejsy wyższego poziomu rozwiązują ten problem prawidłowo. Ten problem nie jest tak naprawdę zakończeniem pokazu, ponieważ nie muszę często pobierać kanału.NiekompletnePrzeczytaj używając httplib

Przeczytałem rozwiązanie, które przechwytuje wyjątek i zwraca częściową zawartość, ale ponieważ niecałkowite odczyty różnią się ilością faktycznie pobranych bajtów, nie mam pewności, że takie rozwiązanie faktycznie zadziała.

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

Jak już wspomniano, nie jest to błąd krytyczny, misja, jeszcze ciekawostka, jak chociaż mogę się spodziewać urllib2 mieć ten problem, jestem zaskoczony, że ten błąd zostanie napotkany w mechanize i wniosków, a także . Moduł feedparser nie generuje nawet błędu, więc sprawdzenie błędów zależy od obecności klucza "bozo_exception".

Edycja: Chciałem tylko wspomnieć, że zarówno wget, jak i curl wykonują funkcję bezbłędnie, pobierając poprawnie cały ładunek za każdym razem. Muszę jeszcze znaleźć czystą metodę Pythona do pracy, z wyjątkiem mojego brzydkiego hacka, i jestem bardzo ciekawy, aby wiedzieć, co dzieje się na zapleczu httplib. Na skowronku, postanowiłem spróbować tego również skandować drugiego dnia i otrzymałem ten sam błąd httplib.

P.S. Jest jedna rzecz, która również uderza mnie jako bardzo dziwną. Funkcja IncompleteRead działa konsekwentnie na jednym z dwóch punktów przerwania w polu danych. Wygląda na to, że feedparser i żądania kończą się niepowodzeniem po przeczytaniu 926 bajtów, ale mechanize i urllib2 zawodzą po przeczytaniu 1854 bajtów. Takie zachowanie jest zgodne i pozostaję bez wyjaśnienia lub zrozumienia.

Odpowiedz

23

Na koniec dnia, wszystkie inne moduły (feedparser, mechanize i urllib2) Połączenie httplib który gdzie wyjątek jest wyrzucane.

Teraz, po pierwsze, również pobrałem to za pomocą wget, a wynikowy plik miał 1854 bajty. Następnie próbowałem z 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) 

Więc to czyta wszystkie 1854 bajty ale uważa, że ​​istnieje więcej. Jeśli mamy wyraźnie poinformować go czytać tylko 1854 bajtów to działa:

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

Oczywiście, jest to tylko użyteczne, jeśli zawsze wiemy dokładną długość wyprzedzeniem. Możemy wykorzystać fakt częściowego odczytu jest zwracany jako atrybut na wyjątek, aby uchwycić całą zawartość:

>>> 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 wskazuje jest to wina serwera i opisuje jak małpa patch metody httplib.HTTPResponse.read() z blok try..except powyżej obsłużyć rzeczy za kulisami:

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) 

Zgłosiłem poprawkę, a następnie feedparser pracował:

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

To nie jest najmilszy sposób robienia rzeczy, ale wygląda na to, że działa. Nie jestem wystarczająco ekspertem w protokołach HTTP, aby stwierdzić z całą pewnością, czy serwer robi coś złego, czy też to, że httplib źle posługuje się przypadkiem brzegowym.

+0

Chociaż zgadzam się, że nie jest to dobry sposób robienia rzeczy, to z pewnością dużo lepsze niż metoda, z której korzystałem. (Naprawdę muszę ćwiczyć częściej używając dekoratorów). Nie jestem też ekspertem od protokołów HTTP, ani też, czy httplib traktuje to poprawnie czy nie, i dlatego uznałem, że może to być dobre pytanie. FWIW, każda inna strona na tej stronie działa dobrze, i to tylko podczas uzyskiwania dostępu do rss url, że ten problem występuje na ich serwerze http. – umeboshi

+0

@umeboshi - może jest to coś związanego z typem zawartości odpowiedzi, tj. Sposób, w jaki skonfigurowano serwer odpowiedzi 'text/html' działa dobrze, ale' text/xml' nie działa? Jeśli nie pojawią się bardziej wyczerpujące odpowiedzi, zawsze możesz spróbować umieścić to pytanie na liście dyskusyjnej Pythona i sprawdzić, czy ktoś może podać diagnozę. – Blair

6

dowiem się w moim przypadku wysyłania HTTP/1.0 prośbę, rozwiązać problem, wystarczy dodać to do kodu:

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

po robię żądanie:

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

po Wracam do http 1.1 z (dla połączeń obsługujących 1.1):

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

Pracował także dla mnie! Wielkie dzięki! Czy masz pojęcie, dlaczego tak się dzieje? Co jest takiego specjalnego w wersji 1.0, jeśli nie czyta się czegoś w ogóle? –

+0

wymuszasz stary typ połączenia, nie używaj jednej funkcji http 1.1 czegoś w rodzaju odczytu w porcjach, powinno się zdarzać często, gdy próbujesz pobrać większe pliki ... –

+0

Nie wszystkie serwery akceptują http 1.0 - otrzymuję 404 z jednego z nich. –