Python3のスクリプトでjsonをPOSTする

ちょっとしたPython3スクリプトでjsonをPOSTしたいということがよくあると思います(適当)。

まずはサンプルコード。

```py3
import urllib.request, json

if __name__ == '__main__':
    url = "http://xxxx/xxxx" 
    method = "POST"
    headers = {"Content-Type" : "application/json"}

    # PythonオブジェクトをJSONに変換する
    obj = {"xxx" : "xxxx", 123 : 123} 
    json_data = json.dumps(obj).encode("utf-8")

    # httpリクエストを準備してPOST
    request = urllib.request.Request(url, data=json_data, method=method, headers=headers)
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode("utf-8")
```

次に簡単な実例として[「ニコニコ動画 『スナップショット検索API』」](search.nicovideo.jp/docs/api/snapshot.html)を利用して、「結月ゆかり」の検索結果のうち、再生数が多い30個の動画を取得してみたいと思います。

```py3
import urllib.request, json

if __name__ == '__main__':
    url = "http://api.search.nicovideo.jp/api/snapshot/"
    method = "POST"
    obj = {
        "query" : "結月ゆかり",
        "service" : ["video"],
        "search" : ["title", "description", "tags"],
        "join" : ["title", "view_counter"],
        "sort_by" : "view_counter",
        "order" : "desc",
        "size" : 30,
        "issuer" : "xxxxxxxxxxxxxxxxxxxxx"
    }
    json_data = json.dumps(obj).encode("utf-8")
    headers = {"Content-Type" : "application/json"}

    request = urllib.request.Request(url, data=json_data, headers=headers, method=method)
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode("utf-8")
        
        result_objs = json.loads(response_body.split('\n')[0])
        for result_obj in result_objs["values"]:
            print("{0:<10}{1}".format(result_obj["view_counter"], result_obj["title"]))

# 2903887   【初音ミク】こちら、幸福安心委員会です。【オリジナル】
# 2331059   みwなwぎwっwてwきwたwww（篠笛禁断症状L5）
# 1979283   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ#1
# 1896978   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ#5
# 1805589   【Minecraft】ゆかり「メイジタワー爆破しましょう」part1【VOICEROID+実況】
# 1662622   【結月ゆかり】名前のない怪物を歌ってもらった【PSYCHO-PASS】
# 1617931   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ#3
# 1587257   【初音ミク】永遠に幸せになる方法、見つけました。【オリジナル】
# 1531263   【結月ゆかり】幸せになれる隠しコマンドがあるらしい【オリジナル】
# 1499237   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ#2
# 1424024   【結月ゆかりの】チュルリラ・チュルリラ・ダッダッダ！
# 1391173   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ#7
# 1356847   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ#6
# 1354592   【Minecraft】ゆかり「メイジタワー爆破しましょう」part3【VOICEROID+実況】
# 1261669   【Minecraft】ゆかり「メイジタワー爆破しましょう」part2【VOICEROID+実況】
# 1241053   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ#8
# 1223469   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ#4
# 1201715   【Minecraft】ゆかり「メイジタワー爆破しましょう」part4【VOICEROID+実況】
# 1182955   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ#9
# 1177519   【Minecraft】ゆかり「メイジタワー爆破しましょう」part9【VOICEROID+実況】
# 1069852   【MAYU】一途な片思い、実らせたい小さな幸せ。【オリジナル】
# 1045793   【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ⑩
# 1040149   【Minecraft】ゆかり「メイジタワー爆破しましょう」part5【VOICEROID+実況】
# 1008825   【Minecraft】ゆかり「メイジタワー爆破しましょう」part6【VOICEROID+実況】
# 993675    【結月ゆかりの】サヨナラチェーンソー
# 963262    【Minecraft】ゆかり「メイジタワー爆破しましょう」part7【VOICEROID+実況】
# 927444    【Minecraft】ゆかり「メイジタワー爆破しましょう」part8【VOICEROID+実況】
# 895328    【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ⑫
# 892567    【Minecraft】ゆかり「メイジタワー爆破しましょう」最終回【VOICEROID+実況】
# 878046    【Minecraft】ゲリラクラフト 周囲に防衛線を築き地雷を張り巡らせるんだ⑪
```

いくつか注意点があるので、それも簡単に紹介しておきますね。

- POSTするデータ、つまり`urllib.request.Request()`の`data`に指定する引数は`bytes`型である必要があります。要するに`str`ではだめで、`encode()`|などを使って変換してやる必要があります。
    - レスポンスの結果を取得する`http.client.HTTPResponse#read()`の戻り値も`bytes`型なので、`str`に変換する場合は`decode()`などを利用します。
- `json.dupms()`はPythonオブジェクトをjson文字列(`str`)に変換してくれます。`json.dump()`という似たような名前のメソッドもありますが、これは別物です。
    - 同様に`json.loads()`と`json.load()`も別物です。
- `urllib.request.urlopen()`の戻り値は`http.client.HTTPResponse`です。