繁体   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