Pythonのurllibでネット上のコンテンツを我が物にする

45, 2019-07-14

目次

urllibを使うとネット上のコンテンツを取得できる

こんにちは、narupoです。

スクレイピングなどでインターネット上のコンテンツをローカルのPCに持ってきたい、保存したいということがあると思います。
Pythonではそういう時はurllibを使うと実現できます。

この記事ではurllibの簡単なサンプルを多数掲載しています。
urllibの大まかな使い方を知りたい時に読むのが適しています。

urllibのインストール方法

urllibはPythonの標準ライブラリに含まれているのでインストールは不要です。

お手軽

ネット上のコンテンツをGETする

urllibでネット上のコンテンツを取得するにはurllib.requestを使います。
urllib.requestはURLを指定してソケット通信をしたりするのに適したモジュールです。

urllib.requestのインポート方法

urllib.requestをインポートするには↓のようにします。

import urllib.request

urllib.requestとタイプするのがだるい場合はエイリアスを付けてインポートすると良いでしょう。

import urllib.request as ur

調べてみましたが、このエイリアスに慣例は無いようです。↑のサンプルではurとしています。
この記事ではエイリアスを使わずに解説します。

urlopenでレスポンスを得る

urlopenにURLを渡すとレスポンスのオブジェクトを取得できます。
このレスポンスはhttp.client.HTTPResponseです。
http.client.HTTPResponseはファイルに似たオブジェクトです。
ファイルと同じように使い終わったらcloseする必要があります。

import urllib.request

# URLを開いてレスポンスを得る
response = urllib.request.urlopen('http://localhost')

print(type(response))
# <class 'http.client.HTTPResponse'>

# レスポンスを閉じる
response.close()

レスポンスからコンテンツを得る

http.client.HTTPResponseからコンテンツを得るにはreadメソッドを使います。
readはバイト列のコンテンツを返します。
バイト列をUTF-8の文字列にデコードするにはdecodeを使いましょう。

これであなたはネット上のコンテンツを扱いやすい文字列で取得することが出来ました。
あとはBeautifulSoupで穴だらけにしたりしても、あなたの自由です。
ローカルのファイルシステム上という自由な環境でじっくりと料理すると良いでしょう。

BeautifulSoupの使い方は「BeautifulSoupの美しさを知りませんでした。実際に使ってみるまでは」を参照してみてください。

import urllib.request

# URLを開いてレスポンスを得る
response = urllib.request.urlopen('http://localhost')

# readでコンテンツを読み込む
content = response.read()
response.close() # 用済み

print(type(content))
# <class 'bytes'>

# バイト列をUTF-8の文字列にデコードでして表示(デコードしないと日本語が表示されない)
print(content.decode())
# <html>
# <head>
#     <title>urllib.requestのテスト</title>
#     <meta charset="utf-8" />
# </head>
# <body>
#     <h1>Hello, World!</h1>
#     <h1>こんにちは、おまえら!</h1>
# </body>
# </html>

with文で楽をする

レスポンスをcloseするのがめんどくさければwith文を使うと良いでしょう。
↓を見るとレスポンスがファイルオブジェクトとそっくりであることがよくわかります。

import urllib.request

# http.client.HTTPResponseはもはやファイルである
with urllib.request.urlopen('http://localhost') as fin:
    content = fin.read()
    print(content.decode())

もっとも、ソケット通信ではエンドポイントのディスクリプタを作成することになるので、HTTPResponseのインターフェースがファイル・オブジェクトと類似するのは当然のことと言えるかもしません。
HTTPResponseも内部ではfpというメンバ変数でソケットのファイル・ポインタを保持しています。
↓のサンプルではレスポンスが持っているディスクリプタの値を出力しています。

import urllib.request

with urllib.request.urlopen('http://localhost') as fin:
    print(fin.fileno())

コンテンツを一行ずつ読み込む

レスポンスから一行ずつコンテンツを取得するにはreadlineを使います。
readlineはEOFに達すると空文字列を返すので、↓のようにしてループから脱出します。

import urllib.request

with urllib.request.urlopen('http://localhost') as fin:
    while True:
        # 1行ずつ読み込む
        line = fin.readline()
        print(type(line))
        # <class 'bytes'>

        if not len(line):
            break
        print(line.decode())

sys.stdin.readlineと同じですね。
HTTPResponseはio.BufferedIOBaseを継承したクラスです。
このクラスはIOBaseを継承しています。

class BufferedIOBase(_io._BufferedIOBase, IOBase):
    __doc__ = _io._BufferedIOBase.__doc__

sys.stdinは_io.TextIOWrapperですが、TextIOWrapperもさかのぼっていくとIOBaseを継承しています。

class TextIOWrapper(TextIOBase):
    ...

class TextIOBase(IOBase):
    ....

readlineはIOBaseで定義されているメソッドです。
(先述したfilenoもIOBaseで定義されています)
つまり、sys.stdinとhttp.client.HTTPResponseは同じ祖先をもっているんですね。

人類みな兄弟

Basic認証を通過する

Basic認証でログインしてからコンテンツを取得したい場合は↓のようにします。

import urllib.request

# パスワードマネージャーにログイン情報を追加する
passwd_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
passwd_mgr.add_password(
    realm=None,
    uri='http://localhost:8123/', # このuriはベースのuri
    user='taro',
    passwd='pass1234',
)

# ハンドラーを作成する
auth_handler = urllib.request.HTTPBasicAuthHandler(passwd_mgr)

# オープナーを作成してurllib.requestにインストールする
opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)

# Basic認証で制限されているURLを開く
with urllib.request.urlopen('http://localhost:8123/bauth.html') as fin:
    print(fin.read().decode())
    # <h1>Welcome to underground...</h1>

プロキシサーバーを中継する

プロキシサーバーを中継したい場合はurllib.request.ProxyHandlerからオープナーを作成します。
これでopenerは指定したプロキシサーバーを中継してリクエストを送ります。

import urllib.request

# プロキシサーバーのURLからハンドラーを作成
proxy_handler = urllib.request.ProxyHandler({
    'http': 'http://example.proxy.com:8765/',
})

# ハンドラーからオープナーを作成
opener = urllib.request.build_opener(proxy_handler)
print(type(opener))
# <class 'urllib.request.OpenerDirector'>

# コンテンツを読み込む
with opener.open('http://example.com') as fin:
    print(fin.read().decode())

プロキシサーバーを中継してBasic認証を通過する

urllib.request.ProxyHandlerとurllib.request.ProxyBasicAuthHandlerを組み合わせてオープナーを作成すれば可能です。

import urllib.request

# プロキシサーバーのURLからハンドラーを作成
proxy_handler = urllib.request.ProxyHandler({
    'http': 'http://example.proxy.com:8765/',
})

# プロキシ経由のベーシック認証のためのハンドラーを作成
proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
proxy_auth_handler.add_password('realm', 'host', 'username', 'password')

# ハンドラーからオープナーを作成
opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)

# コンテンツを読み込む
with opener.open('http://example.com') as fin:
    print(fin.read().decode())

リクエスト・ヘッダーを設定する

リクエストのヘッダーを指定したい場合はRequestを作成して↓のようにします。

import urllib.request

req = urllib.request.Request('http://localhost/header')
req.add_header('Referer', 'http://localhost')
req.add_header('User-Agent', 'CatBrowser/0.1')

with urllib.request.urlopen(req) as fin:
    print(fin.read().decode())
    # あなたのリファラーは「http://localhost/」です。
    # ユーザーエージェントは「CatBrowser/0.1」です。

コンストラクタに指定も出来る

Requestのコンストラクタにheadersを渡してヘッダーを設定することも可能です。

import urllib.request

req = urllib.request.Request(
    url='http://localhost/header',
    headers={
        'Referer': 'http://localhost/bauth.html',
        'User-Agent': 'CatBrowser/0.1',
    },
)

with urllib.request.urlopen(req) as fin:
    print(fin.read().decode())
    # あなたのリファラーは「http://localhost/bauth.html」です。
    # ユーザーエージェントは「CatBrowser/0.1」です。

オープナーにヘッダーを設定

いちいちRequestを作成してリクエストを送るのがめんどくさいというあなたへ。
オープナーを作成すればヘッダーの設定は一度で済みます。

import urllib.request

opener = urllib.request.build_opener()
opener.addheaders = [
    ('Referer', 'http://localhost'),
    ('User-Agent', 'CatBrowser/0.1'),
]

with opener.open('http://localhost/header') as fin:
    print(fin.read().decode())
    # あなたのリファラーは「http://localhost/」です。
    # ユーザーエージェントは「CatBrowser/0.1」です。

URLパラメーターを付ける

urllib.parseurlencodeを使えばdictをURLパラメーターに変換することが可能です。

import urllib.request
import urllib.parse

# dictをURLパラメーターにする
params = urllib.parse.urlencode({
    'age': 20,
    'name': 'hanako',
})

# 作成したパラメーターをURLに付ける
url = 'http://localhost/params/?%s' % params
print(url)
# http://localhost/params/?age=20&name=hanako

with urllib.request.urlopen(url) as fin:
    print(fin.read().decode())
    # name: ['hanako']
    # age: ['20']

ネット上にコンテンツをPOSTする

特定のURLへコンテンツを投稿するのにもurllib.requestは使えます。
urllib.request.RequestにURLと投稿したいデータを渡してコンストラクトします。
あとはこのリクエストをurlopenに渡します。
dataを指定することでPOSTが可能です。

import urllib.request

# Requestを作成する
# POSTするバイト列をdataに設定する
req = urllib.request.Request(
    url='http://localhost/post/',
    data='これこれ、これだよ'.encode(), # 文字列をバイト列にエンコード
)

with urllib.request.urlopen(req) as fin:
    print(fin.read().decode())
    # 受け取ったデータ: これこれ、これだよ

メソッドを指定してリクエストを送る

メソッドを明示的に指定したい場合はRequestにmethodを指定します。
↓はPUTメソッドでリクエストを送るサンプルです。

import urllib.request

# PUTメソッドでリクエストする
req = urllib.request.Request(
    url='http://localhost/method/',
    method='PUT',
)

with urllib.request.urlopen(req) as fin:
    print(fin.read().decode())
    # メソッドは「PUT」です。

環境変数にプロキシを設定する

たとえばmy_proxyという環境変数にプロキシを設定したとします。
urllib.requestではこの値をgetproxiesで取得できます。

import urllib.request

# 環境変数からプロキシーリストを取得する
# 
# Bashでは以下のように環境変数を設定してスクリプトを実行できる
#
#   $ my_proxy=0.0.0.0 python main.py
# 
# 設定する環境変数のフォーマットは {scheme}_proxy である
# この {scheme} の部分が辞書のキーになる
#
print(urllib.request.getproxies())
# {'my': '0.0.0.0'}
# 
# ↑ my_proxyのmyがキーに、環境変数の値がバリューになっている

おわりに

urllibは用法容量を守って正しく使いましょう。
過剰なリクエストはサーバーに負荷をかけます。

サーバーにやさしいプログラミング!

以上、narupoでした。

スポンサーリンク

スポンサーリンク

スポンサーリンク

スポンサーリンク