Подтвердить что ты не робот

Python - декодирование заголовка электронной почты UTF-8

существует ли какой-либо модуль Python, который помогает декодировать различные формы закодированных заголовков почты, в основном Subject, простым словам - строки 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 )

Выводит

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

что действительно не помогает.

Обновление 2: Спасибо Ingmar Hupp в комментариях.

первый пример декодирует список из двух тэгелей:

  
    

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

    

это всегда [(string, encoding), (string, encoding),...], поэтому мне нужен цикл для конкатюции всех элементов [0] в одну строку или как получить все это в одной строке?

Тема: [201101251025] ELStAM; =? UTF-8? B? IFZlcmbDvGd1bmcgdm9tIDIx? =. Januar 2011

не хорошо декодируется:

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

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

4b9b3361

Ответ 1

Этот тип кодирования известен как MIME-кодированное слово и email модуль может декодировать его:

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

Здесь выводится список кортежей, содержащий декодированную строку и используемую кодировку. Это связано с тем, что формат поддерживает разные кодировки в одном заголовке. Чтобы объединить их в одну строку, вам необходимо преобразовать их в общую кодировку и затем объединить ее, что может быть выполнено с помощью юникодного объекта Python:

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 не расшифровывается:

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

На самом деле это ошибка отправителей, которая нарушает требование кодированных слов в заголовке, разделяемом пробелом, указанном в RFC 2047, раздел 5, пункт 1: "закодированное слово", которое появляется в поле заголовка, определяемом как "текст", ДОЛЖНО быть отделено от любого смежного "закодированного слова" или "текста" на "линейно-белое пространство".

Если это необходимо, вы можете обойти это, предварительно обработав эти коррумпированные заголовки с помощью регулярного выражения, которое вставляет пробел после части кодированного слова (если это не конец), например:

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

Ответ 2

Я просто тестировал с закодированными заголовками в 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?='

Ответ 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)))

Ответ 4

Как насчет заголовков декодирования следующим образом:

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"

Ответ 5

Этот script отлично работает для меня.. Я использую этот script для декодирования всех объектов электронной почты

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

Ответ 7

У меня была аналогичная проблема, но мой случай был немного другим:

  • Python 3.5 (вопрос с 2011 года, но все еще очень высокий в google)
  • Чтение сообщения непосредственно из файла в виде байтовой строки

Теперь классной особенностью python 3 email.parser является то, что все заголовки автоматически декодируются в Unicode-Strings. Однако это вызывает небольшое "несчастье" при работе с неправильными заголовками. Поэтому следующий заголовок вызвал проблему:

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: Исправить байт-строку перед фактическим анализом электронной почты. Это, казалось, лучшее решение, однако я изо всех сил пытался реализовать подстановку Regex на байтовые строки. Поэтому я выбрал решение 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) Demuggex 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, мы устанавливаем default_charset = 'unicode-escape' при создании нового значения заголовка.

Теперь correct_header_value:

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

Надеюсь, это немного сэкономит кому-нибудь.

Дополнение: ответ Sander Steffann мне не помог, потому что я не смог получить исходную ценность поля заголовка из класс сообщений.