Python-电子邮件头解码UTF-8

Python-电子邮件头解码UTF-8,python,email,email-headers,Python,Email,Email Headers,有没有Python模块可以帮助将各种形式的编码邮件头(主要是主题)解码为简单的UTF-8字符串 以下是我拥有的邮件文件的主题标题示例: Subject: [ 201105311136 ]=?UTF-8?B?IMKnIDE2NSBBYnM=?=. 1 AO; Subject: [ 201105161048 ] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?= Subject: [ 201105191633 ] =?UTF-8?B?IE

有没有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?=
文本-编码的sting-文本

文本编码字符串

文本编码字符串-编码字符串

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 )
这个输出

调试:根:主题为[('[201101251025] ELStAM;=?UTF-8?B?IFZLCMBDVGD1BMGCGD9TIDIX?=.2011年1月,无)]

这并没有真正的帮助

更新2: 感谢Ingmar Hupp的评论


第一个示例解码为两个元组的列表:

打印解码头(“”[201105161048] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?=“”“)
[(“[201105161048]GewSt:”,无),(“Wegfall der Vorl\xc3\xa4ufigkeit”, ‘utf-8’)]

这总是[(string,encoding),(string,encoding),…]所以我需要一个循环将所有[0]项合并到一个字符串中,或者如何在一个字符串中获得所有项

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

解码不好:

打印解码头(“[201101251025]ELStAM;=?UTF-8?B?IFZLCMBDVGD1BMGCGD9TIDIX?=.2011年1月”)

[('[201101251025]ELStAM;=?UTF-8?B?IFZLCMBDVGD1BMGCGD9TIDIX?=.2011年1月,无)]

Python有一个电子邮件库。


查看email.header.decode_header()

这种编码称为,模块可以对其进行解码:

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
                                                                     ^
实际上是发送方的错误,这违反了标题中的编码字用空格分隔的要求,具体规定如下:出现在标题字段中定义为“*文本”的“编码字”必须用“线性空格”与任何相邻的“编码字”或“文本”分隔

如果需要,您可以通过使用正则表达式预处理这些损坏的头来解决此问题,该正则表达式在编码字部分后插入空格(除非它位于末尾),如下所示:

import re
header_value = re.sub(r"(=\?.*\?=)(?!$)", r"\1 ", 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.3中测试了编码头,我发现这是一种非常方便的处理方法:

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)))
>>> 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?='

我也有类似的问题,但我的情况有点不同:

  • Python 3.5(问题来自2011年,但在google上仍然很高)
  • 以字节字符串形式直接从文件中读取消息
现在,Python3 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编码的单词后面应该有一行空格),如中所述。所以我的答案是受他的启发

解决方案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)
。因为“为所有人服务”会大大降低解析速度(解析大约15万封邮件)

decode_头
功能应用于
fixed_头
后,我们在
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转义序列,我们在构建新的头值时设置
default\u charset='unicode escape'

正确的标题值现在是:

Re: 「コア会議」 (1/9(金 ) 6:00pm-7:00pm)  のお知らせ'
我希望这能节省一些时间


另外:没有真正帮助我,因为我无法从message类中获取头字段的原始值。

这个脚本对我来说很好。。我使用此脚本解码所有电子邮件主题

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?=

对我来说,这非常有效(并且总是给我一个线索):


第一个示例解码为两个元组的列表:
>>打印解码头(“[201105161048]GewSt:=?UTF-8?B?ifdlz2zhbgwgzggvyifzvcmzdphvmawdrzwl0?”)[(“[201105161048]GewSt:”,None),('Wegfall der Vorl\xc3\xa4ufigkeit','UTF-8')]
始终是[(字符串,编码),(字符串,编码),]所以我需要一个循环来连接所有[0]将项目转换为一个字符串或如何将其全部转换为一个字符串?是的,但您需要考虑到它们正在(或可以)使用不同的编码,因此需要进行一些转换。用例子更新了答案。谢谢你的回答。D-encoded单词的错误编码非常奇怪,因为邮件头是由perl MIME::Lite创建的=
dmsgsubject, dmsgsubjectencoding = email.header.decode_header(msg['Subject'])[0]
msgsubject = dmsgsubject.decode(*([dmsgsubjectencoding] if dmsgsubjectencoding else [])) if isinstance(dmsgsubject, bytes) else dmsgsubject
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)