アジョブジ星通信

進捗が出た頃に更新されるブログ。

urllib2 でリダイレクトを制御する

f:id:azyobuzin:20130408001650p:plain

もうすぐ高校生活!……めんどい。 という心境の azyobuzin です。 Qiita よりブログのほうが書き心地がいいので、結局プログラミング Tips もブログに流してしまっています。

大事な注意:タイトルからわかるとおり、 Python 2.7 でのお話です。 3.x にもすごく興味がありますが、 2.7 で使うコードなので勘弁してください。

今日のお題

img.azyobuzi.net の作り直しで、リダイレクト先をスマートに取得できないかと思い、いろいろ試行錯誤してみました。

現在の実装

現在稼働中の img.azyobuzi.net では、 Dropbox や Tumblr の短縮 URI の展開時に

import httplib

conn = httplib.HTTPConnection("db.tt")
conn.request("HEAD", "/" + hoge)
res = conn.getresponse()
uri = res.getheader("location")

というように、生 HTTPConnection を叩いています。これだと URI を直接突っ込めなくて個人的に直感的ではないと感じます。

つまり

使い慣れた urllib2 でなんとかならないのか??

HEAD でリクエストを送信してみる

urlopen は GET と POST しか対応していませんが、このように Request を上書きすることで他のメソッドにも対応できるようです。

import urllib2
request = urllib2.Request('http://localhost:8080')
request.get_method = lambda : 'HEAD'
How do you send a HEAD HTTP request in Python? - Stack Overflow

やってみた

http://j.mp/Z1AkTIGoogle にリダイレクト)を実験台にしてみます。

f:id:azyobuzin:20130407235744p:plain

どこが HEAD だよ!!!思いっきり GET じゃねーか!!!!

原因

urllib2 の実装で、リダイレクトに従うときに自動的に GET にされる。

内部の実装では、新しい URI で新たに Request インスタンスを作成しているようです。これでは get_method が元のままですね…。

とりあえず、リダイレクトに従わなくさせる

リダイレクトに従わなくさせれば、ヘッダー見るだけでリダイレクト先がわかるよね!!!

build_opener の引数に BaseHandler を継承したクラス or インスタンスを突っ込むと、リクエスト前、レスポンス取得時、エラー時の動作を変えることができます。さらに、既存の Handler を継承したクラス or インスタンスを突っ込むと、既存の Handler は除外されます(がんばってソース読んだ)。つまり、 HTTPRedirectHandler を継承して無効化すればめでたく解決なわけです。

そのコードがこちら

使い方はこんな感じ

>>> import urllib2
>>> from module1 import *
>>> opener = urllib2.build_opener(DontRedirectHandler)
>>> request = urllib2.Request("http://j.mp/Z1AkTI")
>>> request.get_method = lambda: "HEAD"
>>> response = opener.open(request)
>>> response.code
301
>>> response.url
'http://j.mp/Z1AkTI'
>>> dict(response.headers)
{'content-length': '114', 'set-cookie': '_bit=51618422-002f9-056a6-2e1cf10a;domain=.j.mp;expires=Fri Oct  4 14:35:14 2013;path=/; HttpOnly', 'server': 'nginx', 'connection': 'close', 'location': 'http://www.google.com/', 'cache-control': 'private; max-age=90', 'date': 'Sun, 07 Apr 2013 14:35:14 GMT', 'content-type': 'text/html; charset=utf-8', 'mime-version': '1.0'}
>>> response.read()
''

本当は install_opener するべきでしょうけど、全部のコードでリダイレクト無視されちゃうのは嫌なのでやりません。

次回予告

メソッドを保持したままリダイレクトに従ってみたいです。
予定は未定です。
もっと! urllib2 でリダイレクトを制御する - アジョブジ星通信