Запис тексту 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, коли ви вперше їх отримуєте та кодуєте їх у міру необхідності на виході.

Якщо ваш рядок насправді є об'єктом 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')

Дякую. Це працює без помилок, але тоді, коли я відкрию текстовий файл, я бачу купу дивних символів :) Мені потрібно скопіювати та вставити текст на сторінку Wordpress (не питати). Чи є якийсь спосіб я насправді роздрукувати символи, які там є? Гадаю, не до файлу txt, правда, але, можливо, до чогось іншого?
симон

1
Що ви використовуєте для відкриття текстового файлу? Я здогадуюсь, що ви в Windows, і ви відкриваєте його в Блокноті, який не надто розумний з кодуваннями. Що відбувається, коли ви відкриєте його в Wordpad?
квазістоїк

@quasistoic звідки формується метод файлу ?
Omar Cusma Fait

Мені потрібно було ввімкнути бінарний режим, тобто 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модуль має належну універсальну підтримку нових рядків.


1
Людина, я витратив стільки часу, щоб знайти це! Дякую!
Георгій Гобозов

2
Це також працює для Python 3 (очевидно, але все ж варто зазначити).
Бегемот

37

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

  1. char вже зберігаються в 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 набагато краще.
david m lee

18

Файл, відкритий файлом, codecs.open- це файл, який приймає unicodeдані, кодує їх iso-8859-1і записує у файл. Однак те, що ви намагаєтесь написати, не є unicode; ти береш unicodeі кодуєш це в iso-8859-1 собі . Це те, що unicode.encodeробить метод, а результатом кодування рядка unicode є тестування ( strтип.)

Вам слід або використовувати нормальне, open()і кодувати Unicode самостійно, або (як правило, краща ідея) використовувати, 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... :(

І просто відкрийте його за допомогою менеджера контекстів і випишіть свої символи Unicode:

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

Приклад з використанням багатьох символів Unicode

Ось приклад, який намагається зіставити кожен можливий символ до трьох біт шириною (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 (не друкуйте / не котуйте весь файл на ваш вихід):

$ 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, а не байтами"
Richard Rast

1

Ця помилка виникає при спробі кодування рядка unicode: він намагається розшифрувати його, припускаючи, що це звичайний ASCII. Є дві можливості:

  1. Ви кодуєте його до тестування, але оскільки ви використовували codecs.open, метод запису очікує об'єкт unicode. Таким чином, ви кодуєте його, і він намагається розшифрувати його знову. Спробуйте:f.write(all_html) натомість.
  2. all_html насправді не є об’єктом unicode. Коли ви робите .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à
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.