質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.36%
JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

文字コード

文字コードとは、文字や記号をコンピュータ上で使用するために用いられるバイト表現を指します。

Q&A

解決済

3回答

9137閲覧

pythonのjson.loadsで文字化けを防ぐ方法に関して

gyungyun545

総合スコア84

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

文字コード

文字コードとは、文字や記号をコンピュータ上で使用するために用いられるバイト表現を指します。

0グッド

2クリップ

投稿2019/04/24 01:42

編集2019/04/24 05:13

python3.6にてcgiを作製しております。
クライアント(javascript)からデータを受け取った後までは、日本語がそのまま表示されるのですが、json.loads()にてjsonを読み込ませたところから文字コードが文字化けしてしまいます。
utf-8やshift-jisへの変換を試してみましたがすべて、変換できないといったエラーが返されてしまいます。
どうしても、方法がわからなくて困ってます。
ご教授いただければ幸いです。

※ 下記にソースを簡単に再現しました。
#クライアント

html

1<html lang="ja"> 2 <head> 3 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 4 </head> 5 <body> 6 <form accept-charset="utf-8"> 7 : 8 :

javascript

1$.post("test.py", JSON.stringify({text:"ふぇふぇふぇ"}), (res) => { 2 : 3 : 4});

#サーバー(cgi)

python3

1#!/usr/bin/env python3.6 2# -*- coding: utf-8 -*- 3import json 4import cgi 5import cgitb 6import sys, io 7cgitb.enable() 8 9print('Content-type: text/html; charset=UTF-8\r\n') 10 11#sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') 12data = sys.stdin.read() 13print(data) 14params = json.loads(data) 15print(params) 16 17exit()

#出力結果
下記のようにdataまでは日本語ですが、json.loads後がバイトコードが表示されてしまいます。

output

1{"text":"ふぇふぇふぇ"} 2{'text':'\udce3\udc81\udcb5\udce3\udc81\udc87\udce3\udc81\udcb5\udce3\udc81\udc87\udce3\udc81\udcb5\udce3\udc81\udc87'}

#試したこと
下記一文を入れてutf-8に変換しようとしたら、unicodeEncodeErrorが返されました。

python3

1sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

error

1UnicodeEncodeError: 'utf-8' codec can't encode characters in position 9-26: surrogates not allowed 2 args =('utf-8', '{"text":"\udce3\udc81\udcb5\udce3\udc81\udc87\udce3\udc81\udcb5\udce3\udc81\udc87\udce3\udc81\udcb5\udce3\udc81\udc87"}', 9, 27, 'surrogates not allowed') 3 encoding ='utf-8' 4 end =27 5 object ='{"text":"\udce3\udc81\udcb5\udce3\udc81\udc87\udce3\udc81\udcb5\udce3\udc81\udc87\udce3\udc81\udcb5\udce3\udc81\udc87"}' 6 reason ='surrogates not allowed' 7 start =9 8 with_traceback =<built-in method with_traceback of UnicodeEncodeError object>

#補足20190424 13:21
環境面の補足です。
python3のデフォルトのencodingですが、環境で実行した場合とcgiから実行した場合で値がことなりました。サーバーでインタプリタで実行した際は「UTF-8」でしたが、
cgiから実行した場合は、stdin/stdoutともに、「ANSI_X3.4-1968」でした。

Bash

1$ locale 2LANG=ja_JP.UTF-8 3LC_CTYPE="ja_JP.UTF-8" 4LC_NUMERIC="ja_JP.UTF-8" 5LC_TIME="ja_JP.UTF-8" 6LC_COLLATE="ja_JP.UTF-8" 7LC_MONETARY="ja_JP.UTF-8" 8LC_MESSAGES="ja_JP.UTF-8" 9LC_PAPER="ja_JP.UTF-8" 10LC_NAME="ja_JP.UTF-8" 11LC_ADDRESS="ja_JP.UTF-8" 12LC_TELEPHONE="ja_JP.UTF-8" 13LC_MEASUREMENT="ja_JP.UTF-8" 14LC_IDENTIFICATION="ja_JP.UTF-8" 15LC_ALL=

#補足20190424 13:54
皆様のおかげでjsonファイルにunicodeを出力するところまで行けました。
unicodeであれば読み込むときに変換ができるので、なんとか行けそうです。
ただ、ファイルにコードではなくちゃんと日本語で表記されるようにするにはどうすればよいでしょうか?
dumpsでensure_ascii=Falseにしても、どうしてもunicodeで出力されてしまいます。

python3

1#!/usr/bin/env python3.6 2# -*- coding: utf-8 -*- 3import json 4import cgi 5import cgitb 6import sys, io 7cgitb.enable() 8 9sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') 10sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') 11sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') 12 13print('Content-type: text/html; charset=UTF-8\r\n') 14 15data = sys.stdin.buffer.read() 16print(data) 17params = json.loads(data) 18print(params) 19with open("sample", mode="w", encode="utf-8") as f: 20 json.dumps(params, f, indent=2, ensure_ascii=False) 21 22exit()

output

1{"text": "\u3042"} 2{"text": "あ"}

ファイルに{"text": "あ"}と出力したい。

sample

1{"text": "\u3042"}

#補足20190424 14:11
解決しました。
ソースを何度も修正する際にミスが有ったようです。
具体的には、同じコードを複数書いてexit()で実行されないようにしていたのですが、
ensure_ascii=Falseを追加する場所を間違えてました。
ずっとこの課題に取り組み、疲れが溜まっていたのが原因です。
本当に解決してよかったです。しばらく休みます。

下に最終的にうまく行ったコードを載せます。

python3

1#!/usr/bin/env python3.6 2# -*- coding: utf-8 -*- 3import json 4import cgi 5import cgitb 6import sys, io 7cgitb.enable() 8 9sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') 10sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') 11sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') 12 13print('Content-type: text/html; charset=UTF-8\r\n') 14 15data = sys.stdin.buffer.read() 16print(data) 17params = json.loads(data.decode()) 18print(params) 19with open("sample", mode="w", encoding="utf-8") as f: 20 json.dumps(params, f, indent=2, ensure_ascii=False) 21 22exit()

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

何が起こっている分からないので、何を確認するか、という話だけ。


print関数のデコードの影響を受けないように、print(data) のところを print(data.encode('unicode_escape')) として、文字列に実際にどんなコードポイントで格納されているかを確認する。

実行例

plain

1% echo -n 'ふ' | python -c 'import sys; print(sys.stdin.read().encode("unicode_escape"))' 2b'\u3075'

(注: 長さ6のバイト列です)


エンコード/デコードの影響を受けないように、

python

1data = sys.stdin.read() 2print(data)

python

1data = sys.stdin.buffer.read() 2print(data)

として、バイト列に実際にどんなデータが格納されているかを確認する。

実行例

plain

1% echo -n 'ふ' | python -c 'import sys; print(sys.stdin.buffer.read())' 2b'\xe3\x81\xb5'

(注: 長さ3のバイト列です)


どんな結果になるでしょうか?

エンコード/デコード関連のトラブルだと、print(data)して{"text":"ふぇふぇふぇ"}が出たからといって安心できなくて、"の間に不可視文字があるんじゃないの? などを疑ったりしないといけないので、コードポイントやバイナリデータを見るようにしましょう。

plain

1% python -c 'print("ふ")' 23% python -c 'print("\uFEFFふ")' 45(↑ゼロ幅空白\uFEFFが一見して区別できない) 6 7% python -c 'print("ふ".encode("unicode_escape"))' 8b'\u3075' 9% python -c 'print("\uFEFFふ".encode("unicode_escape"))' 10b'\ufeff\u3075'

KSwordOfHaste さんの回答の続きのような形になりますが、

plain

1% echo -n 'あ' | LANG=C python -c 'import sys; print(sys.stdin.read().encode("unicode_escape"))' 2b'\udce3\udc81\udc82' 3% LANG=C python -c 'import sys; print(sys.stdin.encoding, sys.stdout.encoding)' 4US-ASCII US-ASCII

LANG=C の設定下だとsys.stdinの方も US-ASCII encoding になる影響を受けるので、

python

1sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding="utf-8") 2sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")

とするのがいいようです。

plain

1% echo -n 'あ' | LANG=C python -c 'import io, sys; sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding="utf-8"); print(sys.stdin.read().encode("unicode_escape"))' 2b'\u3042'

投稿2019/04/24 02:30

編集2019/04/24 04:32
quickquip

総合スコア11231

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

gyungyun545

2019/04/24 02:51 編集

ご教授いただきありがとうございます。 わかりやすさのため、{text:"あ"}で試してみましたが下記のように出力されました。 b'{"text":"\xe3\x81\x82"}' 特に余計な文字コードが含まれているように見えません。 これをdecode()すればよいのかと思ったのですが、エラー?になります。 エラーの返却値は通常はhtml形式でエラー情報が含まれるのですが、中身が空っぽで理解できませんでした。 ```python3 data = sys.stdin.buffer.read() print(data) print(data.decode()) ``` <出力結果> b'{"text":"\xe3\x81\x82"}'<!--: spamContent-Type: text/html<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --><body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> --></font> </font> </font> </script> </object> </blockquote> </pre></table> </table> </table> </table> </table> </font> </font> </font>
guest

0

quiquiさんが本質的なことを既にコメントされているので蛇足かも知れませんが、再現できたっぽいのでコメントします。quiquiさん回答とダブってるところがありますがご容赦ください。

多分サーバー上でのロケール設定(LANGないしはLC_CTYPE)がCなどになっているのではないでしょうか?

cgi.py

python

1#!/usr/bin/python3 2#coding: utf-8 3import sys 4import json 5 6print('stdin:', sys.stdin.encoding) 7print('stdout:', sys.stdout.encoding) 8 9data = sys.stdin.read() 10 11s = ''.join(c for c in data if ord(c) > 0xff) # 日本語文字のみ残す 12c = ''.join(f' U+{ord(c):04x}' for c in s) # U+xxxx形式に変換 13# out = s.encode(sys.stdout.encoding) # LC_CTYPE=Cのとき例外が起きたので断念 14print('in python str: ', c) 15# print('encoded for output: ', out) # LC_CTYPE=Cのとき例外が起きたので断念 16print(data) 17 18prms = json.loads(data) 19print(prms)

(quiquiさんの回答にある`data.encode('unicode_escape')を知らなかったためエレガントでない方法でコードポイントを表示しています・・・)

bash

1$ locale 2LANG=ja_JP.UTF-8 3LC_CTYPE="ja_JP.UTF-8" 4LC_NUMERIC="ja_JP.UTF-8" 5LC_TIME="ja_JP.UTF-8" 6LC_COLLATE="ja_JP.UTF-8" 7LC_MONETARY="ja_JP.UTF-8" 8LC_MESSAGES="ja_JP.UTF-8" 9LC_ALL= 10 # 注: bashを動かしている端末もUTF8にしてます 11 12$ cat in 13{"text":"ふぇふぇふぇ"} 14 15$ ./cgi.py < in 16stdin: UTF-8 17stdout: UTF-8 18in python str: U+3075 U+3047 U+3075 U+3047 U+3075 U+3047 19{"text":"ふぇふぇふぇ"} 20 21{'text': 'ふぇふぇふぇ'} 22 23$ LC_CTYPE=C ./cgi.py < in 24stdin: ANSI_X3.4-1968 25stdout: ANSI_X3.4-1968 26in python str: U+dce3 U+dc81 U+dcb5 U+dce3 U+dc81 U+dc87 U+dce3 U+dc81 U+dcb5 U+dce3 U+dc81 U+dc87 U+dce3 U+dc81 U+dcb5 U+dce3 U+dc81 U+dc87 27{"text":"ふぇふぇふぇ"} 28 29{'text': '\udce3\udc81\udcb5\udce3\udc81\udc87\udce3\udc81\udcb5\udce3\udc81\udc87\udce3\udc81\udcb5\udce3\udc81\udc87'} 30 31$

LC_CTYPEがUTF-8となっている場合はstrの各文字は期待通りのコードポイント値('ふ'がU+3075、'ぇ'がU+3047)になってました。LC_CTYPEをCにするとこれがサロゲートペアにあたる範囲のコードポイントになってしまってます。要するにこれはPythonにおいてふぇふぇふぇを表す文字ではないです。

下記のようにdataまでは日本語ですが

そこの認識がちょっと不足していたのだと思います。上に示したように本当に期待通りの文字かどうかはそのまま表示しても確かなことはわからないと思います。Pythonのstrの各要素の文字のコードポイントを確認して初めて安心できると思います。(quiquiさん回答の通り)

自分の環境(Windows 10 cygwin64bit)ではLC_CTYPEをCにするとPythonインタープリタのI/OエンコーディングがANSI_X3.4-1968となっていました。検索してみつけたのはJavaの資料でしたが、それによればこれはUS_ASCIIとのことでした。普段そんなものを使わないので正確なエンコーディング規則がわかりませんでしたが、7bit(or 8bit?)の範囲で基本的なラテン文字しか含まないものだと思います。

PythonがそのエンコーディングでUTF-8の2バイト以上でエンコードされた文字(MSB=1になったバイト)を読むとどうやらサロゲートペア範囲のコードポイントとして読み込まれるようです。いずれにせよ化けてないように見えたのはこれを逆変換した際に元のUTF8のバイト列が再現されたため端末には期待通りに表示されてしまい、あたかも正しい日本語がPythonに読み込まれたかのように見えただけだと思います。

対処

PythonインタープリタのI/Oのエンコーディングをstdin, stdoutともにUTF-8にすれば期待通りになるのではないでしょうか?あるいはPythonを実行する環境のlocale設定を同じCロケールでもC.UTF-8にしておくとか。

投稿2019/04/24 03:53

編集2019/04/24 04:07
KSwordOfHaste

総合スコア18400

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

gyungyun545

2019/04/24 04:21

ご回答有り難うございます。 stdin, stdoutともにUTF-8にした場合、printでエラーになってしまいました。。。 これは、utf-8以外でもエラーになるようです。 ```python3 sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') ata = sys.stdin.read() print(data) params = json.loads(data) ``` ```error UnicodeEncodeError: 'utf-8' codec can't encode characters in position 9-11: surrogates not allowed args =('utf-8', '{"text":"\udce3\udc81\udc82"}', 9, 12, 'surrogates not allowed') encoding ='utf-8' end =12 object ='{"text":"\udce3\udc81\udc82"}' reason ='surrogates not allowed' start =9 with_traceback =<built-in method with_traceback of UnicodeEncodeError object> ``` ちなみに、printがない場合は、POSTエラーとなり、次の行のprintも表示されないので、おそらくjson.loads(data)の行でコケてるかと思います。 POST https://xxxxxxxx/test.py 500 (Internal Server Error)"
guest

0

json.loads

無視される非推奨の引数 encoding を除いて、その他の引数は load() のものと同じ意味です。

とあるように.loadsではencoding指定しても無意味です。よって以下回答は取り消します。

当方の非CGI環境では現象再現できませんでしたが、params = json.loads(data,encoding='utf-8')のようにloads時に文字コードutf-8指定してみてはいかがでしょうか?

投稿2019/04/24 02:38

編集2019/04/24 02:49
can110

総合スコア38341

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

can110

2019/04/24 02:45

ドキュメント確認しました。 loadsではloadとは異なりencodingは無視されるのですね。 ご指摘ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問