urllib2 – URL をオープンするライブラリ¶
目的: | カスタムプロトコルハンドラを定義して拡張できる URL をオープンするライブラリ |
---|---|
利用できるバージョン: | 2.1 |
urllib2 モジュールは、URL で識別するインターネットリソースのために拡張された API です。個別のアプリケーションで新たなプロトコルをサポートしたり、(HTTP ベーシック認証を処理するといった)既存のプロトコルに修正を加えたりして拡張することを意図して設計されています。
HTTP GET¶
Note
この記事のサンプルのテストサーバは BaseHTTPServer モジュールのサンプルにある BaseHTTPServer_GET.py です。ターミナルでサーバを起動して、それとは別のターミナルでこの記事のサンプルを実行してください。
urllib と同様に HTTP GET の操作は urllib2 の最も簡単な使用法です。リモートのデータを処理する “ファイルのような” オブジェクトを取得するために urlopen() へ URL を渡してください。
import urllib2
response = urllib2.urlopen('http://localhost:8080/')
print 'RESPONSE:', response
print 'URL :', response.geturl()
headers = response.info()
print 'DATE :', headers['date']
print 'HEADERS :'
print '---------'
print headers
data = response.read()
print 'LENGTH :', len(data)
print 'DATA :'
print '---------'
print data
このサンプルサーバは、入力データを受け取って、プレーンテキストでレスポンスを返します。 urlopen() からの返り値に対して、 info() を通して HTTP サーバからのヘッダへ、 read() や readlines() といったメソッドを通してリモートリソースのデータへのアクセスします。
$ python urllib2_urlopen.py
RESPONSE: <addinfourl at 11940488 whose fp = <socket._fileobject object at 0xb573f0>>
URL : http://localhost:8080/
DATE : Sun, 19 Jul 2009 14:01:31 GMT
HEADERS :
---------
Server: BaseHTTP/0.3 Python/2.6.2
Date: Sun, 19 Jul 2009 14:01:31 GMT
LENGTH : 349
DATA :
---------
CLIENT VALUES:
client_address=('127.0.0.1', 55836) (localhost)
command=GET
path=/
real path=/
query=
request_version=HTTP/1.1
SERVER VALUES:
server_version=BaseHTTP/0.3
sys_version=Python/2.6.2
protocol_version=HTTP/1.0
HEADERS RECEIVED:
accept-encoding=identity
connection=close
host=localhost:8080
user-agent=Python-urllib/2.6
urlopen() が返すファイルのようなオブジェクトは繰り返し処理できます。
import urllib2
response = urllib2.urlopen('http://localhost:8080/')
for line in response:
print line.rstrip()
このサンプルは、出力を表示する前にその文字列の改行コードを取り除いています。
$ python urllib2_urlopen_iterator.py
CLIENT VALUES:
client_address=('127.0.0.1', 55840) (localhost)
command=GET
path=/
real path=/
query=
request_version=HTTP/1.1
SERVER VALUES:
server_version=BaseHTTP/0.3
sys_version=Python/2.6.2
protocol_version=HTTP/1.0
HEADERS RECEIVED:
accept-encoding=identity
connection=close
host=localhost:8080
user-agent=Python-urllib/2.6
エンコード引数¶
urllib.urlencode() で引数をエンコードして URL に追加することでサーバへ引数が渡せます。
import urllib
import urllib2
query_args = { 'q':'query string', 'foo':'bar' }
encoded_args = urllib.urlencode(query_args)
print 'Encoded:', encoded_args
url = 'http://localhost:8080/?' + encoded_args
print urllib2.urlopen(url).read()
CLIENT VALUES のリストにエンコードされたクエリ引数が含まれています。
$ python urllib2_http_get_args.py
Encoded: q=query+string&foo=bar
CLIENT VALUES:
client_address=('127.0.0.1', 55849) (localhost)
command=GET
path=/?q=query+string&foo=bar
real path=/
query=q=query+string&foo=bar
request_version=HTTP/1.1
SERVER VALUES:
server_version=BaseHTTP/0.3
sys_version=Python/2.6.2
protocol_version=HTTP/1.0
HEADERS RECEIVED:
accept-encoding=identity
connection=close
host=localhost:8080
user-agent=Python-urllib/2.6
HTTP POST¶
Note
この記事のサンプルのテストサーバは BaseHTTPServer モジュールのサンプルにある BaseHTTPServer_POST.py です。ターミナルでサーバを起動して、それとは別のターミナルでこの記事のサンプルを実行してください。
GET ではなく、リモートサーバへフォームでエンコードされたデータを POST するには、エンコードされたクエリ引数を urlopen() へのデータとして渡してください。
import urllib
import urllib2
query_args = { 'q':'query string', 'foo':'bar' }
encoded_args = urllib.urlencode(query_args)
url = 'http://localhost:8080/'
print urllib2.urlopen(url, encoded_args).read()
このサーバはフォームデータをデコードして名前で個々のデータにアクセスできます。
$ python urllib2_urlopen_post.py
Client: ('127.0.0.1', 55943)
User-agent: Python-urllib/2.6
Path: /
Form data:
q=query string
foo=bar
リクエストを直接処理する¶
urlopen() は、リクエストがどうやって作られて処理されているかの詳細を隠蔽してくれる便利な関数です。より詳細な制御をするには Request オブジェクトを直接インスタンス化して使用すると良いです。
外部向けのヘッダを追加する¶
前説で説明したサンプルのように、デフォルトの User-agent ヘッダの値は、定数 Python-urllib に続く Python インタープリタのバージョンで構成されます。他人が管理している web リソースへアクセスするアプリケーションを開発しているなら、簡単にアクセス元が分かるのでリクエスト情報の中に本当のユーザエージェント情報を含めるのがお作法です。カスタムエージェントを使用すると robots.txt ファイルでクローラを制御することもできます(robotparser を参照)。
import urllib2
request = urllib2.Request('http://localhost:8080/')
request.add_header('User-agent', 'PyMOTW (http://www.doughellmann.com/PyMOTW/)')
response = urllib2.urlopen(request)
data = response.read()
print data
Request オブジェクトの作成後、そのリクエストをオープンする前にユーザエージェントの値をセットするには add_header() を使用してください。この結果出力の最後の行はカスタム値を表示します。
$ python urllib2_request_header.py
CLIENT VALUES:
client_address=('127.0.0.1', 55876) (localhost)
command=GET
path=/
real path=/
query=
request_version=HTTP/1.1
SERVER VALUES:
server_version=BaseHTTP/0.3
sys_version=Python/2.6.2
protocol_version=HTTP/1.0
HEADERS RECEIVED:
accept-encoding=identity
connection=close
host=localhost:8080
user-agent=PyMOTW (http://www.doughellmann.com/PyMOTW/)
フォームデータを POST する¶
サーバへデータを POST する Request にそのデータをセットできます。
import urllib
import urllib2
query_args = { 'q':'query string', 'foo':'bar' }
request = urllib2.Request('http://localhost:8080/')
print 'Request method before data:', request.get_method()
request.add_data(urllib.urlencode(query_args))
print 'Request method after data :', request.get_method()
request.add_header('User-agent', 'PyMOTW (http://www.doughellmann.com/PyMOTW/)')
print
print 'OUTGOING DATA:'
print request.get_data()
print
print 'SERVER RESPONSE:'
print urllib2.urlopen(request).read()
Request が使用する HTTP メソッドは、自動的にデータが追加された後で GET から POST へ変更します。
$ python urllib2_request_post.py
Request method before data: GET
Request method after data : POST
OUTGOING DATA:
q=query+string&foo=bar
SERVER RESPONSE:
Client: ('127.0.0.1', 56044)
User-agent: PyMOTW (http://www.doughellmann.com/PyMOTW/)
Path: /
Form data:
q=query string
foo=bar
Note
add_data() というメソッド名ですが、この処理はデータを追加 しません 。呼び出す毎にその前のデータは置き換えられます。
ファイルをアップロードする¶
アップロード用にファイルをエンコードすることは、シンプルなフォームよりも一手間あります。サーバがアップロードファイルからフォームフィールドを判別できるように、リクエスト本文に完全な MIME メッセージを作成する必要があります。
import itertools
import mimetools
import mimetypes
from cStringIO import StringIO
import urllib
import urllib2
class MultiPartForm(object):
"""Accumulate the data to be used when posting a form."""
def __init__(self):
self.form_fields = []
self.files = []
self.boundary = mimetools.choose_boundary()
return
def get_content_type(self):
return 'multipart/form-data; boundary=%s' % self.boundary
def add_field(self, name, value):
"""Add a simple field to the form data."""
self.form_fields.append((name, value))
return
def add_file(self, fieldname, filename, fileHandle, mimetype=None):
"""Add a file to be uploaded."""
body = fileHandle.read()
if mimetype is None:
mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
self.files.append((fieldname, filename, mimetype, body))
return
def __str__(self):
"""Return a string representing the form data, including attached files."""
# リクエストの "行" を含むリストを作成する
# それぞれのパートはバウンダリ文字列で分割される
# リストが作成されると '\r\n' で区切られた行を返す
parts = []
part_boundary = '--' + self.boundary
# フォームフィールドを追加する
parts.extend(
[ part_boundary,
'Content-Disposition: form-data; name="%s"' % name,
'',
value,
]
for name, value in self.form_fields
)
# アップロードするファイルを追加する
parts.extend(
[ part_boundary,
'Content-Disposition: file; name="%s"; filename="%s"' % \
(field_name, filename),
'Content-Type: %s' % content_type,
'',
body,
]
for field_name, filename, content_type, body in self.files
)
# リストにしてバウンダリ文字列をクローズするマーカーを
# 追加してから CR+LF で分割されたデータを返す
flattened = list(itertools.chain(*parts))
flattened.append('--' + self.boundary + '--')
flattened.append('')
return '\r\n'.join(flattened)
if __name__ == '__main__':
# シンプルなフィールドでフォームを作成する
form = MultiPartForm()
form.add_field('firstname', 'Doug')
form.add_field('lastname', 'Hellmann')
# うそのファイルを追加する
form.add_file('biography', 'bio.txt',
fileHandle=StringIO('Python developer and blogger.'))
# リクエストを作成する
request = urllib2.Request('http://localhost:8080/')
request.add_header('User-agent', 'PyMOTW (http://www.doughellmann.com/PyMOTW/)')
body = str(form)
request.add_header('Content-type', form.get_content_type())
request.add_header('Content-length', len(body))
request.add_data(body)
print
print 'OUTGOING DATA:'
print request.get_data()
print
print 'SERVER RESPONSE:'
print urllib2.urlopen(request).read()
MultiPartForm クラスは、添付ファイルのマルチパート MIME メッセージのように任意のフォームを表せます。
$ python urllib2_upload_files.py
OUTGOING DATA:
--192.168.1.17.527.30074.1248020372.206.1
Content-Disposition: form-data; name="firstname"
Doug
--192.168.1.17.527.30074.1248020372.206.1
Content-Disposition: form-data; name="lastname"
Hellmann
--192.168.1.17.527.30074.1248020372.206.1
Content-Disposition: file; name="biography"; filename="bio.txt"
Content-Type: text/plain
Python developer and blogger.
--192.168.1.17.527.30074.1248020372.206.1--
SERVER RESPONSE:
Client: ('127.0.0.1', 57126)
User-agent: PyMOTW (http://www.doughellmann.com/PyMOTW/)
Path: /
Form data:
lastname=Hellmann
Uploaded biography as "bio.txt" (29 bytes)
firstname=Doug
カスタムプロトコルハンドラ¶
urllib2 は HTTP(S)、FTP、ローカルファイルへのアクセスを組み込み機能でサポートします。その他の URL タイプをサポートする必要があるなら、必要に応じて実行される独自のプロトコルハンドラを登録できます。例えば、リモートの NFS サーバ上に対して、ユーザへ手動でパスをマウントさせずに任意のファイルを参照する URL をサポートしたいなら、 BaseHandler から派生したクラスと nfs_open() メソッドを作成できます。
プロトコルの open() メソッドは Request インスタンスの引数を1つだけ受け取ります。 Request インスタンスは、データを読み込むのに使用される read() メソッド、レスポンスヘッダを返す info() メソッド、読み込まれたファイルの実際の URL を返す geturl() メソッドを持つオブジェクトを返します。これを実装する簡単な方法の1つは、ヘッダ、URL、オープンファイルハンドラをコンストラクタへ渡して urllib.addurlinfo のインスタンスを作成することです。
import mimetypes
import os
import tempfile
import urllib
import urllib2
class NFSFile(file):
def __init__(self, tempdir, filename):
self.tempdir = tempdir
file.__init__(self, filename, 'rb')
def close(self):
print
print 'NFSFile:'
print ' unmounting %s' % self.tempdir
print ' when %s is closed' % os.path.basename(self.name)
return file.close(self)
class FauxNFSHandler(urllib2.BaseHandler):
def __init__(self, tempdir):
self.tempdir = tempdir
def nfs_open(self, req):
url = req.get_selector()
directory_name, file_name = os.path.split(url)
server_name = req.get_host()
print
print 'FauxNFSHandler simulating mount:'
print ' Remote path: %s' % directory_name
print ' Server : %s' % server_name
print ' Local path : %s' % tempdir
print ' File name : %s' % file_name
local_file = os.path.join(tempdir, file_name)
fp = NFSFile(tempdir, local_file)
content_type = mimetypes.guess_type(file_name)[0] or 'application/octet-stream'
stats = os.stat(local_file)
size = stats.st_size
headers = { 'Content-type': content_type,
'Content-length': size,
}
return urllib.addinfourl(fp, headers, req.get_full_url())
if __name__ == '__main__':
tempdir = tempfile.mkdtemp()
try:
# シミュレーションのために一時ファイルを作成する
with open(os.path.join(tempdir, 'file.txt'), 'wt') as f:
f.write('Contents of file.txt')
# NFS ハンドラの opener を作成して、
# デフォルトの opener として登録する
opener = urllib2.build_opener(FauxNFSHandler(tempdir))
urllib2.install_opener(opener)
# URL を通してファイルをオープンする
response = urllib2.urlopen('nfs://remote_server/path/to/the/file.txt')
print
print 'READ CONTENTS:', response.read()
print 'URL :', response.geturl()
print 'HEADERS:'
for name, value in sorted(response.info().items()):
print ' %-15s = %s' % (name, value)
response.close()
finally:
os.remove(os.path.join(tempdir, 'file.txt'))
os.removedirs(tempdir)
FauxNFSHandler と NFSFile クラスは、実際の実装がどこで mount と unmound を呼び出すかを理解するためにメッセージ出力します。このサンプルはただのシミュレーションなので、 FauxNFSHandler に一時ディレクトリの場所を教えて、そこにあるファイルを探します。
$ python urllib2_nfs_handler.py
FauxNFSHandler simulating mount:
Remote path: /path/to/the
Server : remote_server
Local path : /var/folders/9R/9R1t+tR02Raxzk+F71Q50U+++Uw/-Tmp-/tmppv5Efn
File name : file.txt
READ CONTENTS: Contents of file.txt
URL : nfs://remote_server/path/to/the/file.txt
HEADERS:
Content-length = 20
Content-type = text/plain
NFSFile:
unmounting /var/folders/9R/9R1t+tR02Raxzk+F71Q50U+++Uw/-Tmp-/tmppv5Efn
when file.txt is closed
See also
- urllib2
- 本モジュールの標準ライブラリドキュメント
- urllib
- URL 操作のオリジナルのライブラリ
- urlparse
- URL 文字列を処理する
- urllib2 – The Missing Manual
- Michael Foord の urllib2 の記事
- Upload Scripts
- HTTP でファイルをアップロードしてサーバ上でデータを受け取る方法を説明する Michael Foord のサンプルスクリプト
- HTTP client to POST using multipart/form-data
- HTTP でエンコード、ファイルを含めたデータの POST を行う Python クックブックのレシピ
- Form content types
- HTTP フォームでファイルや巨大なデータを解析する W3C の仕様
- mimetypes
- ファイル名と MIME タイプをマップする
- mimetools
- MIME メッセージを解析するツール