Ошибки кодирования почтового ящика Python

Во-первых, позвольте мне сказать, что я полный новичок в Python. Я никогда не учил язык, я просто подумал, «насколько это может быть сложно», когда Google не нашел ничего, кроме фрагментов Python, чтобы решить мою проблему. :)

У меня есть куча почтовых ящиков в формате Maildir (резервная копия с почтового сервера на моем старом веб-хосте), и мне нужно извлечь из них электронные письма. Пока что самый простой способ, который я нашел, - это преобразовать их в формат mbox, который поддерживает Thunderbird, и, похоже, в Python есть несколько классов для чтения / записи обоих форматов. Кажется идеальным.

В документах Python даже есть этот небольшой фрагмент кода, делающий именно то, что мне нужно:

src = mailbox.Maildir('maildir', factory=None)
dest = mailbox.mbox('/tmp/mbox')

for msg in src:   #1
    dest.add(msg) #2

Кроме не работает. И вот тут-то и проявляется мое полное незнание Python. В нескольких сообщениях я получаю UnicodeDecodeError во время итерации (то есть, когда он пытается прочитать msg из src в строке #1). В других случаях я получаю UnicodeEncodeError при попытке добавить msg в dest (строка #2).

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

Я получаю следы стека, подобные следующему:

 File "E:\Python30\lib\mailbox.py", line 102, in itervalues
    value = self[key]
  File "E:\Python30\lib\mailbox.py", line 74, in __getitem__
    return self.get_message(key)
  File "E:\Python30\lib\mailbox.py", line 317, in get_message
    msg = MaildirMessage(f)
  File "E:\Python30\lib\mailbox.py", line 1373, in __init__
    Message.__init__(self, message)
  File "E:\Python30\lib\mailbox.py", line 1345, in __init__
    self._become_message(email.message_from_file(message))
  File "E:\Python30\lib\email\__init__.py", line 46, in message_from_file
    return Parser(*args, **kws).parse(fp)
  File "E:\Python30\lib\email\parser.py", line 68, in parse
    data = fp.read(8192)
  File "E:\Python30\lib\io.py", line 1733, in read
    eof = not self._read_chunk()
  File "E:\Python30\lib\io.py", line 1562, in _read_chunk
    self._set_decoded_chars(self._decoder.decode(input_chunk, eof))
  File "E:\Python30\lib\io.py", line 1295, in decode
    output = self.decoder.decode(input, final=final)
  File "E:\Python30\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 37: character maps to <undefined>

И на UnicodeEncodeErrors:

  File "E:\Python30\lib\email\message.py", line 121, in __str__
    return self.as_string()
  File "E:\Python30\lib\email\message.py", line 136, in as_string
    g.flatten(self, unixfrom=unixfrom)
  File "E:\Python30\lib\email\generator.py", line 76, in flatten
    self._write(msg)
  File "E:\Python30\lib\email\generator.py", line 108, in _write
    self._write_headers(msg)
  File "E:\Python30\lib\email\generator.py", line 141, in _write_headers
    header_name=h, continuation_ws='\t')
  File "E:\Python30\lib\email\header.py", line 189, in __init__
    self.append(s, charset, errors)
  File "E:\Python30\lib\email\header.py", line 262, in append
    input_bytes = s.encode(input_charset, errors)
UnicodeEncodeError: 'ascii' codec can't encode character '\xe5' in position 16:
ordinal not in range(128)

Кто-нибудь может мне здесь помочь? (Очевидно, что также приветствуются предложения по совершенно другим решениям, не связанным с Python. Мне просто нужен способ получить доступ к импорту писем из этих файлов Maildir.

Обновления:

sys.getdefaultencoding возвращает "utf-8"

Я загрузил образцы сообщений, которые вызывают обе ошибки. Этот выбрасывает UnicodeEncodeError, а это выбрасывает UnicodeDecodeError

Я попытался запустить тот же скрипт в Python2.6 и получил вместо этого TypeErrors:

  File "c:\python26\lib\mailbox.py", line 529, in add
    self._toc[self._next_key] = self._append_message(message)
  File "c:\python26\lib\mailbox.py", line 665, in _append_message
    offsets = self._install_message(message)
  File "c:\python26\lib\mailbox.py", line 724, in _install_message
    self._dump_message(message, self._file, self._mangle_from_)
  File "c:\python26\lib\mailbox.py", line 220, in _dump_message
    raise TypeError('Invalid message type: %s' % type(message))
TypeError: Invalid message type: <type 'instance'>

1. Очевидно, вы используете Python 3.0, это правильно? 2. Можете ли вы вставить по одному образцу сообщения каждого из двух типов. 3. Можете ли вы вставить вывод sys.getdefaultencoding() (импортируйте sys перед тем, как это сделать, я никогда не работал с 3.0, Windows). Я могу воспроизвести вторую ошибку, а не первую.

JV. 03.01.2009 19:46

Я использую python 2.6.1, sys.getdefaultencoding () - ascii, и я не могу воспроизвести ни одну из ошибок (я читаю файл mbox, содержащий более 2000 сообщений и использую различные кодировки: iso -8859-1, utf-8, iso-8859-15 и us-ascii.)

Federico A. Ramponi 03.01.2009 20:01

Добавил запрошенную информацию к моему вопросу. :)

jalf 03.01.2009 21:03

@jalf: попробуйте ответ и дайте мне знать, работает ли он для вас

JV. 03.01.2009 22:39
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
6
4
3 316
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте использовать Python 2.5 или 2.6 вместо 3.0. 3.0 имеет совершенно другую обработку Unicode, и этот модуль, возможно, не был обновлен для 3.0.

Я согласен. Я не думаю, что 3.0 является хорошей версией для начала, особенно если вы полный новичок в Python.

Kamil Kisiel 03.01.2009 21:02

Камиль - полностью с тобой согласен. В производственном смысле я считаю Python 3k бета-версией еще как минимум пару лет.

Jimmy2Times 04.01.2009 04:08

не называйте это бета-версией. python 3.0 выпущен и стабилен. Другое дело, пользоваться им или нет.

user3850 04.01.2009 20:50

Что ж, сила Python - это его сообщество, а 3K ломает огромное количество кода. Большинство модулей и фреймворков на него не перенесено. Вот почему я думаю, что использование его в производство следует рассматривать как бета-версию на данный момент ... в целом, это полная функция, но не полностью протестированная.

Jimmy2Times 04.01.2009 23:19
Ответ принят как подходящий

Примечание

  1. @ Jimmy2Times вполне может сказать, что этот модуль не может быть обновлен до версии 3.0.

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

Я разместил все, что смог найти, как Редактировать ниже

=====

Я думаю вот что происходит

Среди многих других символов в ваших данных у вас есть два символа - \x9d и \xe5, и они закодированы в некотором формате кодирования, например, iso-8859-1.

когда Python 3.0 находит закодированную строку, он сначала пытается угадать кодировку строки, а затем декодирует ее в кодировку Unicode, используя предполагаемую кодировку (способ сохранения закодированных строк unicode - Ссылка на сайт).

Я думаю, что это часть догадки, где все идет не так.

Чтобы показать, что, скорее всего, происходит -

Допустим, кодировка была iso-8859-1, а ошибочное предположение было cp1252 (как и при первой трассировке).

Не удается декодировать \x9d.

In [290]: unicode(u'\x9d'.encode('iso-8859-1'), 'cp1252')
---------------------------------------------------------------------------
<type 'exceptions.UnicodeDecodeError'>    Traceback (most recent call last)

/home/jv/<ipython console> in <module>()

/usr/lib/python2.5/encodings/cp1252.py in decode(self, input, errors)
     13 
     14     def decode(self,input,errors='strict'):
---> 15         return codecs.charmap_decode(input,errors,decoding_table)
     16 
     17 class IncrementalEncoder(codecs.IncrementalEncoder):

<type 'exceptions.UnicodeDecodeError'>: 'charmap' codec can't decode byte 0x9d in position 0: character maps to <undefined>

Декодирование для \xe5 проходит, но затем, когда сообщение извлекается из Python где-то, оно пытается закодировать его в ascii, что терпит неудачу.

In [291]: unicode(u'\xe5'.encode('iso-8859-1'), 'cp1252').encode('ascii')
---------------------------------------------------------------------------
<type 'exceptions.UnicodeEncodeError'>    Traceback (most recent call last)

/home/jv/<ipython console> in <module>()

<type 'exceptions.UnicodeEncodeError'>: 'ascii' codec can't encode character u'\xe5' in position 0: ordinal not in range(128)

============

РЕДАКТИРОВАТЬ:

Обе ваши проблемы находятся в строке №2. Где он сначала декодируется в юникод, а затем кодируется в ascii

Сначала делаем easy_install chardet

Ошибка декодирования:

In [75]: decd=open('jalf_decode_err','r').read()

In [76]: chardet.detect(decd)
Out[76]: {'confidence': 0.98999999999999999, 'encoding': 'utf-8'}
##this is what is tried at the back - my guess :)
In [77]: unicode(decd, 'cp1252') 
---------------------------------------------------------------------------
<type 'exceptions.UnicodeDecodeError'>    Traceback (most recent call last)

/home/jv/<ipython console> in <module>()

/usr/lib/python2.5/encodings/cp1252.py in decode(self, input, errors)
     13 
     14     def decode(self,input,errors='strict'):
---> 15         return codecs.charmap_decode(input,errors,decoding_table)
     16 
     17 class IncrementalEncoder(codecs.IncrementalEncoder):

<type 'exceptions.UnicodeDecodeError'>: 'charmap' codec can't decode byte 0x9d in position 2812: character maps to <undefined>'

##this is a FIX- this way all your messages r accepted
In [78]: unicode(decd, chardet.detect(decd)['encoding']) 
Out[78]: u'Return-path: <[email protected]>\nEnvelope-to: [email protected]\nDelivery-date: Fri, 22 Aug 2008 16:49:53 -0400\nReceived: from [77.232.66.102] (helo=apps2.servage.net)\n\tby c1p.hostingzoom.com with esmtp (Exim 4.69)\n\t(envelope-from <[email protected]>)\n\tid 1KWdZu-0003VX-HP\n\tfor [email protected]; Fri, 22 Aug 2008 16:49:52 -0400\nReceived: from apps2.servage.net (apps2.servage.net [127.0.0.1])\n\tby apps2.servage.net (Postfix) with ESMTP id 4A87F980026\n\tfor <[email protected]>; Fri, 22 Aug 2008 21:49:46 +0100 (BST)\nReceived: (from root@localhost)\n\tby apps2.servage.net (8.13.8/8.13.8/Submit) id m7MKnkrB006225;\n\tFri, 22 Aug 2008 21:49:46 +0100\nDate: Fri, 22 Aug 2008 21:49:46 +0100\nMessage-Id: <[email protected]>\nTo: [email protected]\nSubject: =?UTF-8?B?WW5ncmVzYWdlbnMgTnloZWRzYnJldiAyMi44LjA4?=\nFrom: Nyhedsbrev fra Yngresagen <[email protected]>\nReply-To: [email protected]\nContent-type: text/plain; charset=UTF-8\nX-Abuse: Servage.net Listid 16329\nMime-Version: 1.0\nX-mailer: Servage Maillist System\nX-Spam-Status: No, score=0.1\nX-Spam-Score: 1\nX-Spam-Bar: /\nX-Spam-Flag: NO\nX-ClamAntiVirus-Scanner: This mail is clean\n\n\nK\xe6re medlem\n\nH\xe5ber du har en god sommer og er klar p\xe5 at l\xe6se seneste nyt i Yngresagen. God forn\xf8jelse!\n\n\n::. KOM TIL YS-CAF\xc8 .::\nFlere og billigere ungdomsboliger, afskaf 24-\xe5rs-reglen eller hvad synes du? Yngresagen indbyder dig til en \xe5ben debat over kaffe og snacks. Yngresagens Kristian Lauta, Mette Marb\xe6k, og formand Steffen M\xf8ller fort\xe6ller om tidligere projekter og vil gerne diskutere, hvad Yngresagen skal bruge sin tid p\xe5 fremover.  \nVil du diskutere et emne, du br\xe6nder for, eller vil du bare v\xe6re med p\xe5 en lytter?\nS\xe5 kom torsdag d. 28/8 kl. 17-19, Kulturhuset 44, 2200 KBH N \n \n::. VIND GAVEKORT & BLIV H\xd8RT .:: \nYngresagen har lavet et sp\xf8rgeskema, s\xe5 du har direkte mulighed for at sige din mening, og v\xe6re med til at forme Yngresagens arbejde. Brug 5 min. p\xe5 at dele dine holdninger om f.eks. uddannelse, arbejde og unges vilk\xe5r - og vind et gavekort til en musikbutik. Vi tr\xe6kker lod blandt alle svarene og finder tre heldige vindere. Sp\xf8rgeskemaet er her: www.yngresagen.dk\n\n::. YS SPARKER NORDJYLLAND I GANG .::\nNordjylland bliver Yngresagens sunde region. Her er regionsansvarlig Andreas M\xf8ller Stehr ved at starte tre projekter op: 1) L\xf8beklub, 2) F\xf8rstehj\xe6lpskursus, 3) Mad til unge-program.\nVi har brug for flere frivillige til at sparke projekterne i gang. Vi tilbyder gratis fede aktiviteter, gratis t-shirts og ture til K\xf8benhavn, hvor du kan m\xf8de andre unge i YS. Har det fanget din interesse, s\xe5 t\xf8v ikke med at kontakte os: [email protected] tlf. 21935185. \n\n::. YNGRESAGEN I PRESSEN .::\nL\xe6s her et udsnit af sidste nyt om Yngresagen i medierne. L\xe6s og lyt mere p\xe5 hjemmesiden under \u201dYS i pressen\u201d.\n\n:: Radionyhederne: Unge skal informeres bedre om l\xe5n \nUnge ved for lidt om at l\xe5ne penge. Det udnytter banker og rejseselskaber til at give dem l\xe5n med t\xe5rnh\xf8je renter. S\xe5dan lyder det fra formand Steffen M\xf8ller fra landsforeningen Yngresagen. \n\n:: Danmarks Radio P1: Dansk Folkeparti - de \xe6ldres parti? \nHvorfor er det kun fattige \xe6ldre og ikke alle fattige, der kan s\xf8ge om at f\xe5 nedsat medielicens?\nDansk Folkepartis ungeordf\xf8rer, Karin N\xf8dgaard, og Yngresagens formand Steffen M\xf8ller debatterer medielicens, \xe6ldrecheck og indflydelse til unge \n\n:: Frederiksborg Amts Avis: Turen til Roskilde koster en holdning!\nFor at skabe et m\xf8de mellem politikere og unge fragter Yngresagen unge gratis til \xe5rets Roskilde Festival. Det sker med den s\xe5kaldte Yngrebussen, der kan l\xe6ses mere om p\xe5 www.yngrebussen.dk\n\n \n \nMed venlig hilsen \nYngresagen\n\nLandsforeningen Yngresagen\nKulturhuset Kapelvej 44\n2200 K\xf8benhavn N\n\ntlf. 29644960\[email protected]\nwww.yngresagen.dk\n\n\n-------------------------------------------------------\nUnsubscribe Link: \nhttp://apps.corecluster.net/apps/ml/r.php?l=16329&e=public%40jalf.dk%0D%0A&id=40830383\n-------------------------------------------------------\n\n'

Теперь он в юникоде, поэтому у вас не должно возникнуть проблем.

Теперь проблема с кодированием: это проблема

In [129]: encd=open('jalf_encode_err','r').read()

In [130]: chardet.detect(encd)
Out[130]: {'confidence': 0.78187650822865284, 'encoding': 'ISO-8859-2'}

#even after the unicode conversion the encoding to ascii fails - because the criteris is strict by default
In [131]: unicode(encd, chardet.detect(encd)['encoding']).encode('ascii')
---------------------------------------------------------------------------
<type 'exceptions.UnicodeEncodeError'>    Traceback (most recent call last)

/home/jv/<ipython console> in <module>()

<type 'exceptions.UnicodeEncodeError'>: 'ascii' codec can't encode character u'\u0159' in position 557: ordinal not in range(128)'

##changing the criteria to ignore
In [132]: unicode(encd, chardet.detect(encd)['encoding']).encode('ascii', 'ignore')
Out[132]: 'Return-path: <[email protected]>\nEnvelope-to: [email protected]\nDelivery-date: Tue, 21 Aug 2007 06:10:08 -0400\nReceived: from pfepc.post.tele.dk ([195.41.46.237]:52065)\n\tby c1p.hostingzoom.com with esmtp (Exim 4.66)\n\t(envelope-from <[email protected]>)\n\tid 1INQgX-0003fI-Un\n\tfor [email protected]; Tue, 21 Aug 2007 06:10:08 -0400\nReceived: from local.com (ns2.datadan.dk [195.41.7.21])\n\tby pfepc.post.tele.dk (Postfix) with SMTP id ADF4C8A0086\n\tfor <[email protected]>; Tue, 21 Aug 2007 12:10:04 +0200 (CEST)\nFrom: "Kollegiernes Kontor I Kbenhavn" <[email protected]>\nTo: "Jesper Alf Dam" <[email protected]>\nSubject: Fornyelse af profil\nDate: Tue, 21 Aug 2007 12:10:03 +0200\nX-Mailer: Dundas Mailer Control 1.0\nMIME-Version: 1.0\nContent-Type: Multipart/Alternative;\n\tboundary = "Gark=_20078211010346yhSD0hUCo"\nMessage-Id: <[email protected]>\nX-Spam-Status: No, score=0.0\nX-Spam-Score: 0\nX-Spam-Bar: /\nX-Spam-Flag: NO\nX-ClamAntiVirus-Scanner: This mail is clean\n\n\n\n--Gark=_20078211010346yhSD0hUCo\nContent-Type: text/plain; charset=ISO-8859-1\nContent-Transfer-Encoding: Quoted-Printable\n\nHej Jesper Alf Dam=0D=0A=0D=0AHusk at forny din profil hos KKIK inden 28.=\n august 2007=0D=0ALog ind p=E5 din profil og benyt ikonet "forny".=0D=0A=0D=\n=0AVenlig hilsen=0D=0AKollegiernes Kontor i K=F8benhavn=0D=0A=0D=0Ahttp:/=\n/www.kollegierneskontor.dk/=0D=0A=0D=0A\n\n--Gark=_20078211010346yhSD0hUCo\nContent-Type: text/html; charset=ISO-8859-1\nContent-Transfer-Encoding: Quoted-Printable\n\n<html>=0D=0A<head>=0D=0A=0D=0A<style>=0D=0ABODY, TD {=0D=0Afont-family: v=\nerdana, arial, helvetica; font-size: 12px; color: #666666;=0D=0A}=0D=0A</=\nstyle>=0D=0A=0D=0A<title></title>=0D=0A=0D=0A</head>=0D=0A<body bgcolor=3D=\n#FFFFFF>=0D=0A<hr size=3D1 noshade>=0D=0A<table cellpadding=3D0 cellspaci=\nng=3D0 border=3D0 width=3D100%>=0D=0A<tr><td >=0D=0AHej Jesper Alf Dam<br=\n><br>Husk at forny din profil inden 28. august 2007<br>=0D=0ALog ind p=E5=\n din profil og benyt ikonet "forny".=0D=0A<br><br>=0D=0A<a href=3D"http:/=\n/www.kollegierneskontor.dk/">Klik her</a> for at logge ind.<br><br>Venlig=\n hilsen<br>Kollegiernes Kontor i K=F8benhavn=0D=0A</td></tr>=0D=0A</table=\n>=0D=0A<hr size=3D1 noshade>=0D=0A</body>=0D=0A</html>=0D=0A\n\n--Gark=_20078211010346yhSD0hUCo--\n\n'

In [133]: len(encd)
Out[133]: 2303

In [134]: len(unicode(encd, chardet.detect(encd)['encoding']).encode('ascii', 'ignore'))
Out[134]: 2302

ВНИМАНИЕ: как видите, эта процедура может привести к потере данных от незначительной до умеренной. Так что пользователю решать, использовать это или нет.

поэтому код должен выглядеть как

import chardet

for msg in src:
    msg=unicode(msg, chardet.detect(msg)['encoding']).encode('ascii', 'ignore')
    dest.add(msg)

Другие вопросы по теме