J'ai eu un problème persistant pour obtenir un flux rss à partir d'un site Web particulier. J'ai fini par écrire une procédure plutôt moche pour exécuter cette fonction, mais je suis curieux de savoir pourquoi cela se produit et si des interfaces de niveau supérieur gèrent correctement ce problème. Ce problème n'est pas vraiment un bouchon d'exposition, car je n'ai pas besoin de récupérer le flux très souvent.
J'ai lu une solution qui intercepte l'exception et renvoie le contenu partiel, mais comme les lectures incomplètes diffèrent dans la quantité d'octets réellement récupérés, je n'ai aucune certitude qu'une telle solution fonctionnera réellement.
#!/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)
Comme déjà indiqué, ce n'est pas un problème critique pour la mission, mais une curiosité, car même si je peux m'attendre à ce que urllib2 ait ce problème, je suis surpris que cette erreur se rencontre également dans la mécanisation et les demandes. Le module feedparser ne génère même pas d'erreur, donc la recherche d'erreurs dépend de la présence d'une clé 'bozo_exception'.
Edit: Je voulais juste mentionner que wget et curl exécutent parfaitement la fonction, récupérant correctement la charge utile à chaque fois. Je n'ai pas encore trouvé de méthode pure python pour travailler, à l'exception de mon vilain hack, et je suis très curieux de savoir ce qui se passe sur le backend de httplib. Sur une alouette, j'ai décidé de essayez cela avec du sergé l'autre jour et obtenez la même erreur httplib.
P.S. Il y a une chose qui me semble également très étrange. IncompleteRead se produit de manière cohérente à l'un des deux points d'arrêt de la charge utile. Il semble que feedparser et les requêtes échouent après la lecture de 926 octets, mais la mécanisation et urllib2 échouent après la lecture de 1854 octets. Ce comportement est consistant, et je me retrouve sans explication ni compréhension.
À la fin de la journée, tous les autres modules (feedparser
, mechanize
et urllib2
) Appellent httplib
, où l'exception est levée .
Maintenant, tout d'abord, j'ai également téléchargé ceci avec wget et le fichier résultant était de 1854 octets. Ensuite, j'ai essayé avec 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)
Il lit donc tous les 1854 octets mais pense qu'il y a plus à venir. Si nous lui demandons explicitement de ne lire que 1854 octets, cela fonctionne:
>>> 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>'
Évidemment, cela n'est utile que si nous connaissons toujours la longueur exacte à l'avance. Nous pouvons utiliser le fait que la lecture partielle est renvoyée comme attribut sur l'exception pour capturer l'intégralité du contenu:
>>> 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>'
Ce billet de blog suggère que c'est une faute du serveur et décrit comment patcher la méthode httplib.HTTPResponse.read()
avec le bloc try..except
Ci-dessus pour gérer les choses derrière le scènes:
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)
J'ai appliqué le patch puis feedparser
a travaillé:
>>> 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'}
Ce n'est pas la meilleure façon de faire les choses, mais cela semble fonctionner. Je ne suis pas assez expert dans les protocoles HTTP pour dire avec certitude si le serveur fait mal les choses, ou si httplib
gère mal un cas Edge.
Je découvre dans mon cas, envoie une demande HTTP/1.0, corrige le problème, en ajoutant simplement ceci au code:
import httplib
httplib.HTTPConnection._http_vsn = 10
httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0'
après avoir fait la demande:
req = urllib2.Request(url, post, headers)
filedescriptor = urllib2.urlopen(req)
img = filedescriptor.read()
après je reviens à http 1.1 avec (pour les connexions qui prennent en charge 1.1):
httplib.HTTPConnection._http_vsn = 11
httplib.HTTPConnection._http_vsn_str = 'HTTP/1.1'
J'ai résolu le problème en utilisant HTTPS au lieu de HTTP et son bon fonctionnement. Aucun changement de code n'était requis.