Rainbow Planet (GT×IT×SP×SA)

IT技術を分かりやすく簡潔にまとめることによる学習の効率化、また日常の気付きを記録に残すことを目指します。

01_IT技術 (Technology)

PythonのPandas使用時に発生した「UnicodeEncodeError: ‘ascii’ codec can’t encode characters~」エラーの対処方法について

投稿日:

(0)目次&概説

(1) エラー対応1:UnicodeEncodeError
 (1-1) 発生状況・エラーメッセージ
  (1-1-1) エラーメッセージ
  (1-1-2) エラーとなったソース
 (1-2) 原因
  (1-2-1) 全体のフロー
  (1-2-2) 各エンコーディング箇所の考察
 (1-3) 対処方法
 (1-4) 補足事項

(1) エラー対応1:UnicodeEncodeError

(1-1) 発生状況・エラーメッセージ

Web上にあるcsvデータを”datapackage”パッケージを用いて変数にロードし、それをPandasの”read_csv”関数でDataframeに取り込み、最後にPandasの”to_sql”関数でDBにINSERTしようとした際に発生しました(EngineはSQLAlchemyの”create_engine”で作成)。

(1-1-1) エラーメッセージ

(例1)

UnicodeEncodeError: 'ascii' codec can't encode character '\xa3' in position 0: ordinal not in range(128)

(例2)

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
 
(1-1-2) エラーとなったソース
import datapackage
import sqlalchemy
import pandas as pd
import requests
from io import StringIO

def main():
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36'}
    url = 'https://[your URL].json'
    dp = datapackage.Package(url)
    res = dp.resources
    from sqlalchemy import create_engine
    engine = create_engine('oracle://[your schema]:[password]@[hostname]:[port]/[sid]')
    for res in res:
        if res.tabular:
            if res.descriptor['datahub']['type'] == 'derived/csv':
                s=requests.get(res.descriptor['path'], headers= headers).text
                df = pd.read_csv(StringIO(s))
                tmp_name = res.descriptor['name']
                tmp_name = tmp_name.replace('_unindexed','')
                tmp_name = tmp_name.replace('_csv','')
                tmp_name = tmp_name.replace('-','_')
                df.to_sql(con=engine, name=tmp_name.lower(), schema='TENNISDBUSR2', index=False, if_exists='replace')

if __name__ == '__main__':
    main()

(図101)

目次にもどる

(1-2) 原因

結論としては環境変数「NLS_LANG」のエンコーディング設定をしていなかったため、エラーが発生していました。

以降で上記の結論に至るまでの調査プロセスについても備忘として記載しておきます。

まず「UnicodeEncodeError: ‘ascii’ codec can’t encode characters~」のメッセージから「Unicode文字」をエンコード(文字列⇒バイト列への変換)時のエラーである事が読み取れます。ただし、エンコードといっても複数個所でエンコードがされており、どこの箇所のエンコードなのか?を特定していく必要があると考えます。

(1-2-1) 全体のフロー

まず、全体のフローとしてはおおよそ次の図のようになってきます。
(図201)

<全体のフロー>

①csvファイル(UTF-8)
 ↓
 ・Pandasの”read_csv”で”UTF-8″でdecodeされてDataframeに格納
 ↓
②Dataframe(Unicode含む文字列)
 ↓
 ・Pandasの”to_sql”を実行
 ↓
 ・SQLAlchemyのcreate_engineで作成したエンジンを利用してDB接続
 ↓
 ・内部的にDBAPIをコール(cx_Oracle)してDBに接続
 ↓
 ・環境変数”NLS_LANG”で指定した方式(AL32UTF8)でエンコードしてDBへ挿入
 ↓
③DBテーブル(UTF-8)

 

(1-2-2) 各エンコーディング箇所の考察

そしてエンコーディング処理が入るのは、主に次の3か所になります。

1)csv→(read_csv)→DataframeのDecode処理
read_csvはエンコーディングの指定がない場合はデフォルトで”UTF-8″を使うそうですが、今回のcsvファイルも”UTF-8″なので、この箇所は問題ありません。

2)create_engineでのencoding指定
次にエンコーディングの指定が可能な部分が「create_engine」の箇所ですが、ドキュメントを読む限り、ここで指定したエンコーディングはDBAPIでカバーしきれない部分のエンコード処理を行っているようです(バージョンが古い等の理由でエンコードできないケース。例えばPython2のUnicode文字列など)。ですので、基本はDBAPIのエンコーディングが優先されます。

3)NLS_LANGのエンコーディング設定
最後がOracleDBに接続するクライアント側の環境変数「NLS_LANG」によるエンコーディング方式の指定です。そして今回の「’UnicodeEncodeError: ‘ascii’ codec can’t encode character~」のエラーの原因はここの指定が漏れていたため発生しています。メッセージからも”ascii”コーデックと書かれているので、UTF-8系のエンコーディング方式を指定してあげれば、Unicode文字のエンコードが出来てエラーが解消すると推測できます。

目次にもどる

(1-3) 対処方法

クライアントからDBへのCRUD操作をする際のエンコーディング方式を指定する環境変数「NLS_LANG」の設定を次のコードで行います。Pythonの場合は「os」モジュールをインポートします。

・追加コード

import os
os.environ['NLS_LANG'] = ".AL32UTF8"

・追加した後の全文

import os
import datapackage
import sqlalchemy
import pandas as pd
import requests
from io import StringIO

def main():
    os.environ['NLS_LANG'] = ".AL32UTF8"
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36'}
    url = 'https://[your URL].json'
    dp = datapackage.Package(url)
    res = dp.resources
    from sqlalchemy import create_engine
    engine = create_engine('oracle://[your schema]:[password]@[hostname]:[port]/[sid]')
    for res in res:
        if res.tabular:
            if res.descriptor['datahub']['type'] == 'derived/csv':
                s=requests.get(res.descriptor['path'], headers= headers).text
                df = pd.read_csv(StringIO(s))
                tmp_name = res.descriptor['name']
                tmp_name = tmp_name.replace('_unindexed','')
                tmp_name = tmp_name.replace('_csv','')
                tmp_name = tmp_name.replace('-','_')
                df.to_sql(con=engine,name=tmp_name.lower(),schema='TENNISDBUSR2',index=False,if_exists='replace')

if __name__ == '__main__':
    main()


目次にもどる

(1-4) 補足事項

1)DBAPIとは?
PythonでリレーショナルDBを操作するためのAPI仕様のことで、PEP249にて記載されています。PEPというのは”Python Enhancement Proposal”の略で、Pythonの機能拡張に関する詳細設計書のようなドキュメントでPythonのコミュニティに提出されたりしています。

このDBAPIの仕様は異なるリレーショナルDB間で統一する事で、例えDBを操作するモジュール(Oracleなら”cx_Oracle”、SQLServerなら”pyodbc”、MySQLなら”PyMySQL”等)が異なっても、同じインターフェイスを利用できます。

例えば”Module Interface”に書かれたconnect()コンストラクタなど、DBの種類が異なっても似た要領で記述できます。

(例1) SQLServer
pyodbc.connect()で接続

driver='{SQL Server}'
server='[hostname]'
database='[database name]'
uid='[username]'
pwd='[password]'
cnxn=pyodbc.connect('DRIVER='+driver+';SERVER='+server+';DATABASE='+database+';UID='+uid+';PWD='+pwd+';')

(例2) Oracle
cx_Orale.connect()で接続

server='[hostname]'
port='[port]'
srvname='[sid]'
uid='[your shcema]'
pwd='[your password]'
dsn_tns = cx_Oracle.makedsn(server, port, service_name=srvname)
cnxn = cx_Oracle.connect(user=uid, password=pwd, dsn=dsn_tns)

 

2)Unicodeオブジェクトとは?
Unicodeとは世界各国の文字を扱える「文字集合」(正確には「符号化文字集合」)です。

Python2においては「u’….’」リテラルで囲まれたオブジェクトなどはUnicodeオブジェクトと呼ばれており、str型とUnicode型はencode/decodeメソッドを用いて下記のように相互変換を行っていました。
①Unicode ⇒ encode() ⇒ str
②str ⇒ decode() ⇒ Unicode

しかしPython3においてはUnicode文字は通常の「str型」の中で使えるようになりました。Python2との対比で見ると次のような形になります。

(表)

Python3 Python3型 Python3リテラル Python2で対応するもの(≒なもの) Python2型 Python2リテラル
文字列 str ‘xxxx’ Unicode文字列 unicode u’xxxx’
バイト列 bytes b’xxxx’ バイト文字列 str ‘xxxx’

<備考>
・Python2,3いずれも、文字列/Unicode文字列⇒バイト文字列/バイト列への変換はencode()を使う
・Python3ではstr型の中でUnicode文字を扱える
・Python2,3いずれも、バイト文字列/バイト列⇒文字列/Unicode文字列への変換はdecode()を使う
・Python3のバイト列は文字列としての連結操作等が不可(あくまで「バイト」の列であるため)

3)ロケールとは?

アプリケーションにおける地域や言語の設定の事です。Oracleデータベースでは「NLS_LANG」という名前の環境変数でクライアントのロケールを設定します。構造的には以下のようになっています。

(構文)
[ベース言語]_[使用地域].[コードセット]

(例)ドイツ語の「de」+スイス使用の「CH」+コードセット「UTF-8」の例
de_CH.UTF-8 

Adsense審査用広告コード


Adsense審査用広告コード


-01_IT技術 (Technology)

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

VBAによるマトリクスの行列を反転させて転記するプログラム

(0)目次&概説 (1) マクロ概要 (2) 使用方法 (3) アルゴリズム概要 (4) プログラム (1) マクロ概要 マトリクス形式で表現されたデータの行と列を入れ替えるプログラムです。 (※Ex …

CentOS6にVNCServerでリモート接続して日本語入力を可能にする方法

(0)目次&概説 (1) 記事の目的 (2) 日本語化の対応手順  (2-1) ibus系のインストール  (2-2) dbusインストール+エラー回避  (2-3) mocz系パッケージのインストー …

SQL Developer接続エラーへの対処(ORA-12514, ORA-12505)

SQL Developer接続エラーへの対処 SQL Developerを起動してSYSTEMユーザで接続する際に発生した、下記2つのエラーの対処法について記載します。 エラー#1:ORA-12514 …

Oracleデータベースのバックアップをexpdpで取得する方法

(0)目次&概説 (1) expdp/impdpについて  (1-1) expdp/impdpの概要  (1-2) expdp/impdpの配置場所や転送方法 (2) エクスポート手順(expdp) …

「サーブレットコンテナ」や「サーブレットのライフサイクル」とは?(サンプルプログラム付)

※本記事は「サーブレットとは?その役割やHelloWorldサンプルコードのご紹介」の続編です。 (0)目次&概説 (2) サーブレットコンテナの基本  (2-1) サーブレットコンテナとは?  (2 …

  • English (United States)
  • 日本語
Top