urllib – ネットワークリソースへのシンプルなインタフェース¶
目的: | 認証や cookie が必要ないリモートのリソースへアクセスする |
---|---|
利用できるバージョン: | 1.4 以上 |
urllib モジュールは、ネットワークリソースへのシンプルなインタフェースを提供します。 urllib は gopher や ftp プロトコルでも使用できますが、この記事では http のサンプルのみを紹介します。
HTTP GET¶
Note
この記事のサンプルのテストサーバは BaseHTTPServer モジュールのサンプルにある BaseHTTPServer_GET.py です。ターミナルでサーバを起動して、それとは別のターミナルでこの記事のサンプルを実行してください。
HTTP GET の操作は urllib の最も簡単な使用法です。リモートデータに対する “ファイルのような” ハンドラを取得するために urlopen() へ単純に URL を渡してください。
import urllib
response = urllib.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 urllib_urlopen.py
RESPONSE: <addinfourl at 10180248 whose fp = <socket._fileobject object at 0x935c30>>
URL : http://localhost:8080/
DATE : Sun, 30 Mar 2008 16:27:10 GMT
HEADERS :
---------
Server: BaseHTTP/0.3 Python/2.5.1
Date: Sun, 30 Mar 2008 16:27:10 GMT
LENGTH : 221
DATA :
---------
CLIENT VALUES:
client_address=('127.0.0.1', 54354) (localhost)
command=GET
path=/
real path=/
query=
request_version=HTTP/1.0
SERVER VALUES:
server_version=BaseHTTP/0.3
sys_version=Python/2.5.1
protocol_version=HTTP/1.0
また、返り値のファイルのようなオブジェクトは繰り返し処理できます。
import urllib
response = urllib.urlopen('http://localhost:8080/')
for line in response:
print line.rstrip()
このサンプルの行は改行コード付きでそのまま返されるので、出力を表示する前にその改行コードを取り除いています。
$ python urllib_urlopen_iterator.py
CLIENT VALUES:
client_address=('127.0.0.1', 54380) (localhost)
command=GET
path=/
real path=/
query=
request_version=HTTP/1.0
SERVER VALUES:
server_version=BaseHTTP/0.3
sys_version=Python/2.5.1
protocol_version=HTTP/1.0
エンコード引数¶
引数をエンコードして URL に追加することでサーバへ引数が渡せます。
import urllib
query_args = { 'q':'query string', 'foo':'bar' }
encoded_args = urllib.urlencode(query_args)
print 'Encoded:', encoded_args
url = 'http://localhost:8080/?' + encoded_args
print urllib.urlopen(url).read()
CLIENT VALUES のリストに、エンコードされたクエリ引数を含まれていることに注目してください。
$ python urllib_urlencode.py
Encoded: q=query+string&foo=bar
CLIENT VALUES:
client_address=('127.0.0.1', 54415) (localhost)
command=GET
path=/?q=query+string&foo=bar
real path=/
query=q=query+string&foo=bar
request_version=HTTP/1.0
SERVER VALUES:
server_version=BaseHTTP/0.3
sys_version=Python/2.5.1
protocol_version=HTTP/1.0
クエリ文字列の変数を別々に指定してシーケンスを渡すには、 urlencode() を呼び出すときに doseq 引数に True をセットしてください。
import urllib
query_args = { 'foo':['foo1', 'foo2'] }
print 'Single :', urllib.urlencode(query_args)
print 'Sequence:', urllib.urlencode(query_args, doseq=True )
$ python urllib_urlencode_doseq.py
Single : foo=%5B%27foo1%27%2C+%27foo2%27%5D
Sequence: foo=foo1&foo=foo2
クエリ文字列をデコードするには、 cgi モジュールの FieldStorage クラスを参照してください。
サーバ上の URL 解析で問題を起こす可能性があるクエリ引数内の特殊文字は、 urlencode() へ渡されるときに “クォート” されます。そういった特殊文字をローカルで安全にクォートするには quote() や quote_plus() 関数を直接、使用できます。
import urllib
url = 'http://localhost:8080/~dhellmann/'
print 'urlencode() :', urllib.urlencode({'url':url})
print 'quote() :', urllib.quote(url)
print 'quote_plus():', urllib.quote_plus(url)
quote_plus() は quote() より積極的に文字を置き換えることに注意してください。
$ python urllib_quote.py
urlencode() : url=http%3A%2F%2Flocalhost%3A8080%2F%7Edhellmann%2F
quote() : http%3A//localhost%3A8080/%7Edhellmann/
quote_plus(): http%3A%2F%2Flocalhost%3A8080%2F%7Edhellmann%2F
アンクォートするには unquote() や unquote_plus() を適切に使用してください。
import urllib
print urllib.unquote('http%3A//localhost%3A8080/%7Edhellmann/')
print urllib.unquote_plus('http%3A%2F%2Flocalhost%3A8080%2F%7Edhellmann%2F')
$ python urllib_unquote.py
http://localhost:8080/~dhellmann/
http://localhost:8080/~dhellmann/
HTTP POST¶
Note
この記事のサンプルのテストサーバは BaseHTTPServer モジュールのサンプルにある BaseHTTPServer_POST.py です。ターミナルでサーバを起動して、それとは別のターミナルでこの記事のサンプルを実行してください。
GET ではなく、リモートサーバへデータを POST するには、URL に対してエンコードされたクエリ引数を追加するのではなく、 urlopen() へのデータとして渡してください。
import urllib
query_args = { 'q':'query string', 'foo':'bar' }
encoded_args = urllib.urlencode(query_args)
url = 'http://localhost:8080/'
print urllib.urlopen(url, encoded_args).read()
$ python urllib_urlopen_post.py
Client: ('127.0.0.1', 54545)
Path: /
Form data:
q=query string
foo=bar
データとして任意のバイト文字列を送信できます。このサーバのサンプルは POST されたデータにある URL エンコードされたフォーム引数以外のデータを待ちます。
パスと URL¶
オペレーティングシステムによっては、URL よりローカルファイルのパスのコンポーネント区切りに違う文字を使用します。コードの移植性を高めるために、変換したり戻したりするのに pathname2url() や url2pathname() といった関数を使用した方が良いです。私は Mac を使っているので、その関数の Windows 版を明示的にインポートする必要があります。 urllib がエクスポートする関数を使用すると、プラットフォームに適切なデフォルトが渡されるので、こんなことをする必要はありません。
import os
from urllib import pathname2url, url2pathname
print '== Default =='
path = '/a/b/c'
print 'Original:', path
print 'URL :', pathname2url(path)
print 'Path :', url2pathname('/d/e/f')
print
from nturl2path import pathname2url, url2pathname
print '== Windows, without drive letter =='
path = path.replace('/', '\\')
print 'Original:', path
print 'URL :', pathname2url(path)
print 'Path :', url2pathname('/d/e/f')
print
print '== Windows, with drive letter =='
path = 'C:\\' + path.replace('/', '\\')
print 'Original:', path
print 'URL :', pathname2url(path)
print 'Path :', url2pathname('/d/e/f')
パスの接頭辞にドライブ文字が付く、付かない場合の Windows サンプルが2つあります。
$ python urllib_pathnames.py
== Default ==
Original: /a/b/c
URL : /a/b/c
Path : /d/e/f
== Windows, without drive letter ==
Original: \a\b\c
URL : /a/b/c
Path : \d\e\f
== Windows, with drive letter ==
Original: C:\\a\b\c
URL : ///C:/a/b/c
Path : \d\e\f
キャッシュでシンプルに取り出す¶
データを取り出すのは一般的な操作です。 urllib には urlretrieve() 関数があるので、独自にその処理を書く必要はありません。 urlretrieve() は、URL、データを保持する一時ファイル、ダウンロード進捗をレポートする関数、引数の URL が POST されるフォームを参照するときに渡すデータといった引数を取ります。もしファイル名が渡されなければ、 urlretrieve() は一時ファイルを作成します。その一時ファイルはキャッシュのように扱ったり、 urlcleanup() を使用して削除できます。
このサンプルは web サーバからデータを取り出すために GET を使用します。
import urllib
import os
def reporthook(blocks_read, block_size, total_size):
if not blocks_read:
print 'Connection opened'
return
if total_size < 0:
# 不明サイズ
print 'Read %d blocks' % blocks_read
else:
amount_read = blocks_read * block_size
print 'Read %d blocks, or %d/%d' % (blocks_read, amount_read, total_size)
return
try:
filename, msg = urllib.urlretrieve('http://blog.doughellmann.com/', reporthook=reporthook)
print
print 'File:', filename
print 'Headers:'
print msg
print 'File exists before cleanup:', os.path.exists(filename)
finally:
urllib.urlcleanup()
print 'File still exists:', os.path.exists(filename)
このサーバは Content-length ヘッダを返さないので、 urlretrieve() は返されるデータがどのぐらい大きいかが分かりません。そして reporthook() の total_size 引数として -1 が渡されます。
$ python urllib_urlretrieve.py
Connection opened
Read 1 blocks
Read 2 blocks
Read 3 blocks
Read 4 blocks
Read 5 blocks
Read 6 blocks
Read 7 blocks
Read 8 blocks
Read 9 blocks
Read 10 blocks
Read 11 blocks
Read 12 blocks
Read 13 blocks
Read 14 blocks
Read 15 blocks
Read 16 blocks
Read 17 blocks
Read 18 blocks
Read 19 blocks
File: /var/folders/9R/9R1t+tR02Raxzk+F71Q50U+++Uw/-Tmp-/tmp3HRpZP
Headers:
Content-Type: text/html; charset=UTF-8
Last-Modified: Tue, 25 Mar 2008 23:09:10 GMT
Cache-Control: max-age=0 private
ETag: "904b02e0-c7ff-47f6-9f35-cc6de5d2a2e5"
Server: GFE/1.3
Date: Sun, 30 Mar 2008 17:36:48 GMT
Connection: Close
File exists before cleanup: True
File still exists: False