Записывает текст Unicode в текстовый файл?

225

Я вытаскиваю данные из документа Google, обрабатываю их и записываю в файл (который в итоге вставлю на страницу Wordpress).

У него есть не-ASCII символы. Как я могу безопасно преобразовать их в символы, которые можно использовать в исходном коде HTML?

В настоящее время я конвертирую все в Unicode по пути, объединяю все это в строку Python, затем делаю:

import codecs
f = codecs.open('out.txt', mode="w", encoding="iso-8859-1")
f.write(all_html.encode("iso-8859-1", "replace"))

В последней строке есть ошибка кодирования:

UnicodeDecodeError: кодек «ascii» не может декодировать байт 0xa0 в позиции 12286: порядковый номер не в диапазоне (128)

Частичное решение:

Этот Python работает без ошибок:

row = [unicode(x.strip()) if x is not None else u'' for x in row]
all_html = row[0] + "<br/>" + row[1]
f = open('out.txt', 'w')
f.write(all_html.encode("utf-8"))

Но затем, если я открою текстовый файл, я вижу много символов, таких как:

Qur’an 

Может мне нужно написать что-то кроме текстового файла?

Саймон
источник
1
Программа, которую вы используете для ее открытия, неправильно интерпретирует текст UTF-8. Он должен иметь возможность открыть файл как UTF-8.
Томас К

Ответы:

322

Работайте исключительно с объектами Unicode в максимально возможной степени, декодируя объекты в объекты Unicode, когда вы впервые получаете их, и кодируйте их по мере необходимости на выходе.

Если ваша строка на самом деле является объектом Юникода, вам необходимо преобразовать его в объект строки в кодировке Юникод перед записью в файл:

foo = u'Δ, Й, ק, ‎ م, ๗, あ, 叶, 葉, and 말.'
f = open('test', 'w')
f.write(foo.encode('utf8'))
f.close()

Когда вы снова прочитаете этот файл, вы получите строку в кодировке Unicode, которую вы можете декодировать в объект Unicode:

f = file('test', 'r')
print f.read().decode('utf8')
quasistoic
источник
Спасибо. Это работает без ошибок, но затем, если я открываю текстовый файл, я вижу кучу странных символов :) Мне нужно скопировать и вставить текст на страницу Wordpress (не спрашивайте). Можно ли как-то распечатать символы, которые там есть? Я думаю, не в текстовый файл, верно, но, может быть, что-то еще?
Симон
1
Что вы используете, чтобы открыть текстовый файл? Я предполагаю, что вы работаете в Windows, и вы открываете его в Блокноте, который не слишком умён с кодировками. Что происходит, когда вы открываете его в Wordpad?
квазистокий
@quasistoic откуда берется метод файла ?
Омар Кузма Файт
Мне нужно было включить двоичный режим, то есть f = open ('test', 'wb'), как описано в stackoverflow.com/a/5513856/6580199 - в противном случае я получу "аргумент TypeError: write () должен быть str, не байты "
Бенджи
72

В Python 2.6+ вы можете использоватьio.open() это по умолчанию ( встроенныйopen() ) в Python 3:

import io

with io.open(filename, 'w', encoding=character_encoding) as file:
    file.write(unicode_text)

Это может быть более удобно, если вам нужно писать текст постепенно (вам не нужно звонить unicode_text.encode(character_encoding)несколько раз). В отличие от codecsмодуля, ioмодуль имеет правильную универсальную поддержку перевода строки.

JFS
источник
1
Чувак, я потратил так много времени, чтобы найти это! Спасибо!
Георгий Гобозов
2
Это работает и для Python 3 (очевидно, но все же стоит отметить).
Бегемот
37

Обработка строк Unicode уже стандартизирована в Python 3.

  1. символы уже хранятся в Unicode (32-бит) в памяти
  2. Вам нужно всего лишь открыть файл в utf-8
    (преобразование 32-битного Unicode в utf-8 переменной длины байта автоматически выполняется из памяти в файл.)

    out1 = "(嘉南大圳 ㄐㄧㄚ ㄋㄢˊ ㄉㄚˋ ㄗㄨㄣˋ )"
    fobj = open("t1.txt", "w", encoding="utf-8")
    fobj.write(out1)
    fobj.close()
    
Дэвид М Ли
источник
Но это не работает на Python 2, верно? (Я должен сказать, что в этом коде Python 3 он выглядит очень лаконичным и разумным)
Liwen Zhao
это не должно работать на Python 2. Мы остаемся на Python 3. 3 намного лучше.
Дэвид М Ли
18

Открываемый файл codecs.open- это файл, который принимает unicodeданные, кодирует их iso-8859-1и записывает в файл. Однако то, что вы пытаетесь написать, не так unicode; Вы берете unicodeи кодируете это в iso-8859-1 себе . Это то, что unicode.encodeделает метод, и результат кодирования строки Unicode - строка байтов ( strтип.)

Вы должны либо использовать normal open()и кодировать юникод самостоятельно, либо (как правило, лучшая идея) использовать, codecs.open()а не кодировать данные самостоятельно.

Томас Воутерс
источник
17

Предисловие: будет ли работать ваш зритель?

Убедитесь, что ваш просмотрщик / редактор / терминал (однако вы взаимодействуете с файлом в кодировке utf-8) может прочитать файл. Это часто проблема в Windows , например, Блокнот.

Записывает текст Unicode в текстовый файл?

В Python 2 используйте openиз ioмодуля (это то же самое, что встроенный openв Python 3):

import io

Как правило, рекомендуется использовать UTF-8для записи в файлы (нам даже не нужно беспокоиться о порядке следования байтов в utf-8).

encoding = 'utf-8'

utf-8 - это самая современная и универсальная кодировка, которая работает во всех веб-браузерах, большинстве текстовых редакторов (смотрите ваши настройки, если у вас есть проблемы) и большинстве терминалов / оболочек.

В Windows вы можете попробовать, utf-16leесли вы ограничены просмотром вывода в Блокноте (или другом ограниченном средстве просмотра).

encoding = 'utf-16le' # sorry, Windows users... :(

И просто откройте его с помощью менеджера контекста и запишите свои символы Юникода:

with io.open(filename, 'w', encoding=encoding) as f:
    f.write(unicode_object)

Пример использования множества символов Юникода

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

from __future__ import print_function
import io
from unicodedata import name, category
from curses.ascii import controlnames
from collections import Counter

try: # use these if Python 2
    unicode_chr, range = unichr, xrange
except NameError: # Python 3
    unicode_chr = chr

exclude_categories = set(('Co', 'Cn'))
counts = Counter()
control_names = dict(enumerate(controlnames))
with io.open('unidata', 'w', encoding='utf-8') as f:
    for x in range((2**8)**3): 
        try:
            char = unicode_chr(x)
        except ValueError:
            continue # can't map to unicode, try next x
        cat = category(char)
        counts.update((cat,))
        if cat in exclude_categories:
            continue # get rid of noise & greatly shorten result file
        try:
            uname = name(char)
        except ValueError: # probably control character, don't use actual
            uname = control_names.get(x, '')
            f.write(u'{0:>6x} {1}    {2}\n'.format(x, cat, uname))
        else:
            f.write(u'{0:>6x} {1}  {2}  {3}\n'.format(x, cat, char, uname))
# may as well describe the types we logged.
for cat, count in counts.items():
    print('{0} chars of category, {1}'.format(count, cat))

Это должно выполняться примерно в течение минуты, и вы можете просмотреть файл данных, и если ваша программа просмотра файлов может отображать Unicode, вы увидите это. Информацию о категориях можно найти здесь . Основываясь на подсчете, мы, вероятно, можем улучшить наши результаты, исключив категории Cn и Co, которые не имеют связанных с ними символов.

$ python uni.py

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

Я рекомендую lessдля Unix или Cygwin (не выводить / cat весь файл на ваш вывод):

$ less unidata

Например, будет отображаться аналогично следующим строкам, которые я сэмплировал с помощью Python 2 (Unicode 5.2):

     0 Cc NUL
    20 Zs     SPACE
    21 Po  !  EXCLAMATION MARK
    b6 So    PILCROW SIGN
    d0 Lu  Ð  LATIN CAPITAL LETTER ETH
   e59 Nd    THAI DIGIT NINE
  2887 So    BRAILLE PATTERN DOTS-1238
  bc13 Lo    HANGUL SYLLABLE MIH
  ffeb Sm    HALFWIDTH RIGHTWARDS ARROW

Мой Python 3.5 от Anaconda имеет Unicode 8.0, я бы предположил, что большинство 3-х было бы.

Аарон Холл
источник
3

Как напечатать символы Unicode в файл:

Сохраните это в файл: foo.py:

#!/usr/bin/python -tt
# -*- coding: utf-8 -*-
import codecs
import sys 
UTF8Writer = codecs.getwriter('utf8')
sys.stdout = UTF8Writer(sys.stdout)
print(u'e with obfuscation: é')

Запустите его и передайте вывод в файл:

python foo.py > tmp.txt

Откройте tmp.txt и загляните внутрь, вы увидите это:

el@apollo:~$ cat tmp.txt 
e with obfuscation: é

Таким образом, вы сохранили Unicode E с пометкой обфускации в файл.

Эрик Лещинский
источник
2
Я был очень взволнован этим ответом, но он выдает ошибку на моей машине. Когда я копирую / вставляю ваш код, я получаю сообщение об ошибке: «TypeError: должен быть str, а не байты»
Ричард Раст
1

Эта ошибка возникает, когда вы пытаетесь закодировать не-юникодную строку: она пытается декодировать ее, предполагая, что она в простом ASCII. Есть две возможности:

  1. Вы кодируете его в строку байтов, но поскольку вы использовали codecs.open, метод write ожидает объект Unicode. Таким образом, вы кодируете его, и он пытается снова его декодировать. Попробуйте: f.write(all_html)вместо.
  2. На самом деле all_html не является объектом Юникода. Когда вы это делаете .encode(...), он сначала пытается его расшифровать.
Томас К
источник
0

В случае написания на python3

>>> a = u'bats\u00E0'
>>> print a
batsà
>>> f = open("/tmp/test", "w")
>>> f.write(a)
>>> f.close()
>>> data = open("/tmp/test").read()
>>> data
'batsà'

В случае написания на python2:

>>> a = u'bats\u00E0'
>>> f = open("/tmp/test", "w")
>>> f.write(a)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe0' in position 4: ordinal not in range(128)

Чтобы избежать этой ошибки, вы должны закодировать ее в байты, используя кодеки "utf-8", например так:

>>> f.write(a.encode("utf-8"))
>>> f.close()

и декодировать данные при чтении с использованием кодеков "utf-8":

>>> data = open("/tmp/test").read()
>>> data.decode("utf-8")
u'bats\xe0'

А также, если вы попытаетесь выполнить печать для этой строки, она будет автоматически декодирована с использованием кодеков "utf-8", подобных этой

>>> print a
batsà
ashish14
источник