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

URLopener

urllibURLopener ベースクラスと、サポート済みプロトコルのデフォルト操作を行う FancyURLopener を提供します。もしこういったクラスの処理を変更する必要が出てきたら、Python 2.1 で追加された urllib2 モジュールに探してみる方が良いでしょう。

See also

urllib
本モジュールの標準ライブラリドキュメント
urllib2
URL ベースのサービスを処理する API の拡張
urlparse
URL コンポーネントにアクセスする URL 文字列を解析
Bookmark and Share