web-dev-qa-db-ja.com

httplibを使用したIncompleteRead

特定のWebサイトから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エラーが発生しました。

追伸また、非常に奇妙なことに私を襲うことが1つあります。 IncompleteReadは、ペイロードの2つのブレークポイントの1つで一貫して発生します。 926バイトを読み取った後、feedparserとリクエストが失敗するようですが、mechanizeとurllib2は1854バイトを読み取った後に失敗します。この行動は一貫しており、私は説明も理解もされずに残されています。

25
umeboshi

1日の終わりに、他のすべてのモジュール(feedparsermechanize、および_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>'
_

このブログ投稿 は、これがサーバーの障害であることを示唆しており、_try..except_ブロックを使用してhttplib.HTTPResponse.read()メソッドにモンキーパッチを適用し、舞台裏で処理する方法を説明しています:

_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がEdgeケースを誤って処理しているかどうかを確認できません。

26
Blair

私の場合、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()

(1.1をサポートする接続の場合)でhttp 1.1に戻った後:

httplib.HTTPConnection._http_vsn = 11
httplib.HTTPConnection._http_vsn_str = 'HTTP/1.1'
7
Sérgio

HTTPの代わりにHTTPSを使用して問題を修正し、正常に動作しました。コードを変更する必要はありませんでした。

0
Abdul Majeed