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

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

新規登録して質問してみよう
ただいま回答率
85.51%
メール

メールは、コンピュータネットワークを利用し、 情報等を交換する手段のことです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

10070閲覧

pythonを使ってSMTPでメール送信時に、添付ファイル本文の日本語部分が読めません

striker

総合スコア7

メール

メールは、コンピュータネットワークを利用し、 情報等を交換する手段のことです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

3クリップ

投稿2019/03/31 03:59

編集2019/03/31 12:07

python3.6、 SMTPでメール送信

添付ファイル付(日本語部分を含む)のメールを送信時に、添付ファイルの日本語部分が読めない(文字化けではなくエンコードの値のまま)。

具体的には、
(1)添付ファイルのエンコードがUTF-8の時 -> 受信メールの添付ファイルの日本語部分が読めない
(2)添付ファイルのエンコードがiso-2022jpの時 -> 受信メールの添付ファイルの日本語部分は読める。
のように、(2)の場合は、正常に受信側で読み取れます。

ただ、システム上、元の添付ファイルがUTF-8になってしまっています。
なので、codecsで元の添付ファイルをiso-2022jpに変換しようとすると、下記のエラーになります。
「UnicodeEncodeError: 'iso2022_jp' codec can't encode character '\uff8c' in positi on 98: illegal multibyte sequence」

そもそも、UTF-8の添付ファイルに日本語が含まれてる場合に、正常に(日本語部分が読み取れる状態で)送信できないのでしょうか。

該当のソースコード

python

1parent = MIMEMultipart() 2body = MIMEText(text, 'plain', 'utf-8') 3parent.attach(body) 4 5 6attachment = MIMEBase(type, subtype, encoders='utf-8') 7file = open(path, 'rt', encoding='utf-8') 8attachment.set_payload(file.read()) 9file.close() 10encoders.encode_base64(attachment) 11parent.attach(attachment) 12attachment.add_header("Content-Disposition","attachment", filename=filename) 13 14 15parent['Subject'] = Header(subject, charset) 16parent['From'] = from_address 17parent['To'] = ",".join(to_address) 18parent['Date'] = formatdate(localtime=True) 19 20smtp = smtplib.SMTP('xxx.xxx.xxx.xxx', 25) 21smtp.sendmail(from_address, to_address + bcc_addrs, parent.as_string())

試したこと

ここに問題に対して試したことを記載してください。

補足情報(FW/ツールのバージョンなど)

読める場合
読めない場合

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

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

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

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

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

y_waiwai

2019/03/31 04:28

送信した添付ファイルと受信した添付ファイルを比較したときに同一なんでしょうか 添付ファイルってのは読める読めない以前に、ナカミが変わってしまうとまずいですが。
striker

2019/03/31 04:37

早速返信頂き、ありがとうございます。 はい、送信時のものと受信側のもので、ファイルは同一です。
y_waiwai

2019/03/31 04:38

なら、その「読む」ところのコードを提示しましょう
striker

2019/03/31 04:53

失礼致しました。 下記になります。 parent = MIMEMultipart() body = MIMEText(text, 'plain', 'utf-8') attachment = MIMEBase(type, subtype, encoders='utf-8') file = open(path, 'rt', encoding='utf-8') attachment.set_payload(file.read()) file.close() encoders.encode_base64(attachment) parent.attach(attachment) attachment.add_header("Content-Disposition","attachment", filename=filename) smtp = smtplib.SMTP('xxx.xxx.xxx.xxx', 25) smtp.sendmail(from_address, to_address + bcc_addrs, parent.as_string())
ikedas

2019/03/31 07:42

質問文に追記してください (質問文は編集できます)。あと、エラーメッセージの全文を提示してください。
striker

2019/03/31 10:45

ソースを質問文に追記しました。 エラーは起きず、受信側で添付ファイルを開くと日本語部分が読めないのが問題です。(\u307e\u3059~のようにエンコードされたまま)
ikedas

2019/03/31 11:39

「読める」場合と「読めない」場合にどう表示されるのかを示してもらえますか。スクリーンショットをとって質問文に貼ってもらえればいいです。
ikedas

2019/03/31 12:01

原因わかったけど、これから夜間作業なので、明日の晩見てもまだ回答されてなければ回答する。キーワード: バイナリとテキスト、コデック、半角片仮名
striker

2019/03/31 12:09

返信ありがとうございます。 読める場合と読めない場合のスクリーンショットを追加しました。 お忙しいところ、ありがとうございます。 提示頂いたキーワードを元に、自分でも調べてみます。
otn

2019/03/31 13:02

y_waiwaiさん> なら、その「読む」ところのコードを提示しましょう > 失礼致しました。 > 下記になります のコードはどう見ても、受信したファイルを読むコードじゃなくて、ファイルをメールに添付して送信するコードですが? 送受信のファイルが一致していて、受信したファイルが読めないなら、 ・送信ファイルを作る時点で間違っていて、読めないファイルを作った ・受信ファイルを読む方法が間違っている のどちらかであることは自明だと思いますが。
striker

2019/03/31 14:23

説明が足らずにすいません。 スクリプトで行ってるのは送信処理だけで、受信は通常のメーラーで確認してます。
otn

2019/04/01 04:51

> はい、送信時のものと受信側のもので、ファイルは同一です。 はどうやって確認したのでしょう? 出来るだけ主観じゃなくて客観的事実を書いてください。
striker

2019/04/01 08:22

otnさん、返信遅くなってすいません。 正確には、下記になります。 ファイルの比較方法は、WinMergeで比較しました。 (1)読めない時:送信時の添付ファイルと、受信メールの添付ファイルは不一致(日本語部分のみ)。 (1)読める時:送信時の添付ファイルと、受信メールの添付ファイルは完全一致。
otn

2019/04/01 09:56 編集

ということは、前のコメントでの、 > はい、送信時のものと受信側のもので、ファイルは同一です。 は間違いと言う事ですね。これには振り回されました。大きな間違いです。
guest

回答1

0

ベストアンサー

まず、電子メールの内容は文字列ではありません。このことを簡単に説明します(分かっている人は「本題。」のところまで読み飛ばしてください)。

メールを読み書きする人々にとってはたしかにそうで、電子メールはそのほとんどが文字列 (テキストデータ) でできています。しかしプログラマにとっては違います。彼らにとって電子メールというのは、通信回線で伝送されたりサーバのディスクに保存されたりするバイト列です。

文字列は人が考えた概念であって、プログラムの中にしか存在しえないので、伝送したり保存したりするときにバイト列に符号化してやる必要があります。文字列を符号化するために用いる変換表がキャラクタセット (いわゆる「文字コード」) です (電子メールではほかに「伝送符号化」という符号化のやりかたも使いますが、ここでは説明を略します)。

Pythonでは (3.x以降では必ず)、文字列をstr型として、バイト列をbytes型として扱います。str型は文字列を符号化するencode()メソッドを、bytes型はバイト列を文字列に変換するdecode()メソッドを持っています。

プログラムで電子メールを作成するには、文字列やバイト列を元に、最終的にバイト列を作成する必要があります。ここまでの説明を前提に、ご質問に回答していきます。


本題。

python

1parent = MIMEMultipart() 2body = MIMEText(text, 'plain', 'utf-8') 3parent.attach(body) 4 5attachment = MIMEBase(type, subtype, encoders='utf-8') 6file = open(path, 'rt', encoding='utf-8') 7attachment.set_payload(file.read()) 8file.close() 9encoders.encode_base64(attachment) 10parent.attach(attachment) 11attachment.add_header("Content-Disposition","attachment", filename=filename)

bodyの文字列を本文に持ち、pathの場所に保存したファイルを添付したマルチパートのメッセージを作ろうというのですね。

MIMETextクラスは"text" MIME型を持つメッセージパートを表すクラスですから、本文の方はこれでいいです。いっぽうMIMEには個々のメッセージパートに「本文」とそれ以外の「添付」という区別はありません。だから添付のほうも同じくMIMETextクラスを使えばいいでしょう。

上で説明したように、元になるテキストデータは文字列なので、本文でも添付でもstr型のデータを使わなければなりません。open()でテキストモードを明示的に指定しているのは、バイト列であるファイルの内容を読み出して文字列に変換しているのですから、これで正しいですね。

しかし、encode_base64()を使って添付のペイロードをBASE64で伝送符号化しようとしています。この意図はいいのですが、BASE64はバイト列をバイト列に符号化する変換です。文字列 (str) を渡したので、これはうまく動かないでしょう。

実は、MIMETextクラスでcharsetパラメータを指定してインスタンスを作った場合、最終的なメッセージ全体の符号化の際に伝送符号化を適切に判断してやってくれます (と、emailパッケージのソースに書いてありました)。だからここで伝送符号化しなくていいでしょう。

以上のことを元に、この部分を書き直してみたのが以下です (ほかにもちょっと変えました)。

python

1from email.mime.multipart import MIMEMultipart 2from email.mime.multipart import MIMEText 3 4text = ''' 5本文…… 6''' 7path = '/path/to/attachment.txt' 8filename = '添付ファイル.txt' 9 10parent = MIMEMultipart() 11body = MIMEText(text, 'plain', 'utf-8') 12parent.attach(body) 13 14with open(path, 'rt', encoding='utf-8') as f: 15 content = f.read() 16attachment = MIMEText(content, 'plain', 'utf-8') 17attachment.add_header('Content-Disposition', 'attachment', filename=('utf-8', '', filename)) 18 19parent.attach(attachment)

これでうまくいくのではないでしょうか。わたしはちゃんと確認できていないので、確認してみてください。

[2019-04-03追記]

python

1smtp.sendmail(from_address, to_address + bcc_addrs, parent.as_string())

ここですが、「電子メールはバイト列である」という原則からするとas_string()じゃなくてas_bytes()じゃないの? と思うかもしれません。実際、as_bytes()もあるのでそっちを使ってもかまいません。両者はデフォルトでは同じ結果を出します (結果が文字列かバイト列かの違いだけで、どちらもASCIIの範囲の文字/バイトを使う)。が、as_string()はutf8ポリシが有効だと違う結果になります。


しかし、キャラクタセットをutf-8からiso-2022-jpに変えたところ、UnicodeEncodeError例外が発生してしまうということでした。[以下2019-04-03追記]

質問者さんはすでに解決したようですが、改めて書くと、現実には、実際のファイルの内容が想定したキャラクタセットで符号化できるものだとは限らないからです。今回の場合、半角片仮名が含まれています。

ISO-2022-JPの符号化をするコデックは本来、半角片仮名を符号化することができません。しかし、「半角片仮名も使えるようにしようぜ」と考えて独自の方法で符号化できるようにしたコデックの実装も存在してしまっているのが現実です。それらのコデックの符号化方式には互換性がないので、一旦文字列に変換してから符号化すると、元のファイルと違うバイト列になってしまうかもしれません。

MIMETextのインスタンスを作るときに文字列ではなくバイト列を与えると、コデックによる変換をせずにバイト列そのままを符号化されたメッセージに入れてくれます。この場合、open()でファイルを開くときはbフラグを指定してバイト列のままを読み出す必要がありますね。

回答終わり。

投稿2019/04/02 13:55

編集2019/04/03 15:09
ikedas

総合スコア4198

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

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

striker

2019/04/03 12:46

ikedasさん、返信遅くなってすいません。丁寧でわかりやすい解説、ありがとうございます! 僕もMMETextにしたらうまく送信できました。また、教えて頂いたようにset_payload()の引数をbyte型にしたら、MIMEBaseでもうまく送信できました。丸3日悩まされた原因がわかりました。 本当にありがとうございました。ソースは良ければ明日、投稿します。 iso-2022-jpに変えてUnicodeEncodeErrorになるのは、元ファイルのエンコードがiso-2022jpではないから、ではないですか...?
ikedas

2019/04/03 13:04

うーん、まあそういうことです。半角片仮名はISO-2022-JPでは認められていないので、UTF-8からISO-2022-JPに変換しようとしても失敗するのが正しいです。 でもISO-2022-JPなのに半角片仮名が入ってるメッセージってありますよね。その辺の対応も含めて書きたいと思っています。しばしお待ちください。
ikedas

2019/04/03 15:03

書きました。お待たせしてすみません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.51%

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

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

質問する

関連した質問