urllibでベーシック認証が必要なページを取得する方法
urllib.FancyURLopenerを利用して、認証が必要なページへアクセスするとプロンプトが表示されユーザ名とパスワードを聞いてくる。プロンプトでユーザとパスワード指定すれば取得できるが、これだと自動化できない。
このプロンプトはFancyURLopenerのprompt_user_passwdメソッドのデフォルト動作なので、prompt_user_passwdを書き換えてやれば良い。prompt_user_passwdはhostとrealmの引数をとり、ユーザ名とパスワードのタプルを返すのメソッドなので、以下のソースのように書き換える。今回、ユーザ名/パスワードは固定。
import urllib opener = urllib.FancyURLopener() opener.prompt_user_passwd = lambda host , realm : ( user , passw ) opener.open( url )
これで、ベーシック認証が必要なページの取得が出来る。lambda便利だなぁとか思ってたら、認証に失敗するページにアクセスすると、Process is terminated due to StackOverflowException.となって落ちるIronPython(IPCE-r5)。IronPython(IPCE-r5)固有の問題かと思いきやpython 2.4でも落ちる。python 2.4の場合は以下。
File "D:\Program Files\Python24\lib\urllib.py", line 190, in open return getattr(self, name)(url) File "D:\Program Files\Python24\lib\urllib.py", line 322, in open_http return self.http_error(url, fp, errcode, errmsg, headers) File "D:\Program Files\Python24\lib\urllib.py", line 335, in http_error result = method(url, fp, errcode, errmsg, headers) File "D:\Program Files\Python24\lib\urllib.py", line 644, in http_error_401 return getattr(self,name)(url, realm) File "D:\Program Files\Python24\lib\urllib.py", line 657, in retry_http_basic_auth return self.open(newurl) File "D:\Program Files\Python24\lib\urllib.py", line 190, in open return getattr(self, name)(url) File "D:\Program Files\Python24\lib\urllib.py", line 322, in open_http return self.http_error(url, fp, errcode, errmsg, headers) File "D:\Program Files\Python24\lib\urllib.py", line 335, in http_error result = method(url, fp, errcode, errmsg, headers) File "D:\Program Files\Python24\lib\urllib.py", line 644, in http_error_401 return getattr(self,name)(url, realm) File "D:\Program Files\Python24\lib\urllib.py", line 657, in retry_http_basic_auth return self.open(newurl) File "D:\Program Files\Python24\lib\urllib.py", line 190, in open return getattr(self, name)(url) File "D:\Program Files\Python24\lib\urllib.py", line 316, in open_http errcode, errmsg, headers = h.getreply() File "D:\Program Files\Python24\lib\httplib.py", line 1137, in getreply response = self._conn.getresponse() File "D:\Program Files\Python24\lib\httplib.py", line 866, in getresponse response.begin() File "D:\Program Files\Python24\lib\httplib.py", line 365, in begin self.msg = HTTPMessage(self.fp, 0) File "D:\Program Files\Python24\lib\mimetools.py", line 16, in __init__ rfc822.Message.__init__(self, fp, seekable) File "D:\Program Files\Python24\lib\rfc822.py", line 106, in __init__ self.readheaders() File "D:\Program Files\Python24\lib\httplib.py", line 221, in readheaders line = self.fp.readline() File "D:\Program Files\Python24\lib\socket.py", line 313, in readline assert data == "" RuntimeError: maximum recursion depth exceeded in cmp
self.open(newurl)あたりで延々同じ再帰ルートを辿ってRuntimeError: maximum recursion depth exceededになる。ソースを追ってみると、prompt_user_passwd()が(None,None)のタプルを返さない限り、永久に再起する。うーん。
ということは、今回はユーザ名/パスワードは固定で良いので、2回目から(None,None)を返す関数を作れば良いのね。散々lambdaで作れないかと悩んだすえ無理っぽいので、普通に関数定義する方法に。ガクリ。
def user_passwd( host , realm , f = [1] ) : if f[0] != 0 : f[0] = 0 return ( user , passw ) return ( None , None ) opener.prompt_user_passwd = user_passwd
第3パラメタのfはC言語の関数内static変数のようなもの。これがないと再帰ループ中の2回目の呼び出しとか判定できない。あんまりスマートな方法ではないんだけど、グローバル汚染したりこれだけのためにクラス定義するのも面倒なので妥協。ほかに上手いやり方ありそうだけどなぁ。
yield使う邪道な方法もあるけどw
def user_passwd2( host , realm ) : yield ( user , passw ) return ( None , None )