AIの「投稿しました」が文字化けしていた|Windowsのbashで日本語が化ける原因と、投稿後に読み直す確認

AIの「投稿しました」が文字化けしていた|Windowsのbashで日本語が化ける原因と、投稿後に読み直す確認

結論:Windowsのbashで、Pythonが出した日本語を $(…) で受け取り、別の道具に渡すと、環境によっては文字化けします。道具は「成功」を返すので気づけません。本文は道具の中でファイルから直接読み、投稿したら読み直して元の文と比べるまでを1つの作業にします。

うちのAI部下(Claude)が、YouTubeのショート動画に固定コメントを投稿しました。道具の返事は「コメント投稿」の成功。部下の報告も「投稿しました」。そのあと、社長から「ショートのコメント、文字化けしてました」と言われました。

この記事では、化けた原因の再現と、うちで決めた直し方を置いておきます。

目次

何が起きたか:道具は成功、コメントは読めない

投稿したコメントは、こんなふうに一文字も読めない状態でした。

文字化けした固定コメントの再現。道具の返事は成功
文字化けした固定コメントの再現。道具の返事は成功

不思議なのは、同じ動画のタイトルと説明文は無事だったことです。どちらも同じメモのファイルから、同じ日本語を取り出していました。

原因:コメントだけ、bashを一度通していた

違いは本文の渡し方でした。タイトルと説明文は、道具(Pythonのスクリプト)の中でメモのファイルを直接読んでいました。コメントだけは、bashの $(python …) でメモから本文を取り出し、それを道具の引数として渡していました。

確かめた所・確かめていない所の表。タイトルと説明文は直接読んで一致を確認、固定コメントだけ窓口を通して確かめていない
確かめた所・確かめていない所の表。タイトルと説明文は直接読んで一致を確認、固定コメントだけ窓口を通して確かめていない

うちの環境(WindowsのGit BashとPython)で確かめると、Pythonが画面ではなく別のプログラムへ文字を出すとき、何も指定しなければWindowsの古い文字の形式(cp932)で出ていました。そのあと受け渡しのどこかで、文字の形式の解釈が食い違い、日本語が化けました。挙動は、使うシェルやPythonの版、設定で変わります。同じことを手元で再現すると、こうなりました。

Windowsのbashで、Pythonの出力をそのまま渡すと化け、出す側をUTF-8にすると読める。再現の実行結果
Windowsのbashで、Pythonの出力をそのまま渡すと化け、出す側をUTF-8にすると読める。再現の実行結果

出す側に PYTHONIOENCODING=utf-8 を付けると、ちゃんと読めます。ただ、これは「付け忘れたら化ける」直し方です。うちは、もう一段手前で止めることにしました。

直し方1:本文はシェルを通さず、道具の中で直接読む

本文を引数で渡すのをやめて、道具の中でファイルから読みます。文字の形式が変わる場所を、そもそも通りません。

# 本文はメモ(JSON)から直接読む。bashの $(…) で渡さない
import io, json
meta = json.load(io.open("publish/meta/vol27.json", encoding="utf-8"))
text = meta["pinned_comment"]

直し方2:投稿したら、読み直して比べる

道具の「成功」は、投稿できたという意味です。読めるという意味ではありません。そこで、投稿したらYouTubeから読み直して、元の文と一致するかを比べるところまでを1つの作業にしました。

今の手順。投稿→YouTubeから読み直す→元の文と一字ずつ比べる→一致
今の手順。投稿→YouTubeから読み直す→元の文と一字ずつ比べる→一致
# 投稿 → 読み直し → 比べる(YouTube Data API v3・抜粋)
import time
r = youtube.commentThreads().insert(part="snippet", body={"snippet": {
    "videoId": video_id,
    "topLevelComment": {"snippet": {"textOriginal": text}}}}).execute()
time.sleep(5)  # 書き込み直後は古い値が返ることがあるので少し待つ
got = youtube.commentThreads().list(part="snippet", id=r["id"], textFormat="plainText").execute()
got = got["items"][0]["snippet"]["topLevelComment"]["snippet"]["textDisplay"]
print("一致" if got.strip() == text.strip() else "不一致: " + got)

5秒は、うちで普段使っている待ち時間です。書き込み直後は古い値が返ることがあるので、不一致が出たら少し待って読み直してから判断します。この形にしてから、固定コメントは毎回「一致」を見てから報告しています。

同じ夜のもう1件:「0件」も、数えた範囲の話だった

同じ夜、ブログの点検で「実画面(スクリーンショット)が入っていない記事」を数えたら、4本と出ました。ところが、そのうち1本には実画面が4枚入っていました。数えていたのは、ファイル名が決まった頭で始まる画像だけだったからです。作業に入る直前に記事の画像を全部並べて見て気づき、やり直しをせずに済みました。

コメントの「成功」も、記事の「0枚」も、道具が見ている範囲の中では正しい答えでした。外から読み直すと、範囲の外が見えます。

チェックリスト

  • 日本語の本文を、bashの $(…) や引数で渡していないか(渡すなら、出す側に PYTHONIOENCODING=utf-8)
  • 本文は、道具の中でファイルから直接読んでいるか
  • 投稿・保存のあと、相手側から読み直して元の文と比べているか
  • 「0件」「成功」が出たら、道具が何を見て数えたかを一度確かめたか

まとめ

  • Windowsのbashで、Pythonの出力を $(…) で受け取って渡すと、環境によっては日本語が化ける
  • 道具の「成功」は投稿できたという意味。読めるとは限らない
  • 本文は道具の中で直接読み、投稿したら読み直して比べるまでを1つの作業にする
  • 確かめた所は無事、確かめていない所だけが壊れる

AIの「確認しました」が当てにならない別の例は、AIの「測って確認しました」が当てにならないとき|平均に溶けて消える問題で書いています。今回の件は、うちのYouTubeでも【AI事件簿】として動画にしています。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

AIを「部下」として毎日の実務に使っている運営者です。ブログ・note・YouTubeの制作から、音楽や動画の制作、データの集計まで、ほとんどの作業をAIと分担しています。
AIに任せてうまくいったこと、間違えたことを、実際の画面と数字つきで記録しています。
運営者情報はこちら

目次