こうかの雑記

こうかの雑記

昔の懐かしいこと、ubuntuのこと、その他いろいろ

python - RequestsのAdvanced Usage、日本語訳

 昨年pythonスクレイピングする必要がありRequestsを使いました。 その際に参考したサイトが『Requests: 人間のためのHTTP』 で、Requestの理解に役立ちました。 このサイト中のクイックスタートは日本語になっていたのですが、"Advanced Usage"は日本語になっていませんでした。

 今回、思いついたことが有って再びrequestを使ってみたいと思いましたのでGoogle翻訳で訳してみました。 以下に紹介します。

 尚、pythonの記述部分は2.x対応のようです。 print文をprint()関数に直せばpython3で試すことが出来ます。 あえて直さずに原文のままとしました。

 翻訳はほぼGoogle翻訳任せで、わずかに手を入れただけですのでおかしいところがあるかもしれません。その場合は原文を確かめて下さい。

jp.python-requests.org


高度な使用法 Advanced Usage

 このドキュメントは、Requestsのより高度な機能の一部をカバーしています。

Sessionオブジェクト

 Sessionオブジェクトを使用すると、requests間で特定のパラメータを保持できます。 また、Sessionインスタンスから行われたすべてのrequestsにわたってCookieを保持します。

 セッションオブジェクトには、メインのRequests APIのすべてのメソッドがあります。

 requests間でいくつかのクッキーを保持しましょう:

s = requests.Session()

s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get("http://httpbin.org/cookies")

print r.text
# '{"cookies": {"sessioncookie": "123456789"}}'

 Sessionを使用して、requestsメソッドにデフォルトデータを提供することもできます。 これは、セッションオブジェクトのプロパティにデータを提供することで実行されます。

s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})

# both 'x-test' and 'x-test2' are sent
s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})

 要求メソッドに渡す辞書はすべて、設定されているセッションレベルの値とマージされます。 メソッドレベルのパラメーターは、セッションパラメーターをオーバーライドします。


Dictパラメーターから値を削除する

 dictパラメータからセッションレベルのキーを省略したい場合があります。 これを行うには、メソッドレベルのパラメーターでそのキーの値をNoneに設定するだけです。 自動的に省略されます。


 セッション内に含まれるすべての値は、直接利用できます。 詳細については、Session APIのドキュメントをご覧ください。

RequestsとResponseオブジェクト

 要求に対して呼び出しが行われるたびに、2つの主要なことを行います。 最初に、Requestsリソースを要求または照会するためにサーバーに送信されるRequestオブジェクトを構築しています。 次に、requestsがサーバーから応答を取得すると、Responseオブジェクトが生成されます。 responseオブジェクトには、サーバーから返されたすべての情報が含まれ、元々作成したrequestsオブジェクトも含まれています。 ウィキペディアのサーバーから非常に重要な情報を取得するための簡単なリクエストを次に示します。

>>> r = requests.get('http://en.wikipedia.org/wiki/Monty_Python')

 サーバーから返送されたヘッダーにアクセスする場合は、次のようにします。

>>> r.headers
{'content-length': '56170', 'x-content-type-options': 'nosniff', 'x-cache':
'HIT from cp1006.eqiad.wmnet, MISS from cp1010.eqiad.wmnet', 'content-encoding':
'gzip', 'age': '3080', 'content-language': 'en', 'vary': 'Accept-Encoding,Cookie',
'server': 'Apache', 'last-modified': 'Wed, 13 Jun 2012 01:33:50 GMT',
'connection': 'close', 'cache-control': 'private, s-maxage=0, max-age=0,
must-revalidate', 'date': 'Thu, 14 Jun 2012 12:59:39 GMT', 'content-type':
'text/html; charset=UTF-8', 'x-cache-lookup': 'HIT from cp1006.eqiad.wmnet:3128,
MISS from cp1010.eqiad.wmnet:80'}

 ただし、サーバーに送信したヘッダーを取得する場合は、リクエストにアクセスしてから、リクエストのヘッダーにアクセスします。

>>> r.request.headers
{'Accept-Encoding': 'identity, deflate, compress, gzip',
'Accept': '*/*', 'User-Agent': 'python-requests/0.13.1'}

SSL証明書の検証

 Webブラウザと同様に、リクエストはHTTPSリクエストのSSL証明書を検証できます。 ホストのSSL証明書を確認するには、verify引数を使用できます。

>>> requests.get('https://kennethreitz.com', verify=True)
requests.exceptions.SSLError: hostname 'kennethreitz.com' doesn't match either of '*.herokuapp.com', 'herokuapp.com'

 このドメインにはSSLが設定されていないため、失敗します。 エクセレント。でもGithubはやります。

>>> requests.get('https://github.com', verify=True)
<Response [200]>

 プライベート証明書のCA_BUNDLEファイルへのパスの検証を渡すこともできます。 REQUESTS_CA_BUNDLE環境変数を設定することもできます。  verifyをFalseに設定すると、リクエストはSSL証明書の検証を無視することもできます。

>>> requests.get('https://kennethreitz.com', verify=False)
<Response [200]>

 デフォルトでは、verifyはTrueに設定されています。 オプション検証は、ホスト証明書にのみ適用されます。  ローカル証明書ファイルをパスまたはキーと値のペアとして指定することもできます。

>>> requests.get('https://kennethreitz.com', cert=('/path/server.crt', '/path/key'))
<Response [200]>

 もし間違ったパスまたは無効な証明書を指定した場合:

>>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem')
SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib

本文コンテンツのワークフロー

 デフォルトでは、要求を行うと、応答の本文がすぐにダウンロードされます。 この動作をオーバーライドし、streamパラメーターでResponse.content属性にアクセスするまで、応答本文のダウンロードを延期できます。

tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
r = requests.get(tarball_url, stream=True)

 この時点では、応答ヘッダーのみがダウンロードされており、接続は開いたままなので、コンテンツ取得を条件付きにすることができます。

if int(r.headers['content-length']) < TOO_LONG:
  content = r.content
  ...

 Response.iter_contentおよびResponse.iter_linesメソッドを使用するか、Response.rawにある基礎となるurllib3 urllib3.HTTPResponseから読み取ることにより、ワークフローをさらに制御できます。

Keep-Alive

 素晴らしいニュース— urllib3のおかげで、キープアライブはセッション内で100%自動です! セッション内で行うリクエストは、適切な接続を自動的に再利用します!

 接続は、すべてのボディデータが読み取られた後にのみ再利用のためにプールに戻されることに注意してください。 ストリームをFalseに設定するか、Responseオブジェクトのcontentプロパティを読み取るようにしてください。

イベントフック

 Requestsには、リクエストプロセスの一部を操作したり、イベント処理を通知するために使用できるフックシステムがあります。

利用可能なフック:

Response:

  リクエストから生成されたレスポンス。

{hook_name:callback_function}辞書をhooksリクエストパラメータに渡すことで、リクエストごとにフック関数を割り当てることができます。

hooks=dict(response=print_url)

 そのコールバック関数は、データのチャンクを最初の引数として受け取ります。

def print_url(r):
    print(r.url)

 コールバックの実行中にエラーが発生すると、警告が表示されます。

 コールバック関数が値を返す場合、渡されたデータを置き換えるものと見なされます。関数が何も返さない場合、他には何も影響しません。

 実行時にいくつかのリクエストメソッドの引数を出力しましょう:

>>> requests.get('http://httpbin.org', hooks=dict(response=print_url))
http://httpbin.org
<Response [200]>

カスタム認証

 Requestsを使用すると、独自の認証メカニズムを指定できます。  リクエストメソッドにauth引数として渡されるcallableには、ディスパッチされる前にリクエストを変更する機会があります。  認証実装は、requests.auth.AuthBaseのサブクラスであり、簡単に定義できます。 Requestsは、requests.authに2つの一般的な認証スキームの実装、HTTPBasicAuthとHTTPDigestAuthを提供します。  X-Pizzaヘッダーがパスワード値に設定されている場合にのみ応答するWebサービスがあるとします。 ありそうにないが、それだけで行く。

from requests.auth import AuthBase

class PizzaAuth(AuthBase):
    """Attaches HTTP Pizza Authentication to the given Request object."""
    def __init__(self, username):
        # setup any auth-related data here
        self.username = username

    def __call__(self, r):
        # modify and return the request
        r.headers['X-Pizza'] = self.username
        return r

 次に、Pizza Authを使用してリクエストを作成できます。

>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
<Response [200]>

ストリーミングリクエス

 requests.Response.iter_lines()を使用すると、Twitter Streaming APIなどのストリーミングAPIを簡単に反復処理できます。

 Twitter Streaming APIを使用して、キーワード「リクエスト」を追跡するには:

import requests
import json

r = requests.post('https://stream.twitter.com/1/statuses/filter.json',
    data={'track': 'requests'}, auth=('username', 'password'), stream=True)

for line in r.iter_lines():
    if line: # filter out keep-alive new lines
        print json.loads(line)

プロキシ

 プロキシを使用する必要がある場合は、任意のリクエストメソッドのプロキシ引数を使用して個々のリクエストを設定できます。

import requests

proxies = {
  "http": "10.10.1.10:3128",
  "https": "10.10.1.10:1080",
}

requests.get("http://example.org", proxies=proxies)

 環境変数HTTP_PROXYおよびHTTPS_PROXYによってプロキシを構成することもできます。

$ export HTTP_PROXY="10.10.1.10:3128"
$ export HTTPS_PROXY="10.10.1.10:1080"
$ python
>>> import requests
>>> requests.get("http://example.org")

 プロキシでHTTP基本認証を使用するには、http://user:password@host/構文を使用します。

proxies = {
    "http": "http://user:pass@10.10.1.10:3128/",
}

コンプライアンス

 Requestは、すべての関連する仕様とRFCに準拠することを目的としており、その準拠はユーザーに問題を引き起こしません。 仕様に対するこの注意は、関連する仕様に精通していない人にとっては異常に見えるかもしれないいくつかの動作につながる可能性があります。

エンコーディング

 応答を受信すると、Requestsは、Response.textメソッドを呼び出したときに応答のデコードに使用するエンコードを推測します。 要求は最初にHTTPヘッダーのエンコーディングをチェックし、エンコーディングが存在しない場合は、charadeを使用してエンコーディングを推測しようとします。

 Requestsがこれを行わないのは、HTTPヘッダーに明示的な文字セットが存在せず、Content-Typeヘッダーにテキストが含まれている場合のみです。 この状況では、RFC 2616はデフォルトの文字セットがISO-8859-1でなければならないことを指定しています。 この場合、要求は仕様に従います。 別のエンコードが必要な場合は、Response.encodingプロパティを手動で設定するか、生のResponse.contentを使用できます。

HTTP動詞

 Requestを使用すると、GET、OPTIONS、HEAD、POST、PUT、PATCH、DELETEなどのほぼすべてのHTTP動詞にアクセスできます。 以下に、GitHub APIを使用して、Requestでこれらのさまざまな動詞を使用する詳細な例を示します。

 最も一般的に使用される動詞GETから始めます。 HTTP GETは、指定されたURLからリソースを返すべきidempotentメソッドです。 その結果、Webロケーションからデータを取得しようとするときに使用すべき動詞です。 使用例としては、GitHubから特定のコミットに関する情報を取得しようとしています。 Requestでa050fafをコミットしたいとします。 次のようになります。

>>> import requests
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')

 GitHubが正しく応答したことを確認する必要があります。 ある場合は、どのタイプのコンテンツであるかを調べます。 次のようにしてください:

>>> if (r.status_code == requests.codes.ok):
...     print r.headers['content-type']
...
application/json; charset=utf-8

 そのため、GitHubJSONを返します。 これは素晴らしいことです。JSONモジュールを使用してPythonオブジェクトに変換できます。 GitHubUTF-8を返したため、r.contentメソッドではなく、r.textメソッドを使用する必要があります。 r.contentはバイト文字列を返し、r.textはUnicodeエンコードされた文字列を返します。 この応答でバイト操作を実行する予定はないため、Unicodeコードポイントをエンコードする必要があります。

>>> import json
>>> commit_data = json.loads(r.text)
>>> print commit_data.keys()
[u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message']
>>> print commit_data[u'committer']
{u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'}
>>> print commit_data[u'message']
makin' history

 これまでのところ、とても簡単です。 それでは、GitHub APIについて少し調べてみましょう。 これで、ドキュメントを見ることができましたが、代わりにリクエストを使用する場合は、もう少し楽しいかもしれません。 Requests OPTIONS動詞を利用して、使用したURLでサポートされているHTTPメソッドの種類を確認できます。

>>> verbs = requests.options(r.url)
>>> verbs.status_code
500

 あ、何?  それは役に立たない!  多くのAPIプロバイダーと同様に、GitHubは実際にはOPTIONSメソッドを実装していません。 これは面倒な監視ですが、大丈夫です。退屈なドキュメントを使用するだけです。 ただし、GitHubがOPTIONSを正しく実装している場合は、許可されたメソッドをヘッダーで返す必要があります。

>> verbs = requests.options('http://a-good-website.com/api/cats')
>>> print verbs.headers['allow']
GET,HEAD,POST,OPTIONS

 ドキュメントを見ると、コミットに許可されている他のメソッドはPOSTのみであり、これが新しいコミットを作成することがわかります。 Requestsリポジトリを使用しているので、おそらくそれを手作業でPOSTすることは避けるべきです。 代わりに、GitHubの問題機能を試してみましょう。

このドキュメントは、問題#482に対応して追加されました。 この問題は既に存在するため、例として使用します。 それを手に入れることから始めましょう。

>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482')
>>> r.status_code
200
>>> issue = json.loads(r.text)
>>> print issue[u'title']
Feature any http verb in docs
>>> print issue[u'comments']
3

 クール、コメントが3つあります。 それらの最後を見てみましょう。

>>> r.status_code
200
>>> comments = json.loads(r.text)
>>> print comments[0].keys()
[u'body', u'url', u'created_at', u'updated_at', u'user', u'id']
>>> print comments[2][u'body']
Probably in the "advanced" section

 まあ、それは愚かな場所のようです。 彼に愚かなことをポスターに伝えるコメントを投稿しましょう。 とにかく、ポスターは誰ですか?

>>> print comments[2][u'user'][u'login']
kennethreitz

 それでは、このケネスの人に、代わりにこの例がクイックスタートガイドに入るべきだと伝えましょう。 GitHub APIドキュメントによると、これを行う方法はスレッドにPOSTすることです。 やってみましょう。

>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
>>> r = requests.post(url=url, data=body)
>>> r.status_code
404

 ええ、それは奇妙です。 おそらく認証が必要です。 それは苦痛ですよね? 違う。 リクエストを使用すると、非常に一般的な基本認証など、さまざまな形式の認証を簡単に使用できます。

>>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password')
>>> r = requests.post(url=url, data=body, auth=auth)
>>> r.status_code
201
>>> content = json.loads(r.text)
>>> print content[u'body']
Sounds great! I'll get right on it.

 ブリリアント。 ああ、待って、いや! 私は猫に餌をやらなければならなかったので、時間がかかると付け加えました。 このコメントを編集できればいいだけです! 幸いなことに、GitHubでは別のHTTP動詞PATCHを使用してこのコメントを編集できます。 それをしましょう。

>>> print content[u"id"]
5804413
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413"
>>> r = requests.patch(url=url, data=body, auth=auth)
>>> r.status_code
200

 エクセレント。 今、このケネスの男を拷問するために、私は彼に汗をかかせ、私がこれに取り組んでいることを彼に伝えないことに決めました。 つまり、このコメントを削除したいということです。 GitHubでは、非常に適切な名前のDELETEメソッドを使用してコメントを削除できます。 それを取り除こう。

>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'

 エクセレント。 全部なくなった。 最後に知りたいのは、使用したレート制限の量です。 確認してみましょう。 GitHubはその情報をヘッダーで送信するため、ページ全体をダウンロードするのではなく、HEADリクエストを送信してヘッダーを取得します。

>>> r = requests.head(url=url, auth=auth)
>>> print r.headers
...
'x-ratelimit-remaining': '4995'
'x-ratelimit-limit': '5000'
...

 エクセレント。 GitHub APIをあらゆる種類のエキサイティングな方法で悪用するPythonプログラムを書く時間、さらに4995回。

リンクヘッダー

 多くのHTTP APIはリンクヘッダーを備えています。 APIをより自己記述的で発見しやすくします。

 GitHubは、これらをAPIのページネーションに使用します。次に例を示します。

>>> url = 'https://api.github.com/users/kennethreitz/repos?page=1&per_page=10'
>>> r = requests.head(url=url)
>>> r.headers['link']
'<https://api.github.com/users/kennethreitz/repos?page=2&per_page=10>; rel="next", <https://api.github.com/users/kennethreitz/repos?page=6&per_page=10>; rel="last"'

 Requestはこれらのリンクヘッダーを自動的に解析し、簡単に消費できるようにします。

>>> r.links['next']
'https://api.github.com/users/kennethreitz/repos?page=2&per_page=10'

>>> r.links['last']
'https://api.github.com/users/kennethreitz/repos?page=6&per_page=10'

© Copyright 2012, Tsuyoshi Tokuda.