33510


OS-X SieraとPython3.6.0でGmailのメールを取り出すスクリプトを書いています。

文字コードがiso-2022-jpのメールはbodyをデコードできるのですが、UTF-8の場合、デコードに失敗します。

出力結果を見る限り、文字コードの取得は成功しているようなのですが、なぜUTF-8で失敗するのか、分かりません。

どなたか解決方法をご教示頂けないでしょうか。よろしくお願い申し上げます。

* * *

## 実行例

1 : テストメール1  
2017/03/23 15:25:28  
iso-2022-jp

1行目。  
2行目。

2 : テストメール2  
2017/03/23 17:44:33  
utf-8  
CjHooYznm67jgIIKMuihjOebruOAggo=

* * *

## ソース

[code]

    import imaplib
    import email
    import os
    import sys
    import dateutil.parser
    
    def main():
        gmail = imaplib.IMAP4_SSL("imap.gmail.com")
    
        try:
            gmail.login("xxxxxxx@gmail.com","*******")
            gmail.select('Notes')
            typ, [data] = gmail.search(None, "(ALL)")
    
            #取得したメール一覧の処理
            cnt = 0
    
            for num in data.split():
                cnt += 1
                ### 各メールへの処理 ###
                result, d = gmail.fetch(num, "(RFC822)")
                raw_email = d[0][1]
                msg = email.message_from_bytes(raw_email)
    
                #文字コード取得用
                msg = email.message_from_string(raw_email.decode('iso-2022-jp'))
                msg_encoding = email.header.decode_header(msg.get('Subject'))[0][1] or 'iso-2022-jp'
    
                #パースして解析準備
                msg = email.message_from_string(raw_email.decode(msg_encoding))
    
                date = dateutil.parser.parse(msg.get('Date')).strftime("%Y/%m/%d %H:%M:%S")
    
                subject = email.header.decode_header(msg.get('Subject'))
                title = ""
                for sub in subject:
                    if isinstance(sub[0], bytes):
                        title += sub[0].decode(msg_encoding)
                    else:
                        title += sub[0]
    
                body = ""
                if msg.is_multipart():
                    for payload in msg.get_payload():
                        if payload.get_content_type() == "text/plain":
                            body = payload.get_payload()
                else:
                    if msg.get_content_type() == "text/plain":
                        body = msg.get_payload()
    
                print(str(cnt) + " : " + title)
                print(date)
                print(msg_encoding)
                print(body)
                print('')
        finally:
            gmail.close()
            gmail.logout()
            return(cnt)
    
    ##メイン
    main()
    
[/code]

* * *

ご教示いただいた内容を元に、bodyの取得処理にBase64のデコード処理を追加したところ、UTF-8のメールを正常に取得できるようになりました。  
以下に修正後のソースを載せます（try句を外して整理しています）。

## 修正後のソース

[code]

    import imaplib
    import email
    import dateutil.parser
    import base64
    
    def main():
        gmail = imaplib.IMAP4_SSL("imap.gmail.com")
        gmail.login("xxxxxxx@gmail.com","xxxxxxxxxxx")
        gmail.select('Notes')
        typ, [data] = gmail.search(None, "(ALL)")
    
        #取得したメール一覧の処理
        for num in data.split():
            print(num)
    
            ### 各メールへの処理 ###
            result, d = gmail.fetch(num, "(RFC822)")
            raw_email = d[0][1]
    
            #文字コード取得用
            msg = email.message_from_string(raw_email.decode('iso-2022-jp'))
            msg_encoding = email.header.decode_header(msg.get('Subject'))[0][1] or 'iso-2022-jp'
    
            print(msg_encoding)
    
            date = dateutil.parser.parse(msg.get('Date')).strftime("%Y/%m/%d %H:%M:%S")
    
            print(date)
    
            subject = email.header.decode_header(msg.get('Subject'))
            title = ""
            for sub in subject:
                if isinstance(sub[0], bytes):
                    title += sub[0].decode(msg_encoding)
                else:
                    title += sub[0]
    
            print(title)
    
            body = ""
            if msg.is_multipart():
                for payload in msg.get_payload():
                    if payload.get_content_type() == "text/plain":
                        body = payload.get_payload()
                        if msg_encoding == 'utf-8':
                            body = base64.urlsafe_b64decode(body.encode('ASCII')).decode("utf-8")
            else:
                if msg.get_content_type() == "text/plain":
                    body = msg.get_payload()
                    if msg_encoding == 'utf-8':
                        body = base64.urlsafe_b64decode(body.encode('ASCII')).decode("utf-8")
            print(body)
            print('')
        gmail.close()
        gmail.logout()
        return(0)
    
    main()
    
[/code]

* * *

以下のコードでContent-Type内のcharsetを取得できました。

## 追加コード

[code]

    s = email.header.decode_header(msg.get('Content-Type'))[0]
    ss = repr(s[0]).split(';')
    if len(ss) > 1:
        c = ss[1].split('charset="')
        if len(c) > 1:
            code = c[1].split('"')
            if len(code) > 1:
                msg_encoding = code[0]
    
[/code]


CC BY-SA 3.0
2017-03-24T02:49:44.690
33506
2017-03-29T14:58:43.067
2017-03-29T14:58:43.067
21179
21179
question
python macos python3 gmail
Python3.6.0でGmailのUTF-8メールのデコードに失敗


下記にて本文文字列をタイトルで指定されたエンコード方式にてデコードしているようですが

[code]

    msg = email.message_from_string(raw_email.decode(msg_encoding))
    
[/code]

テストメール2の  
CjHooYznm67jgIIKMuihjOebruOAggo=  
はUTF-8文字列をBase64エンコードされているように見えますので、  
まずBase64デコードが必要です。

※蛇足：  
昔のメールサーバーは7bitコードしか処理できないという制約があり、  
7bitコードで扱えるように出てきたのがBase64であり、QuotedPrintableであり、ISO-2022-JPです。  
ISO-2022-JPは一見8bit使用の2byteコードに見えますが各オクテットは7bit範囲内であり、ASCII領域と日本語文字領域の被り部分はエスケープシーケンスを使うことにより切り替えを行います。  
base64は各byteを6bitづつに区切り、ASCII文字列として再生成する仕組みです。  
QPはASCII領域はそのまま、それ以外はASCII文字を使用し文字コードで表現する方法です。


CC BY-SA 3.0
2017-03-24T06:13:35.293
33510
2017-03-28T11:24:11.553
2017-03-28T11:24:11.553
19716
19716
33506
answer