IncompleteRead с использованием httplib
У меня возникла постоянная проблема с получением 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 будет иметь эту проблему, я удивлен, что эта ошибка встречается и в механизации, и в запросах. Модуль feedparser даже не выдает ошибку, поэтому проверка на наличие ошибок зависит от наличия ключа "bozo_exception".
Редактировать: я просто хотел упомянуть, что и wget, и curl выполняют функцию безупречно, каждый раз корректно извлекая всю полезную нагрузку. Я еще не нашел чистого метода Python для работы, за исключением моего уродливого хака, и мне очень любопытно узнать, что происходит на сервере httplib. Как-то так, я решил попробовать это с твилом на днях и получил ту же ошибку httplib.
PS Есть одна вещь, которая также кажется мне очень странной. IncompleteRead происходит последовательно в одной из двух точек останова в полезной нагрузке. Кажется, что feedparser и запросы перестают работать после чтения 926 байтов, но механизация и urllib2 терпят неудачу после чтения 1854 байтов. Такое поведение является последовательным, и я остаюсь без объяснения или понимания.
2 ответа
В конце дня все остальные модули (feedparser
, mechanize
, а также urllib2
) вызов 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>'
Этот пост в блоге предполагает, что это ошибка сервера, и описывает, как обезьяна-патч 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
неправильно обрабатывает крайний случай.
Я выясняю, в моем случае, отправить запрос 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'
Я исправил проблему, используя HTTPS вместо HTTP, и он работает нормально. Изменение кода не требуется.