プロジェクト

全般

プロフィール

操作

第14章 インターネット上のデータを扱う

14.1 URL をパースする urllib.parse

URL をパースする urllib.urlparse()

URL を構成要素 (アドレススキーム、ネットワークロケーション、パス、クエリ 等) に分解できる

  • urlparse(urlstring, scheme='', allow_fragments=True)
    • urlstring パース対象 URL

    • scheme URL スキームを指定 (URL 内にスキーム指定が無い場合のみ有効)

    • allow_fragments True の場合、フラグメント識別子をパースする

    • 戻り値 urllib.parse.ParseResult インスタンス

      • ParseResult は 6 要素の named_tuple で、他にも属性持つ
      属性 インデックス 該当箇所無しの場合の値
      scheme 0 URL スキーム scheme パラメータ
      netloc 1 ネットワーク上の位置 空文字列
      path 2 パス階層 空文字列
      params 3 URL 引数 (; 以降の文字列) 空文字列
      query 4 クエリ文字列 (? 以降の文字列) 空文字列
      fragment 5 フラグメント識別子 (# 以降の文字列) 空文字列
      username ユーザー名 None
      password パスワード None
      hostname ホスト名 None
      port ポート番号 None
      >>> from urllib.parse import urlparse
      >>> o = urlparse("http://docs.python.org:80/3/library/urllib.parse.html?"
      ...              "highlight=params#url-parsing")
      >>> o
      ParseResult(scheme='http', netloc='docs.python.org:80', path='/3/library/urllib.parse.html', params='', query='highlight=params', fragment='url-parsing')
      >>> o.scheme
      'http'
      >>> o.port
      80
      >>> o[2]  # path
      '/3/library/urllib.parse.html'
      

クエリ文字列をパースする parse_qs()

  • クエリ文字列をパースして辞書を返す

  • parse_qs(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace', max_num_fields=None, separator='&')

    • qs クエリ文字列
    • keep_blank_values False の場合、ブランク文字列を無視する
    • strict_parsing False の場合、パース処理中のエラーを無視する
    • encoding エンコードする際の文字コードを指定
    • errors エンコードする際の動作を指定
    • max_num_fields 読み取るフィールドの最大数を指定 (最大数を超えると ValueError)
    • separator 分離に使用する文字列を指定
    • 戻り値 dict
      • 形式は {key1: [val1-1,val2,...], key2: [val2-1, val2-2, ...], ...}
      • キーに対して、(値が1つの場合でも) 値のリスト
  • 同様の関数に parse_qsl() があり、こちらはペア (2要素タプル) のリストを返す

    • 同一キーに複数の値が設定されている場合の戻り値に注意
      • parse_qs では辞書の単一キーにリストで複数の値
      • parse_qsl では1つ目の要素が同じ複数タプルがリストに含まれる
    >>> from urllib import parse
    >>> result = parse.urlparse('https://www.google.co.jp/search?q=python&oq=python&sourceid=chrome&ie=UTF-8')
    >>> result.query
    'q=python&oq=python&sourceid=chrome&ie=UTF-8'
    >>> parse.parse_qs(result.query)  # パース結果を辞書として受け取りたい場合
    {'q': ['python'], 'oq': ['python'], 'sourceid': ['chrome'], 'ie': ['UTF-8']}
    >>> parse.parse_qs('key=1&key=2')  # 1つのキーに対して値が複数ある場合の例
    {'key': ['1', '2']}
    >>> parse.parse_qsl(result.query)  # パース結果をタプルのリストとして受け取りたい場合
    [('q', 'python'), ('oq', 'python'), ('sourceid', 'chrome'), ('ie', 'UTF-8')]
    >>> parse.parse_qsl('key=1&key=2')  # 値が複数ある場合、parse_qsとは異なり2つのタプルとなる
    [('key', '1'), ('key', '2')]
    

クエリ文字列を組み立てる urlencode()

  • urlencode(query, doseq=False, safe='', encoding=None, errors=Noen, quote_via=quote_plus)

    • query クエリ文字列へ変換するデータ構造 (マッピングオブジェクト、または2要素タプルのシーケンス)
    • doseq マッピングの値、タプルの第2要素がシーケンスの場合の動作を指定
      • True の場合、(同一キーの) 複数クエリへ分解される
      • False の場合、シーケンス部分がそのまま文字列値扱い
    • safe URL エンコードしない文字を指定
    • encoding エンコードする際の文字コードを指定
    • errors エンコードする際の動作を指定
    • quote_via エンコードに使用する関数を指定 quate_plus quate
    • 戻り値 str
    >>> from urllib import parse
    >>> dic = {"name": "smith", "age": 41}
    >>> tpl = [("name", "smith"), ("age", 41)]
    >>> qs_dic = parse.urlencode(dic)
    >>> qs_dic
    'name=smith&age=41'
    >>> qs_tpl = parse.urlencode(tpl)
    >>> qs_tpl
    'name=smith&age=41'
    >>> tpl2 = [("name", "smith"), ("birthday", [9, 28])]  # 値にシーケンスを指定
    >>> qs_tpl2 = parse.urlencode(tpl2)
    >>> qs_tpl2
    'name=smith&birthday=%5B9%2C+28%5D'  # doseq=False (デフォルト) の場合の文字列
    >>> qs_tpl3 = parse.urlencode(tpl2, doseq=True)
    >>> qs_tpl3
    'name=smith&birthday=9&birthday=28'  # doseq=True の場合の文字列
    >>> dic2 = {"name": "smith", "birthday": [9, 28]}  # 値にシーケンスを指定
    >>> qs_dic2 = parse.urlencode(dic2)
    >>> qs_dic2
    'name=smith&birthday=%5B9%2C+28%5D'  # doseq=False (タプルと同じ)
    >>> qs_dic3 = parse.urlencode(dic2, doseq=True)
    >>> qs_dic3
    'name=smith&birthday=9&birthday=28'  # doseq=True (タプルと同じ)
    >>> parse.parse_qs(qs_dic3)
    {'name': ['smith'], 'birthday': ['9', '28']}  # doseq=True の場合のクエリをパースすると戻る
    >>> parse.parse_qs(qs_dic2)
    {'name': ['smith'], 'birthday': ['[9, 28]']}  # doseq=False の場合、文字列 '[9, 28]' が値となっていた
    

URL として使用できる文字列に変換する quate(), quote_plus()

文字列を URL として使用できるようにパーセントエンコードする

  • quote(string, safe='/', encoding=None, errors=None)

    • string エンコード対象文字列
    • safe パーセントエンコードしない文字を指定
    • encoding パーセントエンコードする際の文字エンコードを指定
    • errors パーセントエンコードする際の動作を指定
    • 戻り値 str または bytes
  • 同様の関数に quote_plus() がある

    スペース文字 safe
    quote() %20 へ変換 /
    quote_plus() + へ変換 (空文字)

URL を結合する urljoin()

  • urljoin(base, url, allow_fragments=True)
    • base ベースとなる URL
    • url 結合に使用する URL やパス
    • allow_fragments True の場合、フラグメント識別子をパースする
    • 戻り値 str
  • url には相対パスも指定可能 (../../menu など)
  • base url 両方に URL を指定した場合、url が返る

14.2 URL を開く urllib.request

URL を開くためのインターフェースを提供する標準ライブラリ (ただし、より高水準な「Requests」パッケージが公式でも推奨されている)

指定の URL を開く urllib.request.urlopen

  • 関数 urlopen(url, data=None[, timeout], *, cafile=None, capath=None, cadefault=False, context=None)

    • url URL を文字列または Request オブジェクトで指定
    • data URL に POST するデータを bytes あるいは file オブジェクトで指定 (GET の場合は None である必要がある)
    • timeout タイムアウト時間を秒で指定
    • context ssl.SSLContext クラスのインスタンスを指定
    • 戻り値 HTTP(S) の場合は http.client.Response、他は基本 rullib.response.addinfourl
  • cafile, capath, cadefault は 3.6 以降で非推奨となり、context が推奨

  • urlopen には HTTP メソッド指定は無く、URL 指定のみなら GET に (クエリ文字列で情報は与えられる)、data を指定すれば POST 扱いとなる

    • その他のメソッドは url に urllib.request.Request インスタンス (メソッドを指定しておく) を渡す
    # GET リクエスト
    >>> from urllib import request
    >>> with request.urlopen('https://httpbin.org/get') as f:
    ...     res = f.read()[:92]
    >>> res
    b'{\n  "args": {}, \n  "headers": {\n    "Accept-Encoding": "identity", \n    "Host": "httpbin.org'
    
    # クエリ付き GET リクエスト
    >>> res = request.urlopen('hppts://httpbin.org/get?key1=value1')
    
    # POST リクエスト (data を指定)
    >>> data = 'key1=value1&key2=value2'
    >>> res = request.urlopen('https://httpbin.org/post', data=data.encode())
    >>> res.status
    200
    

GET, POST 以外の HTTP メソッドを扱う urllib.request.Request

  • class Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)

    • url URL を指定
    • data URL に送信するデータを bytes または file-like オブジェクトで指定
    • headers HTTP ヘッダーを辞書で指定
    • method HTTP メソッドを指定
  • Request インスタンスを urlopen の url へ指定することで、任意の HTTP メソッドを使用して URL へリクエストできる

    # DELETE メソッド
    >>> from urllib import request
    >>> data = 'key1=value1&key2=value2'
    >>> req = request.Request('https://httpbin.org/delete', data=data.encode(), method='DELETE')  # DELETEメソッドを使うリクエストを作成
    >>> with request.urlopen(req) as f:
    ...     res_body = f.read()[:110]
    ...     res_status = f.status
    ...
    >>> res_status
    200
    >>> res_body
    b'{\n  "args": {}, \n  "data": "", \n  "files": {}, \n  "form": {\n    "key1": "value1", \n    "key2": "value2"\n  }, \n'
    

レスポンスモジュール urllib.response

urllib で使用するレスポンスモジュール urllib.response は file-like オブジェクトで、read() readline() 等のメソッドが使用可能. 実際に取得されるのは urllib.response.addinfourl インスタンス

  • addinfourl の属性
    • url
    • headers
    • status

14.3 ヒューマンフレンドリーな HTTP クライアント Requests

urllib.request と同様に GET や POST リクエストを行う機能を提供
インポートは import requests

指定の URL を開く

Requests では HTTP メソッドごとに対応するインターフェースが存在

HTTP メソッド 対応インターフェース
GET requests.get()
HEAD requests.head()
POST requests.post()
PATCH requests.patch()
PUT requests.put()
DELETE requests.delete()
OPTIONS requests.options()
>>> import requests
>>> r = requests.get('http://httpbin.org/get')
>>> r
<Response [200]>

>>> r.text
'{\n  "args": {}, \n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Host": "httpbin.org", \n    "User-Agent": "python-requests/2.23.0", \n    "X-Amzn-Trace-Id": "Root=1-6058a4b8-363cb0c415a43cc3406ae42c"\n  }, \n  "origin": "219.121.30.187", \n  "url": "http://httpbin.org/get"\n}\n'
>>> r = requests.get('http://httpbin.org/get', params='example')  # クエリ付き GET
>>> r.url
'http://httpbin.org/get?example'
>>> r = requests.get('http://httpbin.org/get', params={'key': 'value'})  # クエリ付き GET (辞書指定)
>>> r.url
'http://httpbin.org/get?key=value'
  • レスポンスオブジェクト

    • Requests では HTTP リクエストの結果を requests.models.Response オブジェクトで返す

      属性 解説
      request リクエスト情報を保持するオブジェクト
      url リクエスト時の URL 文字列
      cookies レスポンスに含まれる Cookie 情報を保持するオブジェクト
      headers 辞書形式のレスポンスヘッダー
      status_code レスポンスの HTTP ステータスコード
      ok レスポンスの HTTP ステータスコードが正常である場合は True、そうでない場合は False
      text 文字列にエンコード済みのレスポンスボディ
      iter_lines() レスポンスボディを1行ずつ返すイテレーターを返す (文字列では無くバイト列を返す)
      json() レスポンスボディを JSON フォーマットとしてパースし、辞書を返す
    • Requests オブジェクトは bool として評価すると以下の通り値を返す為、分岐に利用できる

      • HTTP ステータスが 400 未満: True
      • HTTP ステータスが 400 以上: False
  • Requests では Session モードをサポートしている

    • ログインが必要な Web サイトにおいても、永続的コネクションや Cookie を使用できる

    • requests.Session() で生成した Session オブジェクトのメソッドとして get() post() 等を使用する

       >>> import requests
       >>> session = requests.Session()
       >>> response = session.get('https://httpbin.org/cookies/set?title=pylibbook2&libName=requests')
       >>> session.cookies.get_dict()  # クエリ文字列がCookieとして保存されていることを確認
       {'libName': 'requests', 'title': 'pylibbook2'}
       # さらに別のクエリ文字列を追加してみる
       >>> response = session.get('https://httpbin.org/cookies/set?description=sessionTest')
       >>> session.cookies.get_dict()  # Cookieが追加されていることを確認
       {'description': 'sessionTest', 'libName': 'requests', 'title': 'pylibbook2'}
      

14.4 Base16, Base64 などへエンコードする base64

データのエンコード、デコードを扱う base64 モジュール

  • base64 では以下のエンコード方式を扱うことが可能
    • Base16
    • Base32
    • Base64
    • Base85

これらのエンコード方式は、アルファベットと数字だけなどの限られた文字種類しか扱えない環境において、それ以外の文字 (マルチバイト文字、バイトデータ等) を扱う為に利用される. 最も広く利用されている Base64 は、電子メールや URL の一部、 HTTP リクエストの一部に使われている

Base64 にエンコードする base64.b64encode()

  • 関数 b64encode(s, altchars=None)

    • s Base64 アルファベットにエンコードする バイトデータ を指定
    • altchars + と / の代わりに使用する バイト列 を指定
    • 戻り値 bytes


    Base64 エンコードで扱うのはバイト列であり、文字列を指定すると TypeError となる (文字列を扱う場合は encode() でバイト列化して使用する)

     >>> import base64
     >>> s = 'Python は簡単に習得でき、それでいて強力な言語の1つです。'
     >>> base64.b64encode(s)  # 文字列を渡すとエラー
     Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
       File "/xxxxxxx/python3.9/base64.py", line 58, in b64encode
        encoded = binascii.b2a_base64(s, newline=False)
     TypeError: a bytes-like object is required, not 'str'
     >>> base64.b64encode(s.encode())  # バイト文字列にエンコードして渡す
     b'UHl0aG9uIOOBr+ewoeWNmOOBq+e/kuW+l+OBp+OBjeOAgeOBneOCjOOBp+OBhOOBpuW8t+WKm+OBquiogOiqnuOBruS4gOOBpOOBp+OBmeOAgg=='
     # +と/の代わりに使用する文字列をaltcharsで指定する
     >>> base64.b64encode(s.encode(), altchars=b'@*')
     b'UHl0aG9uIOOBr@ewoeWNmOOBq@e*kuW@l@OBp@OBjeOAgeOBneOCjOOBp@OBhOOBpuW8t@WKm@OBquiogOiqnuOBruS4gOOBpOOBp@OBmeOAgg=='
    

    altchar の指定で + と / の置き換えが指定できるが、URL で安全に利用可能なエンコード文字列を返す urlsafe_b64encode() もある (+- に、/_ になる)

Base64 からデコードする base64.b64decode()

  • 関数 b64decode(s, altchars=None, validate=False)

    • s デコードする バイト列 を指定
    • altchar + と / の代わりに使用する バイト列 を指定
    • validate True の場合、入力に Base64 アルファベット以外の文字があると binascii.Error を返す
    • 戻り値 bytes
     >>> s = 'Python は簡単に習得でき、それでいて強力な言語の1つです。'
     # Base64エンコード/デコード
     >>> enc_s = base64.b64encode(s.encode())
     >>> base64.b64decode(enc_s).decode()
     'Python は簡単に習得でき、それでいて強力な言語の1つです。'
     # Base64アルファベット以外のaltcharsを指定したものをvalidate=Trueでデコードするとエラーになる
     >>> enc_s = base64.b64encode(s.encode(), altchars=b'-_')
     >>> base64.b64decode(enc_s, validate=True)
     Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
       File "/xxxx/python3.9/base64.py", line 86, in b64decode
         raise binascii.Error('Non-base64 digit found')
     binascii.Error: Non-base64 digit found
    

    b64encode() に対する urlsafe_b64encode と同様に、b64decode() に対する urlsafe_b64decode() も存在する

base64 の使われ方

文字列しか扱えないがバイナリデータを送受信したい場面で利用される

  • 例えば
    • 電子メールにバイナリを添付
    • HTTP リクエストでバイナリを送信
    • JSON にバイナリ (画像等) を含める

14.5 電子メールのデータを処理する email

電子メールメッセージや MIME (Multipurpose Internae Mail Extensions) などのメッセージ文書を管理する機能を提供

  • メッセージデータを管理 email.message
  • メールのメッセージを解析 email.parser

メッセージのデータを管理する email.message

最も使用されるのは EmailMessage クラス

メソッド名 解説 戻り値
as_string(unixfrom=False, maxheaderlen=None, policy=None) メッセージ全体を文字列で返す str
as_bytes(unixfrom=None, policy=None) メッセージ全体をバイト列で返す bytes
is_multipart() メッセージのペイロードがマルチパート (複数のデータを含む) 場合に True bool
keys() ヘッダーのフィールド名のリストを返す list
values() メッセージ内のすべてのフィールドの値を返す list
items() ヘッダーのすべてのフィールドと値のタプルのリストを返す list
get(name, failobj=None) 指定したヘッダーフィールドの値を返す str
get_all(name, failobj=None) 指定したフィールドのすべての値を返す list
add_header(_name, _value, **_params) 拡張ヘッダーを設定する None
get_content_charset(failobj=None) Content-Type の charset パラメータの値を返す str
get_content_type() Content-Type の値を返す str
set_content(msg, obj, *args, **kw) email.contentmanager.ContentManager クラスを呼び出し、メッセージを設定する None
add_attachment(*args, content_manager=None, **kw) multipart/mixed の添付ファイルを指定する None
 >>> import email.message
 >>> msg = email.message.EmailMessage()
 # ヘッダーをセット
 >>> msg.add_header("From", "kadowaki@example.com")
 >>> msg.add_header("To", "somebody@example.com, anyone@example.com")
 >>> msg.add_header("Subject", "Test Mail")
 # メッセージをセット
 >>> msg_body = """
 ... Hello Python!
 ...
 ... Bye!!
 ... """
 >>> msg.set_content(msg_body)
 >>> msg.is_multipart()  # マルチパートか確認
 False
 >>> msg.keys()  # ヘッダーの一覧を取得
 ['From', 'To', 'Subject', 'Content-Type', 'Content-Transfer-Encoding', 'MIME-Version']
 >>> msg.values()  # ヘッダーの値の一覧を取得
 ['kadowaki@example.com', 'somebody@example.com, anyone@example.com', 'Test Mail', 'text/plain; charset="utf-8"', '7bit', '1.0']
 >>> msg.get("From")  # Fromの値を取得
 'kadowaki@example.com'
 >>> msg.get_payload()  # ペイロード(本文)を取得
 '\nHello Python!\n\nBye!!\n'
 >>> msg.as_string()  # メッセージ全体を文字列で取得
 'From: kadowaki@example.com\nTo: somebody@example.com, anyone@example.com\nSubject: Test Mail\nContent-Type: text/plain; charset="utf-8"\nContent-Transfer-Encoding: 7bit\nMIME-Version: 1.0\n\n\nHello Python!\n\nBye!!\n'
 # メッセージにファイルを添付する例
 >>> with open('email.txt', 'rb') as f:
 ...     data = f.read()
 ...
 >>> msg.add_attachment(data, maintype='text', subtype='plain')
 >>> msg.add_header('Content-Disposition', 'attachment', filename='email.txt')
 >>> msg.is_multipart()
 True

メールを解析する email.parser

メールのメッセージを解析する

  • class Parser(_class=None, *, policy=policy.compat32)

    • テキストをパースするオブジェクトを生成
    メソッド名 解説 戻り値
    parse(fp, headersonly=False) テキストストリーム fp を解析しメッセージを生成する
    headersonly が True の場合、ヘッダー部分のみ解析する
    email.message.Message
    parsestr(text, headersonly=False) 指定された文字列を解析する email.message.Message
  • class BytesParser(_class=None, *, policy=policy.compat32)

    • バイナリ (バイト列) をパースするオブジェクトを生成
    メソッド名 解説 戻り値
    parse(fp, headersonly=False) バイナリストリーム fp を解析しメッセージを生成する
    headersonly が True の場合、ヘッダー部分のみ解析する
    email.message.Message
    parsebytes(bytes, headersonly=False) 指定された bytes-like オブジェクトを解析する email.message.Message
  • パーサオブジェクトを生成せずに直接パースする関数もある

    • email.message_from_file(fp): Parser().parse(fp) 相当
    • email.message_from_string(s): Parser().parsestr(s) 相当
    • email.message_from_binary_file(fp): BytesParser().parse(fp) 相当
    • email.message_from_byptes(s): BytesParser().parsebytes(s) 相当
  • Parser BytesParser は policy を指定しないと (email.message.EmailMessage ではなく) email.message.Message を返すが、これは後方互換の為に policy=policy.compat32 となっているから

    • policy=email.policy.default とすることで EmailMessage が取得できる

email モジュール とメール送信

  • email モジュールはメッセージ自体の管理を提供する
    • メール送信については設計されていない
  • メール送信には別途 smtplib モジュール等の SMTP 機能を利用する

Tatsuya ISHIGAKI さんが4ヶ月前に更新 · 1件の履歴