python向けHTTP関連モジュールのrequestsが便利すぎる


(追記 2015/04/19) コードは既に互換性が無くなっているので、以下を参考に読み替えて下さい。
pythonのrequestsライブラリの更新に追従



大幅に改善強化されたurllib2ことrequestsモジュールを使ってみたら便利すぎて驚いたので報告。
urllib2で面倒だった処理がどれだけ簡単になるのか。

クッキーの処理

セッションを維持したままHTTPアクセスをする場合、urllib2だと

  1. cookielib.HTTPCookieJarのインスタンス作成し、
  2. urllib2.HTTPCookieProcessorのコンストラクタに渡して、
  3. urlib2.build_openerでOpenerDirectorインスタンスを作成し、
  4. そのインスタンスのadd_handler()の呼び出しで2.で作成したurllib2.HTTPCookieProcessorのインスタンスを渡す。ここまでが前準備。
  5. urllib2.Requestのインスタンスを作成し、
  6. HTTPのパラメータを自分でurllib.quoteなどでURLエンコードし、
  7. RequestオブジェクトをOpenDirector.openの引数に渡す

という手順が必要。

前に、urllib2を使ってはてなにログインするエントリを書きましたが、同じコードをrequestsで書くなら以下のようになります。

import requests

# requests.Sessionインスタンスを作成して、
s = requests.session()

# HTTPのパラメータを表すdictを渡す
params = {
    'name': 'your user id',
    'password': 'your password',
}
r =  s.post('https://www.hatena.ne.jp/login', params=params)

print r.text

楽勝です。というかurllib2が訳分からんです。.textで返される文字列は、HTTPヘッダあるいはchardetモジュールで文字コードを判別してunicodeインスタンスを返すので、そこらを時前で処理する必要もありません。
さらに、レスポンスがjson文字列の場合、.jsonを参照するとレスポンスがdictのインスタンスにデシリアライズされ返されます。便利です。

OAuthのサポート

requestsではOAuthが標準でサポートされています。requestsは、公式のドキュメントにgithubtwitterとの連携コードが書かれていたりして、他サービスのAPIを簡単に使えるよう意識されているようです。

import requests
from requests.auth import OAuth1
import pprint
url = u'https://api.twitter.com/1/statuses/home_timeline.json'

client_key = u'consumer key'
client_secret = u'consumer secret'
resource_owner_key = u'access token'
resource_owner_secret = u'access secret'

queryoauth = OAuth1(client_key, client_secret,
                    resource_owner_key, resource_owner_secret,
                    signature_type='query')

for s in requests.get(url, auth=queryoauth).json:
    print s['text']

.iter_line()でレスポンスを1行ずつ読む事もできるので、Streaming APIのような、サーバにつなぎっぱなしで順次処理するような処理も簡単にかけます。

url = 'https://userstream.twitter.com/2/user.json'
r = requests.get(url, auth=queryoauth, prefetch=False)
for line in r.iter_lines():
    if line == '':
        continue
    print json.loads(line)

非同期

さらに、geventというIOノンブロッキングライブラリを使う事で,非同期リクエストもできます。この機能はGRequestsというまた別のモジュールとして外出しにされていますが。

# coding: utf-8
import grequests
import urllib
import time

# 走査対象のURL一覧
urls = [
    'http://www.heroku.com',
    'http://tablib.org',
    'http://httpbin.org',
    'http://python-requests.org',
    'http://kennethreitz.com',
]

# 同期でのURLフェッチ
class O(urllib.FancyURLopener, object):
    version = 'alternative user-agent'
o = O()
def sync():
    for u in urls:
        conn = o.open(u)
        assert conn.getcode() == 200

# 非同期でのURLフェッチ
def async():
    rs = (grequests.get(u) for u in urls)
    g = grequests.imap(rs)
    for r in g:
        assert r.status_code == 200

# 時間計測関数
def measure(fn):
    s = time.time()
    fn()
    e = time.time()
    return e-s

# 10回、何倍速くなるか計測(初回3回は無視)
print 'sync(sec)\tasync(sec)\tsync/async'
for n in range(13):
    if n >= 3:
        s = measure(sync)
        a = measure(async)
        print '%.2f\t%.2f\t%.2f' % (s, a, s/a)

"""
$ python g.py 
sync(sec)       async(sec)      sync/async
2.47    1.86    1.32
2.53    1.90    1.33
2.55    1.96    1.30
2.52    1.85    1.36
2.52    2.00    1.26
2.57    1.83    1.40
2.62    1.99    1.32
2.70    1.98    1.36
2.50    1.80    1.39
2.56    1.85    1.38
"""

他にも

地味に

  • リダイレクトを自動でfollow
  • gzip圧縮されたレスポンスを自動で展開

とかあったりします。

なんだか色々できちゃって、pyqueryとかBeautifulSoupとかmecabと組み合わせるだけでクローラのフレームワークとか作れそうですね。