Python3でBasic認証のあるAPIにx-www-form-urlencodedでPOSTし、Requestsとurllibを理解する


こんにちは。
入社2ヶ月でそろそろ新人ヅラできなくなり、立ち位置に困りはじめた、新米エンジニアのTerryです。

皆さんと一緒に勉強して、1日でも早く重鎮ヅラできるように頑張ります。

今回は、タイトル通りPython3でAPIを叩く話についてまとめたいと思います。
「そんなん、よくある話やんけ」という感じ、ごもっともですが、ちょっと珍しいタイプのAPIにPOSTする機会があったので。

それに、Google検索してて気付いたのですが、インターネットの海ではPython2系と3系の話がごっちゃになっていて、整備されていない感じもあったので、その辺明示的にしてあげたいなぁと。

例えば、Python3ではURLをエンコードしてparseする際に、urllibというモジュールを使うことが多いのですが、Python2にはurllib2とかあったりして、今回の投稿ではその辺りケアしていこうかと思います。

それでは早速、よろしくお願いします!

何がしたいのか

  • 以下の条件でAPIにPOSTしたい
    • 環境:Python3.6.3(要するに3.6系)
    • 送信形式:application/x-www-form-urlencoded
    • Basic認証あり

ちなみに、Basic認証とは、コレのことです。
一度くらい見たことがある人がほとんどだと思います。

スクリプト

さて、僕が今回使ったPythonスクリプトですが、結論から言うとこんな感じ。

import requests
import urllib

# 任意のurl(エンドポイント)
url = 'xxx.co.jp' 

# 送信するパラメータ(例)
params = {
'UserName': 'test',
'UserNameKana': 'テスト',
'MailAddress': '[email protected]',
'LoginPassword': 'password'
}

# URLをエンコード
params = urllib.parse.urlencode(params)

# headerでコンテンツタイプを指定
headers = {'Content-Type': 'application/x-www-form-urlencoded'}

# authにBasic認証のIDとPASSを設定する
r = requests.post(url=url, data=params, headers=headers, auth=('funfun', 'funfun'))

# 送信結果が知りたい場合には記載
print(r.status_code)
print(r.text)

さて、コードの中身を見ていきましょう。

使用するモジュール

Requests

いや今更いいよ、って感じですが一応。

Requestsは、PythonでHTTPメソッドを実行するプログラムを簡単に書けるモジュールです。

Requestsのドキュメント(Requests: HTTP for Humans)
http://docs.python-requests.org/en/latest/)

他にHTTPメソッドを実行する方法として、Pythonにはurllibモジュール(後述)にもrequestというライブラリが用意されていて、そちらを使うことも可能なんですが、requestの後に色々とメソッドを繋いだり引数を設定したりで、どうにも使いづらい。

urllib.request — URL を開くための拡張可能なライブラリ
https://docs.python.jp/3/library/urllib.request.html)

そのため、今回は一般的でより使いやすいRequestsを使っていきます。
Requestsについてわかりやすい記事はこちら。

Requestsの使い方(Python Library)
https://qiita.com/sqrtxx/items/49beaa3795925e7de666)

今回はAPIにデータを送信するので、使用するHTTPメソッドは当然POSTです。
ここで、今回のキモとなるPOST部分のコードを見てみます。

r = requests.post(url=url, data=params, headers=headers, auth=('funfun', 'funfun'))

引数を見ていきましょう。
今回解説が必要なのは、headersauthかと思います。

headers引数には、POSTしたいデータ形式を{'Content-Type': 'hogehoge'}で指定します。
今回は、タイトル通りx-www-form-urlencodedなので、それを指定します。
(x-www-form-urlencodedについては後述)

headers = {'Content-Type': 'application/x-www-form-urlencoded'}

次はauth引数で、タイトルの「Basic認証」のキモになる部分です。
RequestsはBasic認証についても、非常にシンプルに記述できます。

auth=('ユーザー名', 'パスワード')としてあげれば良いのです。
これでユーザー名とパスワードが合っていれば、Basic認証は通ります。

プラスαな話ですが、このままではPOSTがうまくいったかどうか分からないので、業務では送信したReqiestsのstatus_codeとtextをprintしてあげることをオススメします。

POSTの送信自体がうまくいったかどうかをステータスコードで確認し、POSTしたリクエストに対し期待通りのレスポンスが返ってきているかどうかをtextで確認できます。

HTTP 応答状態コード
https://developer.mozilla.org/ja/docs/Web/HTTP/Status)

なるほど、と言う感じですがRequestsひとつでやりたいことのほぼすべてをカバーできているあたり、Requestsの有能さを感じますね。

urllib

さてバージョンを意識する上ではコイツがちょっとクセ者なんですが、結論から言うと、urllib2はurllibに統合されました。
正確に言うと、urllib2はurllibに.request()と.error()に分割され、urllibに統合されたということです。

ドキュメントをよく読むと、そのような旨が書いてありますね。

20.6. urllib2 — URL を開くための拡張可能なライブラリ(注:Python2.7の記事)
https://docs.python.jp/2.7/library/urllib.html#module-urllib)

ちなみに、実はPythonにはurllib3というのもあるんですが、Requestsでやりたいことがほとんどできてしまうので、使うことはあまりないかと思います。

urllib3 Sanity-friendly HTTP client.
http://urllib3.readthedocs.io/en/latest/)

ちょっと話が逸れましたが、一旦はurllibだけ意識しとけばいいよ、って感じです。
さてさて、今回urllibを用いたのは、以下のコード部分。

params = {
'UserName': 'test',
'UserNameKana': 'テスト',
'MailAddress': '[email protected]',
'LoginPassword': 'password'
}
params = urllib.parse.urlencode(params)

urllibちゃんのあまりイケてないところは、メソッドが何をしているのかよく分からんところです。
ドキュメントに立ち返り、何をしているのかをよく見てみましょう。

21.8. urllib.parse — URL を解析して構成要素にする
https://docs.python.jp/3/library/urllib.parse.html#urllib.parse.urlencode)

マッピング型オブジェクトまたは2個の要素からなるタプルのシーケンス(strかbytesオブジェクトが含まれているかもしれません)を、パーセントエンコードされたASCII文字列に変換します。戻り値の文字列がurlopen()関数でのPOST操作のdataで使用される場合はバイト列にエンコードしなければなりません。そうでない場合はTypeErrorが送出されます。戻り値は'&'文字で区切られたkey=valueのペアからなる一組の文字列になります。keyとvalueはquote_viaを使用してクオートされます。デフォルトで、値をクォートするためにquote_plus()が使用されます。つまり、スペースは'+'文字に、'/'文字は%2Fにクォートされます。これはGETリクエストの標準に準拠します(application/x-www-form-urlencoded)。 quote_viaとして渡すことができる別の関数はquote()です。それはスペースを%20にエンコードし、'/'をエンコードしません。何がクォートされるかを最大限コントロールしたければ、quoteを使ってsafeに値を指定してください。

なるほど、わからん。

はい、よくわからん場合は、実際にインタプリタで実行して確かめてみましょうね。

>>> import urllib
>>> params = {
... 'UserName': 'test',
... 'UserNameKana': 'テスト',
... 'MailAddress': '[email protected]',
... 'LoginPassword': 'password'
... }
>>>
>>> params = urllib.parse.urlencode(params)
>>> print(params)
UserName=test&UserNameKana=%E3%83%86%E3%82%B9%E3%83%88&MailAddress=test%40example.com&LoginPassword=password

やっとわかりそう!
ポイントは2つほど。

まず1つ目は、dict型であるパラメーターをkey=valueの形に成形し、各key=valueを&で繋いであげていることです。
これによって、プログラムがわかりやすい形にデータを変えてあげているわけですね。

もう1つは、日本語が%エンコードされていることです。
'UserNameKana': 'テスト'UserNameKana=%E3%83%86%E3%82%B9%E3%83%88になっています。

これも趣旨は先ほどと一緒で、機械に理解できない日本語をわかりやすいようにASCIIに翻訳してくれていると。

つまりurllib.parse.urlencodeは、人間にわかりやすい形式だったparamsのデータをparseして、機械にわかるよう(=解析)にしてくれているわけです。

application/x-www-form-urlencodedとは

さて、最後に今回送信しようとしているデータ送信形式のapplication/x-www-form-urlencodedについて、少し触れておきます。

一般的にデータ送信といえば、JSONという気味の悪い拡張子とかXMLがほとんどなわけですが、今回は珍しくこの形式をクライアントに指定されました。

調べてみると、フォームの提出のためのデータ送信形式のひとつで、OAuthやWebAPIではよく使われるそうです。

ドキュメントを色々と探してみたんですが、味わい深いデザインのサイトが多かったです。
結構レガシーなやり方なんですかね?

通信用語の基礎知識 『application/x-www-form-urlencoded』
https://www.wdic.org/w/WDIC/application/x-www-form-urlencoded)

application/x-www-form-urlencoded (MIME型)
https://wiki.suikawiki.org/n/application%2Fx-www-form-urlencoded)

また、調べていくと納得したのですが、文字列がASCIIによって表現されていて、keyとvalueはパーセント符号化することになっているみたい。
それで、送りたいデータをurllib.parse.urlencodeするんですね。

最後に全体の流れを整理

前後関係を無視してダラダラと説明してしまったので、最後にコードの全体の流れをさらっと整理しておきましょう。

コード

import requests
import urllib

url = 'xxx.co.jp' 

params = {
'UserName': 'test',,
'UserNameKana': 'テスト',
'MailAddress': '[email protected]',
'LoginPassword': 'password',
}

params = urllib.parse.urlencode(params)
headers = {'Content-Type': 'application/x-www-form-urlencoded'}

r = requests.post(url=url, data=params, headers=headers, auth=('funfun', 'funfun'))

print(r.status_code)
print(r.text)

全体の流れ

  1. POSTするためのRequestsと、データをparseするためのurllibをimportする
  2. POSTするurlを変数に格納
  3. 変数に送信したいデータを格納
  4. urllib.parse.urlencodeで送信する用にデータを成形
  5. headersに送信したいデータ形式を指定
  6. auth引数にユーザー名とpasswordを指定し、POST
  7. (確認用に)ステータスコードとレスポンスを表示させる

以上です!

長々と書いてしまいましたが、ここまでお付き合いいいただいた方、ありがとうございました!
基本的なモジュールやメソッドについて掘り下げていきましたが、簡単だと侮らず、しっかりと理解した上でコードを書いていきましょう!(自分への戒め)