簡體   English   中英

Python - 電子郵件標頭解碼 UTF-8

[英]Python - email header decoding UTF-8

是否有任何 Python 模塊可以幫助將各種形式的編碼郵件標頭(主要是主題)解碼為簡單的 - 例如 - UTF-8 字符串?

以下是我擁有的郵件文件中的示例主題標頭:

Subject: [ 201105311136 ]=?UTF-8?B?IMKnIDE2NSBBYnM=?=. 1 AO;
Subject: [ 201105161048 ] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?=
Subject: [ 201105191633 ]
  =?UTF-8?B?IERyZWltb25hdHNmcmlzdCBmw7xyIFZlcnBmbGVndW5nc21laHJhdWZ3ZW5kdW4=?=
  =?UTF-8?B?Z2VuIGVpbmVzIFNlZW1hbm5z?=

文本 - 編碼的刺痛 - 文本

文本 - 編碼字符串

文本 - 編碼字符串 - 編碼字符串

Encodig 也可以是 ISO 8859-15 之類的其他東西。

更新 1:我忘了提,我試過 email.header.decode_header

    for item in message.items():
    if item[0] == 'Subject':
            sub = email.header.decode_header(item[1])
            logging.debug( 'Subject is %s' %  sub )

這輸出

DEBUG:root:Subject 是 [('[ 201101251025 ] ELStAM;=?UTF-8?B?IFZlcmbDvGd1bmcgdm9tIDIx?=. Januar 2011', None)]

這並沒有真正的幫助。

更新 2:感謝 Ingmar Hupp 在評論中。

第一個示例解碼為兩個元組的列表:

打印 decode_header("""[201105161048] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?=""")
[('[ 201105161048 ] GewSt:', None), (' Wegfall der Vorl\\xc3\\xa4ufigkeit', 'utf-8')]

這總是 [(string, encoding),(string, encoding), ...] 所以我需要一個循環來將所有 [0] 項連接到一個字符串中,或​​者如何將它們全部放在一個字符串中?

主題:[ 201101251025 ] ELStAM;=?UTF-8?B?IFZlcmbDvGd1bmcgdm9tIDIx?=。 2011 年 1 月

解碼不好:

打印 decode_header("""[ 201101251025 ] ELStAM;=?UTF-8?B?IFZlcmbDvGd1bmcgdm9tIDIx?=.Januar 2011""")

[('[ 201101251025 ] ELStAM;=?UTF-8?B?IFZlcmbDvGd1bmcgdm9tIDIx?=.Januar 2011', None)]

這種類型的編碼稱為MIME 編碼字電子郵件模塊可以對其進行解碼:

from email.header import decode_header
print decode_header("""=?UTF-8?B?IERyZWltb25hdHNmcmlzdCBmw7xyIFZlcnBmbGVndW5nc21laHJhdWZ3ZW5kdW4=?=""")

這將輸出一個元組列表,包含解碼的字符串和使用的編碼。 這是因為該格式在單個標頭中支持不同的編碼。 要將它們合並為單個字符串,您需要將它們轉換為共享編碼,然后將其連接起來,這可以使用 Python 的 unicode 對象來完成:

from email.header import decode_header
dh = decode_header("""[ 201105161048 ] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?=""")
default_charset = 'ASCII'
print ''.join([ unicode(t[0], t[1] or default_charset) for t in dh ])

更新 2:

此主題行未解碼的問題:

Subject: [ 201101251025 ] ELStAM;=?UTF-8?B?IFZlcmbDvGd1bmcgdm9tIDIx?=. Januar 2011
                                                                     ^

實際上是發件人的錯誤,這違反了RFC 2047 第 5 節第 1 段中指定的標頭中由空格分隔的編碼字的要求:出現在定義為 ' 的標頭字段中的“編碼字” *text' 必須通過 'linear-white-space' 與任何相鄰的 'encoded-word' 或 'text' 分開。

如果需要,您可以通過使用正則表達式預處理這些損壞的標頭來解決此問題,該正則表達式在編碼字部分之后插入一個空格(除非它在末尾),如下所示:

import re
header_value = re.sub(r"(=\?.*\?=)(?!$)", r"\1 ", header_value)

我只是在 Python 3.3 中使用編碼頭進行測試,我發現這是處理它們的一種非常方便的方法:

>>> from email.header import Header, decode_header, make_header

>>> subject = '[ 201105161048 ] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?='
>>> h = make_header(decode_header(subject))
>>> str(h)
'[ 201105161048 ] GewSt:  Wegfall der Vorläufigkeit'

如您所見,它會自動在編碼的單詞周圍添加空格。

它在內部將編碼和 ASCII 標頭部分分開,正如您在重新編碼非 ASCII 部分時所看到的:

>>> h.encode()
'[ 201105161048 ] GewSt: =?utf-8?q?_Wegfall_der_Vorl=C3=A4ufigkeit?='

如果您想重新編碼整個標頭,您可以將標頭轉換為字符串,然后再轉換回標頭:

>>> h2 = Header(str(h))
>>> str(h2)
'[ 201105161048 ] GewSt:  Wegfall der Vorläufigkeit'
>>> h2.encode()
'=?utf-8?q?=5B_201105161048_=5D_GewSt=3A__Wegfall_der_Vorl=C3=A4ufigkeit?='
def decode_header(value):
    return ' '.join((item[0].decode(item[1] or 'utf-8').encode('utf-8') for item in email.header.decode_header(value)))

如何以以下方式解碼標頭:

import poplib, email

from email.header import decode_header, make_header

...

        subject, encoding = decode_header(message.get('subject'))[0]

        if encoding==None:
            print "\n%s (%s)\n"%(subject, encoding)
        else:
            print "\n%s (%s)\n"%(subject.decode(encoding), encoding)

這從電子郵件中獲取主題並使用指定的編碼對其進行解碼(如果編碼設置為無,則不進行解碼)。

為我工作的編碼設置為“無”、“utf-8”、“koi8-r”、“cp1251”、“windows-1251”

我有一個類似的問題,但我的情況有點不同:

  • Python 3.5(問題來自 2011 年,但在 google 上仍然很高)
  • 直接從文件中讀取消息作為字節字符串

現在 python 3 email.parser 的一個很酷的特性是所有的頭都自動解碼為 Unicode 字符串。 然而,這在處理錯誤的標題時會導致一些“不幸”。 因此,以下標題導致了問題:

Subject: Re: =?ISO-2022-JP?B?GyRCIVYlMyUiMnE1RCFXGyhC?=
 (1/9(=?ISO-2022-JP?B?GyRCNmIbKEI=?=) 6:00pm-7:00pm) 
 =?ISO-2022-JP?B?GyRCJE4kKkNOJGkkOxsoQg==?=

這導致了以下msg['subject']

Re: 「コア會議」 (1/9(=?ISO-2022-JP?B?GyRCNmIbKEI=?=) 6:00pm-7:00pm)  のお知らせ

好吧,問題是不符合 RFC 2047(在 MIME 編碼字之后應該有一個行空白),如Ingmar Hupp回答中所述。 所以我的回答受到了他的啟發。

解決方案 1:在實際解析電子郵件之前修復字節字符串。 這似乎是更好的解決方案,但是我正在努力在字節字符串上實現正則表達式替換。 所以我選擇了解決方案2:

解決方案 2:修復已解析和部分解碼的標頭值:

with open(file, 'rb') as fp:  # read as byte-string
    msg = email.message_from_binary_file(fp, policy=policy.default)
    subject_fixed = fix_wrong_encoded_words_header(msg['subject'])


def fix_wrong_encoded_words_header(header_value):
    fixed_header_value = re.sub(r"(=\?.*\?=)(?=\S)", r"\1 ", header_value)

    if fixed_header_value == header_value:  # nothing needed to fix
        return header_value
    else:
        dh = decode_header(fixed_header_value) 
        default_charset = 'unicode-escape'
        correct_header_value = ''.join([str(t[0], t[1] or default_charset) for t in dh])
        return correct_header_value

重要部分說明:

我修改了 Ingmar Hupp 的正則表達式以僅替換錯誤的 MIME 編碼詞: (=\\?.*\\?=)(?=\\S) Debuggex Demo 因為為所有人做會嚴重減慢解析速度(解析大約 150'000 封郵件)。

decode_header函數應用於fixed_header ,我們在dh有以下部分:

dh == [(b'Re: \\u300c\\u30b3\\u30a2\\u4f1a\\u8b70\\u300d (1/9(', None), 
       (b'\x1b$B6b\x1b(B', 'iso-2022-jp'), 
       (b' ) 6:00pm-7:00pm)  \\u306e\\u304a\\u77e5\\u3089\\u305b', None)]

為了重新解碼 unicode 轉義序列,我們在構建新的 header-value 時設置default_charset = 'unicode-escape'

correct_header_value現在是:

Re: 「コア會議」 (1/9(金 ) 6:00pm-7:00pm)  のお知らせ'

我希望這會為某人節省一些時間。

補充: Sander Steffann回答並沒有真正幫助我,因為我無法從消息類中獲取標頭字段的原始值。

這個腳本對我來說很好用..我用這個腳本來解碼所有的電子郵件主題

pat2=re.compile(r'(([^=]*)=\?([^\?]*)\?([BbQq])\?([^\?]*)\?=([^=]*))',re.IGNORECASE)

def decodev2(a):
    data=pat2.findall(a)
    line=[]
    if data:
            for g in data:
                    (raw,extra1,encoding,method,string,extra)=g
                    extra1=extra1.replace('\r','').replace('\n','').strip()
                    if len(extra1)>0:
                            line.append(extra1)
                    if method.lower()=='q':
                            string=quopri.decodestring(string)
                            string=string.replace("_"," ").strip()
                    if method.lower()=='b':
                            string=base64.b64decode(string)
                    line.append(string.decode(encoding,errors='ignore'))
                    extra=extra.replace('\r','').replace('\n','').strip()
                    if len(extra)>0:
                            line.append(extra)
            return "".join(line)
    else:
            return a

樣品:

=?iso-8859-1?q?una-al-dia_=2806/04/2017=29_Google_soluciona_102_vulnerabi?=
 =?iso-8859-1?q?lidades_en_Android?=

=?UTF-8?Q?Al=C3=A9grate?= : =?UTF-8?Q?=20La=20compra=20de=20tu=20vehi?= =?UTF-8?Q?culo=20en=20tan=20s=C3=B3lo=2024h?= =?UTF-8?Q?=2E=2E=2E=20=C2=A1Valoraci=C3=B3n=20=26?= =?UTF-8?Q?ago=20=C2=A0inmediato=21?=
from email.header import decode_header
mail = email.message_from_bytes(data[0][1])

subject_list = decode_header(mail['subject'])

sub_list = []
for subject in subject_list:
    if subject[1]:
        subject = (subject[0].decode(subject[1]))
    elif type(subject[0]) == bytes:
        subject = subject[0].decode('utf-8')
    else:
        subject = subject[0]
    sub_list.append(subject)

subject = ''.join(sub_list)
print('Subject:' + subject)

對我來說,這很完美(並且總是給我一個字符串):

dmsgsubject, dmsgsubjectencoding = email.header.decode_header(msg['Subject'])[0]
msgsubject = dmsgsubject.decode(*([dmsgsubjectencoding] if dmsgsubjectencoding else [])) if isinstance(dmsgsubject, bytes) else dmsgsubject

Python 有一個電子郵件庫。 http://docs.python.org/library/email.header.html

看看 email.header.decode_header()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM