Пакет электронной почты Python: как надежно конвертировать / декодировать многочастные сообщения в str - PullRequest
0 голосов
/ 29 апреля 2018

Я пытался обрабатывать старые, потенциально несовместимые письма с Python. Я мог прочитать сообщение без проблем:

In [1]: m=email.message_from_binary_file(open('/path/to/problematic:2,S',mode='rb'))

Но впоследствии преобразование его в строку дало ошибку UnicodeEncodeError: кодек «gb2312» не может кодировать символ «\ ufffd» в позиции 1238: недопустимая многобайтовая последовательность. (Много) часть этого проблемного сообщения имеет "Content-Type: text / plain; charset =" gb2312 "и" Content-Transfer-Encoding: 8bit ".

In [2]: m.as_string()
---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
<ipython-input-26-919a3a20e7d8> in <module>()
----> 1 m.as_string()

~/tools/conda/envs/conda3.6/lib/python3.6/email/message.py in as_string(self, unixfrom, maxheaderlen, policy)
    156                       maxheaderlen=maxheaderlen,
    157                       policy=policy)
--> 158         g.flatten(self, unixfrom=unixfrom)
    159         return fp.getvalue()
    160

~/tools/conda/envs/conda3.6/lib/python3.6/email/generator.py in flatten(self, msg, unixfrom, linesep)
    114                     ufrom = 'From nobody ' + time.ctime(time.time())
    115                 self.write(ufrom + self._NL)
--> 116             self._write(msg)
    117         finally:
    118             self.policy = old_gen_policy

~/tools/conda/envs/conda3.6/lib/python3.6/email/generator.py in _write(self, msg)
    179             self._munge_cte = None
    180             self._fp = sfp = self._new_buffer()
--> 181             self._dispatch(msg)
    182         finally:
    183             self._fp = oldfp

~/tools/conda/envs/conda3.6/lib/python3.6/email/generator.py in _dispatch(self, msg)
    212             if meth is None:
    213                 meth = self._writeBody
--> 214         meth(msg)
    215
    216     #

~/tools/conda/envs/conda3.6/lib/python3.6/email/generator.py in _handle_multipart(self, msg)
    270             s = self._new_buffer()
    271             g = self.clone(s)
--> 272             g.flatten(part, unixfrom=False, linesep=self._NL)
    273             msgtexts.append(s.getvalue())
    274         # BAW: What about boundaries that are wrapped in double-quotes?

~/tools/conda/envs/conda3.6/lib/python3.6/email/generator.py in flatten(self, msg, unixfrom, linesep)
    114                     ufrom = 'From nobody ' + time.ctime(time.time())
    115                 self.write(ufrom + self._NL)
--> 116             self._write(msg)
    117         finally:
    118             self.policy = old_gen_policy

~/tools/conda/envs/conda3.6/lib/python3.6/email/generator.py in _write(self, msg)
    179             self._munge_cte = None
    180             self._fp = sfp = self._new_buffer()
--> 181             self._dispatch(msg)
    182         finally:
    183             self._fp = oldfp

~/tools/conda/envs/conda3.6/lib/python3.6/email/generator.py in _dispatch(self, msg)
    212             if meth is None:
    213                 meth = self._writeBody
--> 214         meth(msg)
    215
    216     #

~/tools/conda/envs/conda3.6/lib/python3.6/email/generator.py in _handle_text(self, msg)
    241                 msg = deepcopy(msg)
    242                 del msg['content-transfer-encoding']
--> 243                 msg.set_payload(payload, charset)
    244                 payload = msg.get_payload()
    245                 self._munge_cte = (msg['content-transfer-encoding'],

~/tools/conda/envs/conda3.6/lib/python3.6/email/message.py in set_payload(self, payload, charset)
    313             if not isinstance(charset, Charset):
    314                 charset = Charset(charset)
--> 315             payload = payload.encode(charset.output_charset)
    316         if hasattr(payload, 'decode'):
    317             self._payload = payload.decode('ascii', 'surrogateescape')

UnicodeEncodeError: 'gb2312' codec can't encode character '\ufffd' in position 1238: illegal multibyte sequence

Я не очень знаком с особенностями внутренних функций электронной почты, и при поиске в Интернете таких ошибок возникали в основном проблемы при поиске в Интернете, и в основном предлагалось несколько очевидное: необработанные байты, которые считываются, содержат символы Юникода, которые невозможно кодировать с помощью целевого кодека.

Мой вопрос: как правильно обрабатывать (потенциально не соответствующие) электронные письма?

EDIT

Интересно, что m.get_payload(i=0).as_string() вызовет то же исключение, но m.get_payload(i=0).get_payload(decode=False) дал str, который правильно отображался на моем терминале, в то время как m.get_payload(i=0).get_payload(decode=True) дал bytes (b'\xd7\xaa...'), который я могу ' т декодировать. Однако ошибка возникает для другого символа :

----> 1 m.get_payload(i=0).get_payload(decode=True).decode('gb2312')
UnicodeDecodeError: 'gb2312' codec can't decode byte 0xac in position 1995: illegal multibyte sequence

или

----> 1 m.get_payload(i=0).get_payload(decode=True).decode('gb18030')
UnicodeDecodeError: 'gb18030' codec can't decode byte 0xa3 in position 2033: illegal multibyte sequence

Ответы [ 2 ]

0 голосов
/ 01 мая 2018

Очевидно, что если Content-Transfer-Encoding равно 8bit, message.get_payload(decode=False) все равно будет пытаться декодировать его, чтобы восстановить исходные байты. С другой стороны, message.get_payload(decode=True) всегда производит bytes, хотя фактическое декодирование происходит, только если существует Content-Transfer-Encoding и quoted-printable или base64.

Я получил следующий код. Не уверен, что это правильный способ обработки электронной почты.

body = []
if m.preamble is not None:
    body.extend(m.preamble.splitlines(keepends=True))

for part in m.walk():
    if part.is_multipart():
        continue

    ctype = part.get_content_type()
    cte = part.get_params(header='Content-Transfer-Encoding')
    if (ctype is not None and not ctype.startswith('text')) or \
       (cte is not None and cte[0][0].lower() == '8bit'):
        part_body = part.get_payload(decode=False)
    else:
        charset = part.get_content_charset()
        if charset is None or len(charset) == 0:
            charsets = ['ascii', 'utf-8']
        else:
            charsets = [charset]

        part_body = part.get_payload(decode=True)
        for enc in charsets:
            try:
                part_body = part_body.decode(enc)
                break
            except UnicodeDecodeError as ex:
                continue
            except LookupError as ex:
                continue
        else:
            part_body = part.get_payload(decode=False)

    body.extend(part_body.splitlines(keepends=True))

if m.epilogue is not None:
    body.extend(m.epilogue.splitlines(keepends=True))
0 голосов
/ 29 апреля 2018

Короткий ответ обычно обработчики ошибок в ваших bytes.decode вызовах. Но детали зависят от многих вещей.

Во-первых, что вы пытаетесь сделать с данными? Часто вам нужно что-то абсолютно обратимое, поэтому вы можете гарантировать, что в худшем случае вы сможете заново сгенерировать то, что вы взяли, и в этом случае вы, вероятно, захотите surrogate-escape. В других случаях вы хотите сгенерировать что-то понятное человеку, и лучше просто пропустить невозможный моджибак, чем пытаться представить его, поэтому ignore может быть правильным ответом. И так далее.

Во-вторых, это тот случай, когда подавляющее большинство сообщений в порядке, но некоторые ошибочны, или многие сообщения в основном хороши, но содержат несколько ошибок?

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

...