Requests: gzip圧縮された応答のストリーミング

作成日 2014年07月31日  ·  10コメント  ·  ソース: psf/requests

大きなXML応答をストリームとして処理する必要があります。 圧縮されていない応答のサイズは数百メガバイトになる可能性があるため、XMLパーサーに渡す前に完全にメモリにロードすることはできません。

私はlxmlを使用して解析し、リクエストドキュメントのどこかに説明されているように、 response.rawをそのiterparse()関数に渡すだけです。 これは、圧縮されていない応答に対しては正常に機能します。

残念ながら、私が呼び出しているAPIは特に優れていません。 そのため、圧縮されていないデータを明示的に要求した場合でも、 Content-Encoding: gzipが返されることがあります。 また、これらの非常に反復的で冗長なXMLファイルの圧縮率は非常に優れている(10x +)ので、圧縮された応答を利用したいと思います。

これはリクエストで可能ですか? ドキュメントで見つかりませんでした。 urllib3を詳しく調べてみると、そのHTTPResponse.read()メソッドはdecode_contentパラメーターをサポートしているようです。 設定されていない場合、urllib3はコンストラクターで設定されたものにフォールバックします。 requestsがrequests.adapters.HTTPAdapter.send()でコンストラクターを呼び出すと、 decode_contentを明示的にFalseに設定します。

リクエストがそれを行う理由はありますか?

不思議なことに、 iter_content()実際に読み取り中にdecode_content=Trueを設定します。 なぜここに? それはすべて少し恣意的に見えます。 私は、ここと別の方法でそれを行う動機を本当に理解していません。
個人的には、lxmlにファイルのようなオブジェクトが必要なため、もちろんiter_content()を実際に使用することはできません。

以前、リクエストとlxmlの間にフックできる独自のファイルのようなオブジェクトを作成しましたが、もちろんバッファリングは難しく、以前に作成したよりも賢い人のように感じるので、自分でロールする必要はありません。 。

これを処理する方法についてのあなたのアドバイスは何ですか? リクエストをデフォルトに変更して、urllib3でdecode_content=Trueを設定する必要がありますか?

Contributor Friendly Documentation Planned

最も参考になるコメント

私は過去にこれをしました

r = requests.get('url', stream=True)
r.raw.decode_content = True
...

全てのコメント10件

いいえ、さまざまな理由でデフォルトで設定するべきではありません。 あなたがすべきことは、 functools.partialを使用して、応答のreadメソッドを置き換える(または単に別の方法でラップする)ことです。これにより、次のようなことができます。

response.raw.read = functools.partial(response.raw.read, decode_content=True)

次に、 response.rawをパーサーに渡します。

@ sigmavirus24ありがとう、それは間違いなく私が上で概説した問題に対するエレガントな解決策です!

これをリクエストのドキュメントに追加することをお勧めします。たとえば、FAQ: http: //docs.python-requests.org/en/latest/community/faq/#encoded -data
現在、「リクエストはgzipでエンコードされた応答を自動的に解凍します」というステートメントは、 stream=True場合には正しくなく、驚きにつながる可能性があります。

私の問題については、 urllib3の問題について読んだように、gzip解凍のurllib3実装には、コードで回避する必要のある独自の小さな癖がありますが、それはリクエストの問題ではなくなりました。

しかし、それはもはやリクエストの問題ではありません。

あなたのようにこれは閉じることができると思いますか?

@ sigmavirus24現在のドキュメントが正しくないため、ドキュメント化する必要があると思います。

しかし、あなたがそれに同意しないなら、はい、すぐに!

ドキュメントはより明確になる可能性があります。 私にとって(そしてこれは完全に私がコア開発者であるためです)、最初の段落は生の応答に決して触れないユーザーの90%に話しますが、2番目の段落は最初の段落と矛盾します。生データ、それはあなたのためにあります。」 私が言ったように、それは私には明らかですが、それがどのように明確にされることができるかを見ることができます。 今夜その作業をします。

私にとっては、「生データ」を「生ペイロード」、つまり解凍されたストリームとして解釈したほうがよいでしょう。 必要なチャンクでそれを読まなければなりません。 解凍されたblobである.contentとは対照的です(ペイロードでもありますが、形式が異なります)。

実際の解凍は、私にとってHTTPライブラリの懸念のように感じます。HTTPの実装の詳細は、要求が抽象化されることを期待しています。 リクエストからペイロードをストリームとして読み取るか、プリフェッチされたデータのBLOBとして読み取るかは、違いはありません。 いずれにせよ、リクエストは実装の詳細「圧縮」を抽象化します。

(この仮定は、デフォルトのdecode_contentTrueに設定するという私の最初の要求の中核でもありました。もちろん、これがリークのある抽象化であることがわかったので、それを示唆しなくなりました。)

しかし、ええ、私はあなたのユーザーの99%がこの詳細によって影響を受けることは決してないことに絶対に同意します。

この問題はお気軽に終了してください。

したがって、これは実際には、しばらくの間頭の中でガタガタと音を立てていて、APIの大幅な変更になるため、まだ提案していないものにつながります。

r.raw使用することをお勧めするという事実は、私たちが文書化していないオブジェクトであり、 urllib3によって提供されるオブジェクトであるため(過去に主張したことですが)、私は好きではありません。実装の詳細)。 そのことを念頭に置いて、 urllib3メソッドにプロキシするだけのResponseオブジェクトにメソッドを提供するというアイデアをいじっています( readraw.readプロキシするだけです) urllib3柔軟性が高まり、(ユーザーに代わって) urllib3 APIの変更を処理できるようになります(これは歴史的にほとんど問題にならなかったため、問題はありません。その緊急性)。

そうは言っても、私の意見では、Responseオブジェクトにはすでに十分なメソッドがあり、APIを拡張することは理想的ではありません。 最良のAPIは、削除するものが何もないAPIです。 ですから、私はこれについて絶えず危機に瀕しています。


この仮定は、デフォルトのdecode_contentをTrueにするという私の最初の要求の中核でもありました。 もちろん、これがリークのある抽象化であることがわかったので、私はもはやそれを示唆していません。

これを見つけて、なぜこれが真実であるかわからないかもしれない他の人のために、私に説明させてください。

自動解凍をオフにして応答の長さを検証したり、その他の重要なことを実行したりするリクエストのユーザーが何人かいます。 前者の種類の1つのコンシューマーはOpenStackです。 OpenStackクライアントの多くは、クライアントに送信されたContent-Lengthヘッダーと、受信した本体の実際の長さを検証します。 彼らにとって、減圧を処理することは、彼らが有効な応答を受け取って処理していることを確認するための公正なトレードオフです。

もう1つのコンシューマーはBetamax(または実際にはResponseオブジェクトを(再)構築するツール)です。これは、完全に有効な応答を行うプロセス全体を処理する場合、コンテンツが圧縮形式である必要があるためです。

@Lukasaも私も

今日同じ問題にぶつかり、現時点で応答をストリーミングする他の方法がないため、同じ仮定をすることになりました。

Responseで複数の新しいメソッドを使用するのではなく、 .rawへのプロキシと同じ役割を果たすresponse.streamなどの単一の新しい属性を使用しないのはなぜですか。 また、 stream=True設定/パラメーターを適切に反映し、現在の.raw動作を必要とするユーザーには影響しません。

私は過去にこれをしました

r = requests.get('url', stream=True)
r.raw.decode_content = True
...

@ sigmavirus24による回避策は、 tellメソッドのセマンティクスを壊し、誤ったオフセットを返すことに注意してください

応答を再開可能なアップロードとしてGoogleCloud Storage APIにストリーミングしているときに、これに遭遇しました。GoogleCloudStorage APIは、 tell()を使用して、読み取られたばかりのバイト数を計算します(ここ)。

このページは役に立ちましたか?
0 / 5 - 0 評価