Pythonの “urllib.request” (Python 2では “urllib2”) というモジュールを使用すると、サーバとの通信の仕方やその仕様が分からなくても、オブジェクトの操作という形で簡単にWebページの内容を取得してその内容やレスポンスヘッダを扱うことができる。同モジュールにはファイルをローカルに保存する機能もあるが、ここでは扱わない。
本記事では、HTTPサーバからWebページの内容を取得して、これをレスポンスヘッダとともに出力するという操作を行う。
以前、同様の内容を2008年5月に扱っているが、新しいurllibの使い方やPython 3での変更点などの関係で大部分の内容を書き直し、更にプロキシサーバ関係の内容を追加している。
扱い方
urlopen()
関数を前述のモジュールからインポートし、 “http://” もしくは “https://” で始まるURL文字列を引数に指定して呼び出し、戻り値としてオブジェクトを得る- Python 3では本文の内容はバイト型で得られ、文字列として扱うにはページのエンコーディングに合わせて
str()
を “encoding=
” 付きで呼ぶ - Python 3とPython 2の両方で同じように動くものが作りたい場合は “urllib.request” からのインポートを試みてImportError例外が発生するかどうかなどでバージョン判別を行い、動作の違いを吸収するための同名の関数をそれぞれに対して用意するなどする
簡単な例
UTF-8エンコーディングのWebページのURL文字列を引数に指定して下のスクリプトを実行すると、レスポンスヘッダとページ本文の内容が標準出力に出力される。
retrieve-webpage.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import print_function
# Python 3では、Python 2で "urllib2" とされていたものをもとに
# 細かく分割されており、 "urllib2" の名前では存在しない
try:
from urllib.request import urlopen # Python 3
# 取得したテキストを表示するための処理がPython 3とPython 2とで異なるため
# 表示用の関数をそれぞれ定義しておくことにする
# 入力は改行文字を含むため、print()のend引数を空文字列にする
def print_line (l):
print (str (l, encoding = 'utf8'), end = '')
except ImportError:
from urllib2 import urlopen # Python 2
def print_line (l):
print (l, end = '')
import sys
if len (sys.argv) < 2:
sys.exit ('USAGE: {0} [URL]'.format (__file__))
# "http://www.example.com/" などのURL文字列を受け付ける
# HTTP,HTTPSのいずれのURLスキームも指定されていない場合("www.example.com" など)
# HTTPと解釈することにする
url = sys.argv[1]
if not url.startswith ('http://') and not url.startswith ('https://'):
url = 'http://' + url
try:
# URLを開く
f = urlopen (url)
# HTTPヘッダの各行の内容を表示
for l in str (f.info ()).rstrip ().splitlines ():
print (l)
print ('-' * 80)
# Content-Typeを取得し、テキスト系の形式(text/*)でのみ内容を表示する
if f.info ().get ('Content-Type').startswith ('text/'):
try:
for l in f:
# Python 3では行ごとのデータはバイト列として得られるので
# エンコーディングを指定してデコードする必要がある
print_line (l)
except IOError as e:
sys.exit ('Could not read from URL "{0}": {1}'.format (url, e))
finally:
f.close ()
else:
print ('(Binary data)')
f.close ()
except IOError as e:
sys.exit ('Could not open URL "{0}": {1}'.format (url, e))
実行例$ /path/to/retrieve-webpage.py http://www.example.com/ Accept-Ranges: bytes ... Connection: close -------------------------------------------------------------------------------- <!doctype html> <html> ... </html>
ユーザエージェントの扱いと変更方法
既定では “urllib.request” (旧urllib2) を用いてHTTPサーバに要求を送信する際に伝えるユーザエージェント文字列は “Python-urllib/X.Y” [3]となっている。
これを変更するには、事前にRequest
クラスをインポートしておき、urlopen()
の引数に文字列ではなく同クラスのオブジェクトを事前に作成して入れ、そのオブジェクト作成時の最初の引数にURL文字列を指定したうえで辞書型の引数headers
の “User-Agent
” に対する値として別のユーザエージェント文字列を指定する。
もう1つの方法はbuild_opener()
で作成したOpenerDirector
クラスのオブジェクトのメンバaddheaders
に “[('User-agent', 'CUSTOM USER AGENT')]” 形式の値[4]を代入してメンバ関数open()
をurlopen()
の代わりに呼び出すものとなる。
プロキシサーバの扱い
GNU/Linuxでurllibを使用する場合、プロキシサーバについては実行時の環境変数により自動的に処理される。
プロトコル | 環境変数名 |
---|---|
HTTP | http_proxy |
HTTPS | https_proxy |
値には “http://[プロキシサーバ]:[ポート番号]” 形式の文字列を指定する。
(HTTPとHTTPSの両方でローカルプロキシの8080番を一時的に指定する例) $ http_proxy='http://localhost:8080' https_proxy='http://localhost:8080' /path/to/retrieve-webpage.py https://www.example.com/
常にこの設定が必要な場合は使用している端末シェルの初期化設定ファイル(Bashの場合は[ホームディレクトリ]/.bashrc
)やグラフィカルログイン時の自動実行スクリプト([ホームディレクトリ]/.xprofile
など・GUI環境依存)やOSごとの環境変数設定ファイル(ディストリ依存)などで環境変数を設定しておく。
環境変数による処理を行わずにプログラム内で設定を行うことも可能で、build_opener()
にProxyHandler
クラスのオブジェクト[5]を渡してメンバ関数open()
でURLを開く。
ユーザエージェントやプロキシの処理を含む例
下は見出し:簡単な例のサンプルをもとにしてユーザエージェントを指定したものとなる。使い方や出力は基本的に同じ。プロキシ関係の設定をプログラムで行う場合の処理についても、コメントアウトされた形で入れている(使用する場合は環境に合わせた編集が必要)。
retrieve-webpage-custom-ua.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import print_function
try:
from urllib.request import Request, urlopen # Python 3
##from urllib.request import ProxyHandler, build_opener
def print_line (l):
print (str (l, encoding = 'utf8'), end = '')
except ImportError:
from urllib2 import Request, urlopen # Python 2
##from urllib2 import ProxyHandler, build_opener
def print_line (l):
print (l, end = '')
import sys
if len (sys.argv) < 2:
sys.exit ('USAGE: {0} [URL]'.format (__file__))
url = sys.argv[1]
if not url.startswith ('http://') and not url.startswith ('https://'):
url = 'http://' + url
try:
# 辞書型の引数headersにて "User-Agent" に対する値として文字列を記述した
# RequestクラスのオブジェクトをURL文字列の代わりに渡すと
# 任意の文字列がユーザエージェントとして使用できる
f = urlopen (Request (url, headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240'}))
# build_opener()で作成したOpenerDirectorクラスのオブジェクトの
# メンバ関数open()を *urlopen()の代わりに* 用いることもできる
# (別途import文でbuild_openerもインポートしておく)
##od = build_opener ()
# プロキシの設定を環境変数からではなくプログラム内で行う場合は
# build_opener()の引数にProxyHandlerクラスのオブジェクトを渡す
# (別途import文でProxyHandlerもインポートしておく)
##od = build_opener (ProxyHandler ({'http' : 'http://localhost:8080', 'https' : 'http://localhost:8080'}))
# メンバaddheadersでユーザエージェントを書き換えることができる("a" は小文字)
##od.addheaders = [('User-agent', 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240')]
# 下のコメントを解除する場合は上にあるurlopen()の行をコメントにするか消す
##f = od.open (url)
for l in str (f.info ()).rstrip ().splitlines ():
print (l)
print ('-' * 80)
if f.info ().get ('Content-Type').startswith ('text/'):
try:
for l in f:
print_line (l)
except IOError as e:
sys.exit ('Could not read from URL "{0}": {1}'.format (url, e))
finally:
f.close ()
else:
print ('(Binary data)')
f.close ()
except IOError as e:
sys.exit ('Could not open URL "{0}": {1}'.format (url, e))
他記事における使用例(追記)
- TiMidity++で使える音源(GUS patch)“Eawplus”と自作インストーラ: ループを用いてファイルのダウンロード・保存処理を行っている
- Python 2.7.11, 3.5.1