ドコモAPIを使用した対話プログラムのエラー: POST data should be bytes or an iterable of bytes

投稿者: Anonymous

昨日質問させていただいた内容は解決したのですが、その後

POST data should be bytes or an iterable of bytes. It cannot be of type str.

というエラーコードに悩んでいます。
調べたところスタックトレースを見ると良いともあったのですが、できるように色々いじって見たのですが、他にエラーが増えてしまうだけでした。

プログラムとして現状

#!/usr/bin/env python
# -*- coding: utf-8 -*-

#Docomoの雑談対話APIを使ってチャットできるスクリプト

import sys
import urllib.request
import json
import os

APP_URL = 'https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue'

class DocomoChat(object):
    #Docomoの雑談対話APIでチャット

    def __init__(self, api_key):
        api_key = os.environ.get('DOCOMO_DIALOGUE_API_KEY', api_key)
        super(DocomoChat, self).__init__()
        self.api_url = APP_URL + ('?APIKEY=%s'%api_key)
        self.context, self.mode = None, None

    def __send_message(self, input_message='', custom_dict=None):
        req_data = {'utt': input_message}
        if self.context:
            req_data['context'] = self.context
        if self.mode:
            req_data['mode'] = self.mode
        if custom_dict:
            req_data.update(custom_dict)
        request = urllib.request.Request(self.api_url, json.dumps(req_data))
        request.add_header('Content-Type', 'application/json')
        try:
            response = urllib.request.urlopen(request)
        except Exception as e:
            print (e)
            sys.exit()
        return response

    def __process_response(self, response):
        resp_json = json.load(response)
        self.context = resp_json['context'].encode('utf-8')
        self.mode    = resp_json['mode'].encode('utf-8')
        return resp_json['utt'].encode('utf-8')

    def send_and_get(self, input_message):
        response = self.__send_message(input_message)
        received_message = self.__process_response(response)
        return received_message


    def set_name(self, name, yomi):
        response = self.__send_message(custom_dict={'nickname': name, 'nickname_y': yomi})
        received_message = self.__process_response(response)
        return received_message


def main():
    chat = DocomoChat('api_key')
    resp = chat.set_name('あなたのニックネーム', 'ニックネームのヨミガナ')
    print (('相手  : %s'% resp))
    message = ''
    while message != 'バイバイ':
        message = eval(input('あなた : '))
        resp = chat.send_and_get(message)
        print (('相手  : %s'%resp))

if __name__ == "__main__":
    main()

このような状態です。

質問ばかりの頼りきりで申し訳有りません。
宜しくお願いします。

解決

urllib.request.Requestdata 引数は bytes 型である必要があります。この引数に対して現状のコードでは json.dumps 関数の戻り値がそのまま渡されていますが、これは str 型です。今回は Content-Typeapplication/json なのでそのまま bytes 型に変換し

json.dumps(JSONを表すデータ).encode('何らかのエンコーディング')

という形で渡してあげれば良いです。 (補足:Content-Type が標準の application/x-www-form-urlencoded の場合、更に URL エンコードする必要があります。)

念の為、こちらで動作したコードを gist に上げておきました


参考

追記

他に気づいた点を簡単に書いておきます。

  • Python 2 の raw_input 関数に対応する Python 3 の関数は input です。
  • 一般に、任意の入力が考えられる場所で eval を使うのは危ないです。今回の場合、単に不要です。
  • Python 2 と Python 3 では Unicode 文字列の扱いが変わったため、 str.encode 関数の挙動が変わっています。Python 2 では Unicode 文字列と単なる文字列が区別されていたため、文字列から文字列へ変換するために encode 関数が使われていました。Python 3 では文字列型の区別が無くなり、更に bytes 型が追加されたため、encode 関数は str 型から bytes 型への変換に使われるようになりました。簡単なサンプルコードを下に示しておきます。

    $ python2
    >>> u'あいう'
    u'u3042u3044u3046'
    >>> u'あいう'.encode('utf-8')
    'xe3x81x82xe3x81x84xe3x81x86'
    >>> type(u'あいう')
    <type 'unicode'>
    >>> type(u'あいう'.encode('utf-8'))
    <type 'str'>
    >>> exit()
    
    $ python3
    >>> u'あいう'    # Python 3 では u が不要です
    'あいう'
    >>> 'あいう'.encode('utf-8')
    b'xe3x81x82xe3x81x84xe3x81x86'
    >>> type(u'あいう')
    <class 'str'>
    >>> type('あいう'.encode('utf-8'))
    <class 'bytes'>
    >>> exit()
    
回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *