Chuyển đổi Unicode sang ASCII mà không gặp lỗi trong Python


178

Mã của tôi chỉ xóa một trang web, sau đó chuyển đổi nó thành Unicode.

html = urllib.urlopen(link).read()
html.encode("utf8","ignore")
self.response.out.write(html)

Nhưng tôi nhận được một UnicodeDecodeError:


Traceback (most recent call last):
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/webapp/__init__.py", line 507, in __call__
    handler.get(*groups)
  File "/Users/greg/clounce/main.py", line 55, in get
    html.encode("utf8","ignore")
UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 2818: ordinal not in range(128)

Tôi cho rằng điều đó có nghĩa là HTML chứa một số nỗ lực hình thành sai ở Unicode ở đâu đó. Tôi có thể bỏ bất kỳ byte mã nào đang gây ra sự cố thay vì gặp lỗi không?


2
Tôi coi đó là một lỗi nếu các nhân vật quan trọng bị loại bỏ! (Ngoài ra, câu hỏi ở đâu?)
Arafangion

Có vẻ như bạn đã gặp phải một "không gian nghỉ" trong trang web? sẽ cần được đi trước bởi một c2byte hoặc bạn có thể gặp lỗi giải mã: hexutf8.com/?q=C2A0
jar

Câu trả lời:


105

Cập nhật 2018:

Kể từ tháng 2 năm 2018, việc sử dụng nén như gzipđã trở nên khá phổ biến (khoảng 73% tất cả các trang web sử dụng nó, bao gồm các trang web lớn như Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow và Stack Exchange Network).
Nếu bạn thực hiện một giải mã đơn giản như trong câu trả lời ban đầu với phản hồi được nén, bạn sẽ gặp một lỗi giống hoặc tương tự như sau:

UnicodeDecodeError: 'utf8' codec không thể giải mã byte 0x8b ở vị trí 1: byte mã không mong muốn

Để giải mã một phản hồi gzpipped, bạn cần thêm các mô-đun sau (trong Python 3):

import gzip
import io

Lưu ý: Trong Python 2 bạn sử dụng StringIOthay vìio

Sau đó, bạn có thể phân tích nội dung như thế này:

response = urlopen("https://example.com/gzipped-ressource")
buffer = io.BytesIO(response.read()) # Use StringIO.StringIO(response.read()) in Python 2
gzipped_file = gzip.GzipFile(fileobj=buffer)
decoded = gzipped_file.read()
content = decoded.decode("utf-8") # Replace utf-8 with the source encoding of your requested resource

Mã này đọc phản hồi và đặt các byte vào bộ đệm. Các gzipmô-đun sau đó đọc bộ đệm bằng cách sử dụng GZipFilechức năng. Sau đó, tệp gzipped có thể được đọc lại thành byte và được giải mã thành văn bản thường có thể đọc được.

Câu trả lời gốc từ năm 2010:

Chúng ta có thể nhận được giá trị thực tế được sử dụng cho link?

Ngoài ra, chúng ta thường gặp phải vấn đề này ở đây khi chúng ta đang cố gắng để .encode()một chuỗi byte đã được mã hóa. Vì vậy, bạn có thể cố gắng giải mã nó đầu tiên như trong

html = urllib.urlopen(link).read()
unicode_str = html.decode(<source encoding>)
encoded_str = unicode_str.encode("utf8")

Ví dụ:

html = '\xa0'
encoded_str = html.encode("utf8")

Thất bại với

UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 0: ordinal not in range(128)

Trong khi:

html = '\xa0'
decoded_str = html.decode("windows-1252")
encoded_str = decoded_str.encode("utf8")

Thành công không có lỗi. Xin lưu ý rằng "windows-1252" là thứ tôi sử dụng làm ví dụ . Tôi đã nhận được điều này từ chardet và nó đã tự tin 0,5 rằng nó đúng! (tốt, như được đưa ra với chuỗi có độ dài 1 ký tự, bạn mong đợi điều gì) Bạn nên thay đổi điều đó thành mã hóa chuỗi byte được trả về từ .urlopen().read()nội dung bạn áp dụng cho nội dung bạn đã truy xuất.

Một vấn đề khác tôi thấy đó là .encode()phương thức chuỗi trả về chuỗi đã sửa đổi và không sửa đổi nguồn tại chỗ. Vì vậy, thật vô dụng khi có self.response.out.write(html)html không phải là chuỗi được mã hóa từ html.encode (nếu đó là những gì bạn đã nhắm đến ban đầu).

Như Ignacio đã đề xuất, hãy kiểm tra trang web nguồn để biết mã hóa thực tế của chuỗi được trả về từ đó read(). Đó là một trong các thẻ Meta hoặc trong tiêu đề ContentType trong phản hồi. Sử dụng sau đó làm tham số cho .decode().

Tuy nhiên, xin lưu ý rằng không nên giả định rằng các nhà phát triển khác chịu trách nhiệm đủ để đảm bảo các khai báo bộ tiêu đề và / hoặc ký tự meta khớp với nội dung thực tế. (Đó là một PITA, vâng, tôi nên biết, tôi một trong số đó trước đây).


1
Trong ví dụ của bạn, tôi nghĩ bạn có ý nghĩa cho dòng cuối cùng là encoded_str = decoded_str.encode("utf8")
Ajith Antony

1
Tôi đã thử trong Python 2.7.15 và tôi nhận được thông báo này raise IOError, 'Not a gzipped file'. Lỗi tôi đã làm là gì?
Hyun-geun Kim

222
>>> u'aあä'.encode('ascii', 'ignore')
'a'

Giải mã chuỗi bạn nhận lại, sử dụng bộ ký tự trong metathẻ thích hợp trong phản hồi hoặc trong Content-Typetiêu đề, sau đó mã hóa.

Phương pháp encode(encoding, errors)chấp nhận xử lý tùy chỉnh cho các lỗi. Các giá trị mặc định, bên cạnh đó ignorelà:

>>> u'aあä'.encode('ascii', 'replace')
b'a??'
>>> u'aあä'.encode('ascii', 'xmlcharrefreplace')
b'a&#12354;&#228;'
>>> u'aあä'.encode('ascii', 'backslashreplace')
b'a\\u3042\\xe4'

Xem https://docs.python.org/3/l Library / stdtypes.html#str.encode


119

Như một phần mở rộng cho câu trả lời của Ignacio Vazquez-Abrams

>>> u'aあä'.encode('ascii', 'ignore')
'a'

Đôi khi, mong muốn xóa dấu trọng âm khỏi ký tự và in biểu mẫu cơ sở. Điều này có thể được thực hiện với

>>> import unicodedata
>>> unicodedata.normalize('NFKD', u'aあä').encode('ascii', 'ignore')
'aa'

Bạn cũng có thể muốn dịch các ký tự khác (chẳng hạn như dấu chấm câu) sang các ký tự tương đương gần nhất của chúng, ví dụ ký tự unicode RIGHT SINGLE QUOTATION Mark không được chuyển đổi thành mã ascii APOSTROPHE khi mã hóa.

>>> print u'\u2019'

>>> unicodedata.name(u'\u2019')
'RIGHT SINGLE QUOTATION MARK'
>>> u'\u2019'.encode('ascii', 'ignore')
''
# Note we get an empty string back
>>> u'\u2019'.replace(u'\u2019', u'\'').encode('ascii', 'ignore')
"'"

Mặc dù có nhiều cách hiệu quả hơn để thực hiện điều này. Xem câu hỏi này để biết thêm chi tiết Cơ sở dữ liệu "ASCII tốt nhất cho Unicode" này của Python ở đâu?


4
Cả hai đều hữu ích trong việc giải quyết câu hỏi đã được hỏi và thực tế để giải quyết các vấn đề có thể nằm dưới câu hỏi được hỏi. Đây là một câu trả lời mô hình cho loại câu hỏi này.
shanusmagnus

96

Sử dụng unidecode - nó thậm chí chuyển đổi các ký tự lạ thành ascii ngay lập tức và thậm chí chuyển đổi tiếng Trung sang ngữ âm ascii.

$ pip install unidecode

sau đó:

>>> from unidecode import unidecode
>>> unidecode(u'北京')
'Bei Jing'
>>> unidecode(u'Škoda')
'Skoda'

3
halle-freakin-lujah - đã đến lúc tôi tìm thấy một câu trả lời phù hợp với mình
Aurielle Perlmann

10
Nâng cao giá trị thú vị. Lưu ý rằng từ này mangle trong tất cả các ngôn ngữ có dấu. Škoda không phải là Skoda. Skoda có lẽ có nghĩa là một cái gì đó thô với lươn và hoverccraft.
Sylvain

1
Tôi đã lùng sục trên mạng nhiều ngày cho đến bây giờ .... cảm ơn, cảm ơn rất nhiều
Stephen

23

Tôi sử dụng chức năng trợ giúp này trong tất cả các dự án của tôi. Nếu nó không thể chuyển đổi unicode, nó sẽ bỏ qua nó. Điều này liên kết với một thư viện django, nhưng với một nghiên cứu nhỏ, bạn có thể bỏ qua nó.

from django.utils import encoding

def convert_unicode_to_string(x):
    """
    >>> convert_unicode_to_string(u'ni\xf1era')
    'niera'
    """
    return encoding.smart_str(x, encoding='ascii', errors='ignore')

Tôi không còn nhận được bất kỳ lỗi unicode sau khi sử dụng này.


10
Đó là CUNG CẤP vấn đề, không chẩn đoán và sửa chữa. Nó giống như nói "Sau khi tôi cắt chân ra, tôi không còn gặp vấn đề với bắp và bánh".
John Machin

10
Tôi đồng ý nó đang đàn áp vấn đề. Có vẻ như đó là những gì câu hỏi sau mặc dù. Nhìn vào ghi chú của anh ấy: "Tôi có thể bỏ bất kỳ byte mã nào đang gây ra sự cố thay vì gặp lỗi không?"
Gattster

3
điều này giống hệt như gọi đơn giản là "some-string" .encode ('ascii', 'ign')
Joshua Burns

17
Tôi không thể nói cho bạn biết tôi mệt mỏi như thế nào khi ai đó đặt câu hỏi về SO và nhận được tất cả những câu trả lời thuyết giáo này. "Xe của tôi sẽ không khởi động." "Tại sao bạn muốn khởi động xe của bạn? Thay vào đó bạn nên đi bộ." Dừng lại đi!
shanusmagnus

8
@JohnMachin Không ai quan tâm. Tôi không quan tâm những gì mà người chậm phát triển đưa vào nguồn cấp dữ liệu RSS, nếu đó là một số nhân vật không có trong ascii thì nó có thể bị cắt ngắn. Vấn đề của họ. Tôi chỉ muốn con trăn thực sự bóp cổ nó và xử lý nó, không đưa ra lỗi cho tôi mỗi khi tôi chỉ định 'bỏ qua'. Ai đã nghĩ ra thứ chết tiệt đó?!
dùng1244215

10

Đối với các bảng điều khiển bị hỏng như cmd.exevà đầu ra HTML, bạn luôn có thể sử dụng:

my_unicode_string.encode('ascii','xmlcharrefreplace')

Điều này sẽ bảo tồn tất cả các ký tự không phải mã ascii trong khi làm cho chúng có thể in được bằng ASCII thuần túy bằng HTML.

CẢNH BÁO : Nếu bạn sử dụng mã này trong mã sản xuất để tránh lỗi thì rất có thể có lỗi trong mã của bạn . Trường hợp sử dụng hợp lệ duy nhất cho việc này là in ra bàn điều khiển không unicode hoặc chuyển đổi dễ dàng sang các thực thể HTML trong ngữ cảnh HTML.

Và cuối cùng, nếu bạn đang ở trên windows và sử dụng cmd.exe thì bạn có thể gõ chcp 65001để bật đầu ra utf-8 (hoạt động với phông chữ Lucida Console). Bạn có thể cần phải thêm myUnicodeString.encode('utf8').


6

Bạn đã viết "" "Tôi cho rằng điều đó có nghĩa là HTML chứa một số nỗ lực hình thành sai ở unicode ở đâu đó." ""

HTML KHÔNG được dự kiến ​​có chứa bất kỳ loại "nỗ lực nào ở unicode", được định dạng tốt hay không. Nó nhất thiết phải chứa các ký tự Unicode được mã hóa trong một số mã hóa, thường được cung cấp ở phía trước ... tìm "bộ ký tự".

Bạn dường như cho rằng bộ ký tự là UTF-8 ... dựa trên cơ sở nào? Byte "\ xA0" được hiển thị trong thông báo lỗi của bạn cho biết rằng bạn có thể có bộ ký tự byte đơn, ví dụ cp1252.

Nếu bạn không thể hiểu được bất kỳ tuyên bố nào khi bắt đầu HTML, hãy thử sử dụng chardet để tìm hiểu xem mã hóa có khả năng là gì.

Tại sao bạn đã gắn thẻ câu hỏi của bạn với "regex"?

Cập nhật sau khi bạn thay thế toàn bộ câu hỏi của mình bằng một câu hỏi không:

html = urllib.urlopen(link).read()
# html refers to a str object. To get unicode, you need to find out
# how it is encoded, and decode it.

html.encode("utf8","ignore")
# problem 1: will fail because html is a str object;
# encode works on unicode objects so Python tries to decode it using 
# 'ascii' and fails
# problem 2: even if it worked, the result will be ignored; it doesn't 
# update html in situ, it returns a function result.
# problem 3: "ignore" with UTF-n: any valid unicode object 
# should be encodable in UTF-n; error implies end of the world,
# don't try to ignore it. Don't just whack in "ignore" willy-nilly,
# put it in only with a comment explaining your very cogent reasons for doing so.
# "ignore" with most other encodings: error implies that you are mistaken
# in your choice of encoding -- same advice as for UTF-n :-)
# "ignore" with decode latin1 aka iso-8859-1: error implies end of the world.
# Irrespective of error or not, you are probably mistaken
# (needing e.g. cp1252 or even cp850 instead) ;-)

4

Nếu bạn có một chuỗi line, bạn có thể sử dụng .encode([encoding], [errors='strict'])phương thức cho chuỗi để chuyển đổi các loại mã hóa.

line = 'my big string'

line.encode('ascii', 'ignore')

Để biết thêm thông tin về cách xử lý ASCII và unicode trong Python, đây là một trang web thực sự hữu ích: https://docs.python.org/2/howto/unicode.html


1
Điều này không hoạt động khi bạn có một ký tự không phải ascii như ü trong chuỗi.
sajid

4

Tôi nghĩ rằng câu trả lời là có nhưng chỉ bằng bit và miếng, điều này gây khó khăn cho việc khắc phục vấn đề nhanh chóng như

UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 2818: ordinal not in range(128)

Hãy lấy một ví dụ, giả sử tôi có tệp có một số dữ liệu ở dạng sau (có chứa ký tự ascii và không ascii)

1/10/17, 21:36 - Vùng đất: Chào mừng bạn

và chúng tôi muốn bỏ qua và chỉ giữ lại các ký tự ascii.

Mã này sẽ làm:

import unicodedata
fp  = open(<FILENAME>)
for line in fp:
    rline = line.strip()
    rline = unicode(rline, "utf-8")
    rline = unicodedata.normalize('NFKD', rline).encode('ascii','ignore')
    if len(rline) != 0:
        print rline

và loại (đường dây) sẽ cung cấp cho bạn

>type(rline) 
<type 'str'>

Điều này cũng hoạt động cho các trường hợp (không đạt tiêu chuẩn) "mở rộng ascii"
Oliver Zendel

1
unicodestring = '\xa0'

decoded_str = unicodestring.decode("windows-1252")
encoded_str = decoded_str.encode('ascii', 'ignore')

Làm việc cho tôi


-5

Có vẻ như bạn đang sử dụng python 2.x. Python 2.x mặc định là ascii và nó không biết về Unicode. Do đó ngoại lệ.

Chỉ cần dán dòng dưới đây sau shebang, nó sẽ hoạt động

# -*- coding: utf-8 -*-

Các codingbình luận không phải là một cứu cánh kỳ diệu. Bạn cần biết tại sao lỗi được tạo ra, điều này chỉ khắc phục mọi thứ khi có các ký tự xấu trong nguồn Python của bạn. Điều đó dường như không phải là trường hợp cho câu hỏi này.
Đánh dấu tiền chuộc
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.