Подтвердить что ты не робот

Неполное чтение с помощью 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 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. На жаворонке я решил также попробовать это с twill на днях и получил ту же ошибку httplib.

P.S. Есть одна вещь, которая также поражает меня как очень странную. IncompleteRead последовательно выполняется на одной из двух точек останова в полезной нагрузке. Кажется, что feedparser и запросы терпят неудачу после прочтения 926 байт, но механизация и urllib2 завершаются после прочтения 1854 байта. Такое поведение является последовательным, и я остаюсь без объяснения или понимания.

4b9b3361

Ответ 1

В конце дня все остальные модули (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 неправильно обрабатывает регистр.

Ответ 2

В моем случае я узнаю, отправьте запрос 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'