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] в одну строку или как получить все это в одной строке?
print decode_header ( «» [201101251025] ELStAM; =? UTF-8? B? IFZlcmbDvGd1bmcgdm9tIDIx? =. Januar 2011 «» «) [(‘[201101251025] ELStAM; =? UTF-8? B? IFZlcmbDvGd1bmcgdm9tIDIx? =. Januar 2011’, None)]
Я думаю, что make_header(decode_header(subject)) является самым простым решением. Смотрите документы для make_header (): docs.python.org/2/library/…
7 ответов
Этот тип кодирования известен как 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)
первый пример декодирует в список из двух наборов: >>> print decode_header(«»»[ 201105161048 ] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?=»»») [(‘[ 201105161048 ] GewSt:’, None), (‘ Wegfall der Vorl\xc3\xa4ufigkeit’, ‘utf-8’)] это всегда [[string, encoding), (string, encoding), . ], поэтому мне нужен цикл для объединения всех [0] элементы в одну строку или как получить все в одной строке?
Да, но вы должны принять во внимание, что они (или могут быть) используют разные кодировки, поэтому требуется немного преобразования. Обновленный ответ с примером.
Спасибо за Ваш ответ. Неправильная кодировка ofd encoded-word действительно странная, потому что заголовок письма был создан perl MIME :: Lite.
Обновление 2 будет прервано для такого субъекта =? Utf-8? Q? = E5 = 9B = 9E = E5 = A4 = 8D = EF = BC = 9A_ = E6 = 94 = B6 = E4 = BB = B6 = E7 = AE = = B1 = 5F03? =
Библиотека электронной почты Python для меня выглядит как библиотека низкого уровня. Немного похоже на ассемблер . Я хотел бы избежать этого, но не знаю лучшего решения 🙁 — не поймите меня неправильно: спасибо за то, что поделились своими знаниями, это помогло мне сделать вещи быстрее.
@guettli: В Python 3 str(make_header(decode_header(subject))) работает для всех примеров из вопроса (нет необходимости в re.sub , ».join ) (это 3 вызова вместо 1, но это не так плохой).
Я просто тестировал с закодированными заголовками в 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?='
Это правильный ответ, документация в email.header не проясняет это, но make_header(decode_header()) — это то, как правильно декодировать заголовки писем.
Похоже, что этот метод работает и для Python 2.7, но с использованием unicode() вместо str() для преобразования объекта заголовка обратно в строку (unicode).
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»
почему [0]? decode_header возвращает два аргумента, вы хотите поместить и в тему, и в кодировку соответственно, но [0] кажется, что вы хотите получить только первый аргумент
Этот 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?=
У меня была аналогичная проблема, но мой случай был немного другим:
- 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’ при создании нового значения заголовка.
Re: 「コア会議」 (1/9(金 ) 6:00pm-7:00pm) のお知らせ'
Надеюсь, это немного сэкономит кому-нибудь.
Дополнение: ответ Sander Steffann мне не помог, потому что я не смог получить исходную ценность поля заголовка из класс сообщений.