Viết văn bản Unicode vào một tệp văn bản?


225

Tôi đang lấy dữ liệu ra khỏi tài liệu Google, xử lý nó và ghi nó vào một tệp (cuối cùng tôi sẽ dán vào trang Wordpress).

Nó có một số biểu tượng không phải ASCII. Làm cách nào tôi có thể chuyển đổi chúng một cách an toàn thành các biểu tượng có thể được sử dụng trong nguồn HTML?

Hiện tại tôi đang chuyển đổi mọi thứ sang Unicode trên đường đi, nối tất cả lại với nhau trong một chuỗi Python, sau đó thực hiện:

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

Có lỗi mã hóa trên dòng cuối cùng:

UnicodeDecodeError: 'ascii' codec không thể giải mã byte 0xa0 ở vị trí 12286: thứ tự không nằm trong phạm vi (128)

Giải pháp từng phần:

Python này chạy không có lỗi:

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"))

Nhưng sau đó nếu tôi mở tệp văn bản thực tế, tôi thấy rất nhiều biểu tượng như:

Qur’an 

Có lẽ tôi cần phải viết vào một cái gì đó ngoài một tập tin văn bản?


1
Chương trình bạn đang sử dụng để mở nó không diễn giải chính xác văn bản UTF-8. Nó nên có một tùy chọn để mở tệp dưới dạng UTF-8.
Thomas K

Câu trả lời:


322

Xử lý độc quyền với các đối tượng unicode càng nhiều càng tốt bằng cách giải mã mọi thứ thành các đối tượng unicode khi bạn lần đầu tiên nhận được chúng và mã hóa chúng khi cần thiết trên đường ra.

Nếu chuỗi của bạn thực sự là một đối tượng unicode, bạn sẽ cần chuyển đổi nó thành một đối tượng chuỗi được mã hóa unicode trước khi ghi nó vào một tệp:

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

Khi bạn đọc lại tệp đó, bạn sẽ nhận được một chuỗi mã hóa unicode mà bạn có thể giải mã thành một đối tượng unicode:

f = file('test', 'r')
print f.read().decode('utf8')

Cảm ơn. Điều này chạy không có lỗi, nhưng sau đó nếu tôi mở tệp văn bản, tôi thấy một loạt các ký hiệu lạ :) Tôi cần sao chép và dán văn bản vào trang Wordpress (đừng hỏi). Có cách nào tôi thực sự có thể in các biểu tượng ở đó không? Tôi đoán không phải là một tập tin txt, phải, nhưng có lẽ với một cái gì đó khác?
simon

1
Bạn đang sử dụng gì để mở tệp văn bản? Tôi đoán bạn đang ở trên Windows và bạn đang mở nó trong Notepad, điều này không quá thông minh với mã hóa. Điều gì xảy ra khi bạn mở nó trong Wordpad?
quasistoic

@quasistoic phương thức tập tin hình thành ở đâu?
Omar Cusma Fait

Tôi cần bật chế độ nhị phân, ví dụ f = open ('test', 'wb'), như được mô tả trong stackoverflow.com/a/5513856/6580199 - nếu không tôi sẽ nhận được đối số "TypeError: write () phải là str, không phải byte "
Benji

72

Trong Python 2.6+, bạn có thể sử dụngio.open() mặc định ( dựng sẵnopen() ) trên Python 3:

import io

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

Có thể thuận tiện hơn nếu bạn cần viết văn bản tăng dần (bạn không cần phải gọi unicode_text.encode(character_encoding)nhiều lần). Không giống như codecsmô-đun, iomô-đun có một hỗ trợ dòng mới phổ quát thích hợp.


1
Man, tôi đã dành rất nhiều thời gian để tìm thấy điều này! Cảm ơn bạn!
Georgy Gobozov

2
Điều này cũng hoạt động với Python 3 (rõ ràng, nhưng vẫn đáng để chỉ ra).

37

Xử lý chuỗi Unicode đã được chuẩn hóa trong Python 3.

  1. char đã được lưu trữ bằng Unicode (32-bit) trong bộ nhớ
  2. Bạn chỉ cần mở tệp trong utf-8
    (Chuyển đổi Unicode 32 bit sang độ dài byte biến đổi được tự động thực hiện từ bộ nhớ sang tệp.)

    out1 = "(嘉南大圳 ㄐㄧㄚ ㄋㄢˊ ㄉㄚˋ ㄗㄨㄣˋ )"
    fobj = open("t1.txt", "w", encoding="utf-8")
    fobj.write(out1)
    fobj.close()
    

Nhưng điều này không hoạt động trên Python 2, phải không? (Tôi nên nói, trên mã Python 3 này, nó trông rất súc tích và hợp lý)
Liwen Zhao

nó không hoạt động trên Python 2. Chúng tôi ở lại trên Python 3. 3 tốt hơn nhiều.
david m lee

18

Tệp được mở bởi codecs.openmột tệp lấy unicodedữ liệu, mã hóa nó iso-8859-1và ghi vào tệp. Tuy nhiên, những gì bạn cố gắng viết không phải unicode; bạn lấy unicodevà mã hóa nó trong iso-8859-1 chính mình . Đó là những gì unicode.encodephương thức làm và kết quả của việc mã hóa chuỗi unicode là một bytestring (một strloại.)

Bạn nên sử dụng bình thường open()và tự mã hóa unicode, hoặc (thường là một ý tưởng tốt hơn) sử dụng codecs.open()không tự mã hóa dữ liệu.


17

Lời nói đầu: người xem của bạn sẽ làm việc?

Đảm bảo trình xem / trình chỉnh sửa / thiết bị đầu cuối của bạn (tuy nhiên bạn đang tương tác với tệp được mã hóa utf-8) có thể đọc tệp. Đây thường là một vấn đề trên Windows , ví dụ, Notepad.

Viết văn bản Unicode vào một tệp văn bản?

Trong Python 2, sử dụng opentừ iomô-đun (điều này giống với nội dung opentrong Python 3):

import io

Nói chung, cách tốt nhất là sử dụng UTF-8để ghi vào tệp (chúng ta thậm chí không phải lo lắng về thứ tự byte với utf-8).

encoding = 'utf-8'

utf-8 là mã hóa hiện đại và có thể sử dụng phổ biến nhất - nó hoạt động trong tất cả các trình duyệt web, hầu hết các trình soạn thảo văn bản (xem cài đặt của bạn nếu bạn gặp sự cố) và hầu hết các thiết bị đầu cuối / shell.

Trên Windows, bạn có thể thử utf-16lenếu bạn bị giới hạn xem đầu ra trong Notepad (hoặc một trình xem giới hạn khác).

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

Và chỉ cần mở nó với trình quản lý bối cảnh và viết các ký tự unicode của bạn ra:

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

Ví dụ sử dụng nhiều ký tự Unicode

Đây là một ví dụ cố gắng ánh xạ mọi ký tự có thể rộng tới ba bit (4 là tối đa, nhưng sẽ đi hơi xa) từ biểu diễn kỹ thuật số (theo số nguyên) sang đầu ra có thể in được mã hóa, cùng với tên của nó, nếu có thể (đặt cái này vào một tập tin gọi là 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))

Điều này sẽ chạy theo thứ tự khoảng một phút và bạn có thể xem tệp dữ liệu và nếu trình xem tệp của bạn có thể hiển thị unicode, bạn sẽ thấy nó. Thông tin về các loại có thể được tìm thấy ở đây . Dựa trên số lượng, có lẽ chúng tôi có thể cải thiện kết quả của mình bằng cách loại trừ các danh mục Cn và Co, không có biểu tượng liên quan đến chúng.

$ python uni.py

Nó sẽ hiển thị ánh xạ thập lục phân, danh mục , ký hiệu (trừ khi không thể lấy tên, vì vậy có thể là ký tự điều khiển) và tên của biểu tượng. ví dụ

Tôi khuyên dùng lesstrên Unix hoặc Cygwin (không in / cat toàn bộ tệp cho đầu ra của bạn):

$ less unidata

ví dụ: sẽ hiển thị tương tự như các dòng sau mà tôi đã lấy mẫu từ nó bằng 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 Python 3.5 của tôi từ Anaconda có unicode 8.0, tôi đoán là hầu hết 3 sẽ.


3

Cách in các ký tự unicode thành một tệp:

Lưu cái này vào tập tin: 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: é')

Chạy nó và đầu ra đường ống để tập tin:

python foo.py > tmp.txt

Mở tmp.txt và nhìn vào bên trong, bạn thấy điều này:

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

Vì vậy, bạn đã lưu unicode e với một dấu obfuscation trên nó vào một tập tin.


2
Tôi đã rất vui mừng về câu trả lời này, nhưng nó gây ra lỗi trên máy của tôi. Khi tôi sao chép / dán mã của bạn, tôi gặp lỗi: "TypeError: phải là str, không phải byte"
Richard Rast

1

Lỗi đó phát sinh khi bạn cố mã hóa một chuỗi không unicode: nó cố gắng giải mã nó, giả sử đó là ASCII đơn giản. Có hai khả năng:

  1. Bạn đang mã hóa nó thành một bytestring, nhưng vì bạn đã sử dụng codec.open, phương thức ghi sẽ mong đợi một đối tượng unicode. Vì vậy, bạn mã hóa nó, và nó cố gắng giải mã nó một lần nữa. Hãy thử: f.write(all_html)thay vào đó.
  2. all_html trên thực tế không phải là một đối tượng unicode. Khi bạn làm .encode(...), đầu tiên nó sẽ cố gắng giải mã nó.

0

Trong trường hợp viết bằng 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à'

Trong trường hợp viết bằng 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)

Để tránh lỗi này, bạn sẽ phải mã hóa nó thành byte bằng cách sử dụng codec "utf-8" như thế này:

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

và giải mã dữ liệu trong khi đọc bằng cách sử dụng codec "utf-8":

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

Và nếu bạn cố gắng thực hiện in trên chuỗi này, nó sẽ tự động giải mã bằng cách sử dụng codec "utf-8" như thế này

>>> print a
batsà
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.