Перенаправление stdout python в файл завершается с ошибкой UnicodeEncodeError

У меня есть скрипт на Python, который подключается к Twitter Firehose и отправляет данные для обработки. Раньше он работал нормально, но теперь я пытаюсь получить только текстовое тело. (Это не вопрос о том, как я должен извлекать данные из Twitter или как кодировать/декодировать символы ascii). Поэтому, когда я запускаю свой скрипт напрямую, вот так:

python -u fetch_script.py

Он работает нормально, и я вижу, что сообщения приходят на экран. Например:

root@domU-xx-xx-xx-xx:/usr/local/streaming# python -u fetch_script.py 
Cuz I'm checking you out >on Facebook<
RT @SearchlightNV: #BarryLies???????? has crapped on all honest patriotic hard-working citizens in the USA but his abuse of WWII Vets is sick #2A…
"Why do men chase after women? Because they fear death."~Moonstruck
RT @SearchlightNV: #BarryLies???????? has crapped on all honest patriotic hard-working citizens in the USA but his abuse of WWII Vets is sick #2A…
Never let anyone tell you not to chase your dreams. My sister came home crying today, because someone told her she's not good enough.
"I can't even ask anyone out on a date because if it doesn't end up in a high speed chase, I get bored."
RT @ColIegeStudent: Double-checking the attendance policy while still in bed
Well I just handed my life savings to ya.. #trustingyou #abouttomakebankkkkk
Zillow $Z and Redfin useless to Wells Fargo Home Mortgage, $WFC, and FannieMae $FNM. Sale history LTV now 48%, $360 appraisal fee 4 no PMI.
The latest Dump and Chase Podcast http://somedomain.com/viaRSA9W3i check it out and subscribe on iTunes, or your favorite android app #Isles

но если я попытаюсь вывести их в файл следующим образом:

python -u fetch_script.py >fetch_output.txt

сразу выдает ошибку:

root@domU-xx-xx-xx-xx:/usr/local/streaming# python -u fetch_script.py >fetch_output.txt
ERROR:tornado.application:Uncaught exception, closing connection.
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/tornado/iostream.py", line 341, in wrapper
    callback(*args)
  File "/usr/local/lib/python2.7/dist-packages/tornado/stack_context.py", line 331, in wrapped
    raise_exc_info(exc)
  File "/usr/local/lib/python2.7/dist-packages/tornado/stack_context.py", line 302, in wrapped
    ret = fn(*args, **kwargs)
  File "/usr/local/streaming/twitter-stream.py", line 203, in parse_json
    self.parse_response(response)
  File "/usr/local/streaming/twitter-stream.py", line 226, in parse_response
    self._callback(response)
  File "fetch_script.py", line 57, in callback
    print msg['text']
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2026' in position 139: ordinal not in range(128)
ERROR:tornado.application:Exception in callback <functools.partial object at 0x187c2b8>
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/tornado/ioloop.py", line 458, in _run_callback
    callback()
  File "/usr/local/lib/python2.7/dist-packages/tornado/stack_context.py", line 331, in wrapped
    raise_exc_info(exc)
  File "/usr/local/lib/python2.7/dist-packages/tornado/stack_context.py", line 302, in wrapped
    ret = fn(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/tornado/iostream.py", line 341, in wrapper
    callback(*args)
  File "/usr/local/lib/python2.7/dist-packages/tornado/stack_context.py", line 331, in wrapped
    raise_exc_info(exc)
  File "/usr/local/lib/python2.7/dist-packages/tornado/stack_context.py", line 302, in wrapped
    ret = fn(*args, **kwargs)
  File "/usr/local/streaming/twitter-stream.py", line 203, in parse_json
    self.parse_response(response)
  File "/usr/local/streaming/twitter-stream.py", line 226, in parse_response
    self._callback(response)
  File "fetch_script.py", line 57, in callback
    print msg['text']
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2026' in position 139: ordinal not in range(128)

P.S

Еще немного контекста:

В функции callback происходит ошибка:

def callback(self, message):
        if message:
            msg = message
            msg_props = pika.BasicProperties()
            msg_props.content_type = 'application/text'
            msg_props.delivery_mode = 2
            #print self.count
            print msg['text']
            #self.count += 1
            ...

Однако Если я удалю ['text'] и буду жить только print msg, оба случая работают как часы.


person Vor    schedule 02.10.2013    source источник
comment
Та же проблема возникает и с простым скриптом: print u'\u2026', так что не беспокойтесь о добавлении контекста! Проблема в том, что python устанавливает кодировку вывода при записи в терминал, но не при записи в файл. Я не уверен, какая текущая лучшая практика для его исправления, и меня интересуют ответы.   -  person tdelaney    schedule 02.10.2013
comment
это хороший момент, нужно погуглить, но почему у меня нет проблем, когда я вставляю всю полезную нагрузку в файл??? как я объяснил в разделе P.S.   -  person Vor    schedule 02.10.2013
comment
это потому, что вы напечатали строковое представление dict. print {'text':u'\2026'} выводит {'text': u'\x826'}, то есть печатает ascii-представление экранированного символа Юникода.   -  person tdelaney    schedule 03.10.2013


Ответы (1)


Так как никто еще не вскочил, вот мой шанс. Python устанавливает кодировку stdout при записи в консоль, но не при записи в файл. Этот скрипт воспроизводит проблему:

import sys

msg = {'text':u'\2026'}
sys.stderr.write('default encoding: %s\n' % sys.stdout.encoding)
print msg['text']

при запуске выше показывает ошибку:

$ python bad.py>/tmp/xxx
default encoding: None
Traceback (most recent call last):
  File "fix.py", line 5, in <module>
    print msg['text']
UnicodeEncodeError: 'ascii' codec can't encode character u'\x82' in position 0: ordinal not in range(128)

Добавление кодировки к приведенному выше скрипту:

import sys

msg = {'text':u'\2026'}
sys.stderr.write('default encoding: %s\n' % sys.stdout.encoding)
encoding = sys.stdout.encoding or 'utf-8'
print msg['text'].encode(encoding)

и проблема решена:

$ python good.py >/tmp/xxx
default encoding: None
$ cat /tmp/xxx
6
person tdelaney    schedule 02.10.2013
comment
Человек ты РОК! большое спасибо! )) Я голову ломал как это сделать. - person Vor; 03.10.2013
comment
Это что-то нетривиальное :) Спасибо. Вы сэкономили мне много времени своим ответом - person Gábor Lipták; 04.06.2018
comment
Это было действительно полезно. - person M.E.; 11.01.2019
comment
Спасибо, это работает в другом контексте, где я использую цветовые коды ASCII. - person Yan King Yin; 05.09.2019