แปลง Unicode เป็น ASCII โดยไม่มีข้อผิดพลาดใน Python


178

รหัสของฉันเพิ่ง scrapes หน้าเว็บแล้วแปลงเป็น Unicode

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

แต่ฉันได้รับ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)

ฉันคิดว่านั่นหมายความว่า HTML มีความพยายามที่ผิดพลาดเกิดขึ้นที่ Unicode บางแห่ง ฉันสามารถทิ้งรหัสไบต์ใดก็ตามที่ทำให้เกิดปัญหาแทนที่จะได้รับข้อผิดพลาดได้หรือไม่


2
ฉันคิดว่ามันเป็นข้อผิดพลาดหากตัวละครสำคัญถูกทิ้ง! (นอกจากนี้คำถามอยู่ที่ไหน)
Arafangion

ดูเหมือนว่าคุณอาจพบกับ "ไม่มีที่ว่าง" ในหน้าเว็บ? จะต้องนำหน้าด้วยc2ไบต์หรือคุณอาจได้รับข้อผิดพลาดในการถอดรหัส: hexutf8.com/?q=C2A0
jar

คำตอบ:


105

ปรับปรุง 2018:

ตั้งแต่เดือนกุมภาพันธ์ 2018 การใช้การบีบอัดอย่างgzipเป็นที่นิยม (ประมาณ 73% ของเว็บไซต์ทั้งหมดใช้รวมถึงเว็บไซต์ขนาดใหญ่เช่น Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow และ Stack Exchange Network)
หากคุณทำการถอดรหัสอย่างง่ายเช่นเดียวกับคำตอบดั้งเดิมที่มีการตอบกลับ gzipped คุณจะได้รับข้อผิดพลาดเช่นหรือคล้ายกับสิ่งนี้:

UnicodeDecodeError: ตัวแปลงสัญญาณ 'utf8' ไม่สามารถถอดรหัสไบต์ 0x8b ในตำแหน่งที่ 1: ไบต์รหัสที่ไม่คาดคิด

ในการถอดรหัสการตอบกลับ gzpipped คุณต้องเพิ่มโมดูลต่อไปนี้ (ใน Python 3):

import gzip
import io

หมายเหตุ: ใน Python 2 คุณจะใช้StringIOแทนio

จากนั้นคุณสามารถแยกเนื้อหาออกดังนี้:

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

รหัสนี้อ่านการตอบสนองและวางไบต์ในบัฟเฟอร์ gzipโมดูลแล้วอ่านบัฟเฟอร์โดยใช้GZipFileฟังก์ชั่น หลังจากนั้นไฟล์ gzipped สามารถอ่านเป็นไบต์อีกครั้งและถอดรหัสเป็นข้อความที่อ่านได้ตามปกติในตอนท้าย

คำตอบเดิมจาก 2010:

เราสามารถรับมูลค่าที่แท้จริงได้linkหรือไม่?

นอกจากนี้เรามักจะพบปัญหานี้ที่นี่เมื่อเราพยายามที่จะ.encode()สตริงไบต์เข้ารหัสแล้ว ดังนั้นคุณอาจลองถอดรหัสก่อน

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

ตัวอย่างเช่น:

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

ล้มเหลวด้วย

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

ในขณะที่:

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

ประสบความสำเร็จโดยไม่มีข้อผิดพลาด ทำทราบว่า "1252 หน้าต่าง" เป็นสิ่งที่ฉันใช้เป็นตัวอย่าง ฉันได้รับมาจากชาร์ตและมีความมั่นใจ 0.5 ว่าถูกต้อง! (เช่นเดียวกับที่ได้รับกับสตริงที่มีความยาว 1 ตัวอักษรคุณคาดหวังอะไร) คุณควรเปลี่ยนการเข้ารหัสไบต์สตริงที่ส่งกลับจาก.urlopen().read()เป็นสิ่งที่ใช้กับเนื้อหาที่คุณดึงมา

ปัญหาอื่นที่ฉันเห็นมีอยู่ว่า.encode()วิธีสตริงส่งกลับสตริงที่แก้ไขและไม่ได้แก้ไขแหล่งที่มา ดังนั้นมันจึงไม่มีประโยชน์ที่จะมีself.response.out.write(html)html ไม่ใช่สตริงที่เข้ารหัสจาก html.encode (ถ้านั่นคือสิ่งที่คุณตั้งใจไว้ตั้งแต่แรก)

ตามที่อิกนาชิโอแนะนำให้ตรวจสอบหน้าเว็บต้นทางสำหรับการเข้ารหัสสตริงที่ส่งคืนread()จริง มันอาจเป็นหนึ่งในแท็ก Meta หรือในส่วนหัว ContentType ในการตอบสนอง .decode()ใช้นั้นเป็นพารามิเตอร์สำหรับ

โปรดทราบว่าไม่ควรถือว่าผู้พัฒนารายอื่นมีความรับผิดชอบเพียงพอที่จะตรวจสอบให้แน่ใจว่าการประกาศชุดอักขระส่วนหัวและ / หรือเมตาตรงกับเนื้อหาจริง (ซึ่งเป็น PITA ใช่ฉันควรรู้ฉันเป็นหนึ่งในนั้นก่อนหน้านี้)


1
ในตัวอย่างของคุณฉันคิดว่าคุณตั้งใจจะให้บรรทัดสุดท้ายเป็น encoded_str = decoded_str.encode("utf8")
Ajith Antony

1
ฉันพยายามใน Python 2.7.15 และฉันได้รับข้อความraise IOError, 'Not a gzipped file'นี้ ฉันทำผิดอะไร?
คิมฮยอนกุน

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

ถอดรหัสสตริงที่คุณได้รับกลับมาโดยใช้ชุดอักขระในmetaแท็กที่เหมาะสมในการตอบกลับหรือในContent-Typeส่วนหัวแล้วเข้ารหัส

วิธีการencode(encoding, errors)ยอมรับตัวจัดการที่กำหนดเองสำหรับข้อผิดพลาด ค่าเริ่มต้นนอกจากignoreนี้คือ:

>>> 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'

ดูhttps://docs.python.org/3/library/stdtypes.html#str.encode


119

เป็นส่วนขยายของคำตอบของ Ignacio Vazquez-Abrams

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

บางครั้งเป็นที่พึงปรารถนาที่จะลบเครื่องหมายเน้นเสียงออกจากตัวอักษรและพิมพ์แบบฟอร์มฐาน ซึ่งสามารถทำได้ด้วย

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

คุณอาจต้องการแปลอักขระอื่น ๆ (เช่นเครื่องหมายวรรคตอน) เป็นรายการเทียบเท่าที่ใกล้ที่สุดตัวอย่างเช่นอักขระยูนิโค้ด RIGHT SINGLE QUOTATION MARK ไม่ได้ถูกแปลงเป็น ASCII APOSTROPHE เมื่อทำการเข้ารหัส

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

แม้ว่าจะมีวิธีที่มีประสิทธิภาพมากกว่านี้ ดูคำถามนี้สำหรับรายละเอียดเพิ่มเติมฐานข้อมูล "ASCII ที่ดีที่สุดสำหรับ Unicode นี้" ของไพ ธ อนอยู่ที่ไหน


4
ทั้งเป็นประโยชน์ในการจัดการกับคำถามที่ถูกถามและเป็นประโยชน์สำหรับการแก้ไขปัญหาที่อาจเป็นพื้นฐานของคำถามที่ถาม นี่คือคำตอบของแบบจำลองสำหรับคำถามประเภทนี้
shanusmagnus

96

ใช้unidecode - มันยังแปลงอักขระแปลก ๆ ให้เป็น ascii ได้ทันทีและยังแปลงภาษาจีนเป็นแบบออกเสียงด้วย ascii

$ pip install unidecode

แล้ว:

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

3
Halle-freakin-lujah - เวลาที่มันเกี่ยวกับฉันพบคำตอบที่ทำงานสำหรับฉัน
Aurielle Perlmann

10
โหวตขึ้นเพื่อความสนุก โปรดทราบว่าคำนี้รวบรวมคำในภาษาที่เน้นเสียงทั้งหมด Škodaไม่ใช่ Skoda Skoda อาจหมายถึงบางสิ่งบางอย่างกับปลาไหลและ hovercrafts
Sylvain

1
ฉันใช้อินเทอร์เน็ตมาหลายวันแล้วในตอนนี้ .... ขอบคุณมากขอบคุณ
Stephen

23

ฉันใช้ฟังก์ชันตัวช่วยนี้ตลอดโครงการทั้งหมดของฉัน ถ้ามันไม่สามารถแปลงยูนิโค้ดได้ก็จะไม่สนใจมัน สิ่งนี้เชื่อมโยงกับห้องสมุด django แต่ด้วยการวิจัยเล็กน้อยคุณสามารถข้ามมันได้

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

ฉันไม่ได้รับข้อผิดพลาด unicode อีกต่อไปหลังจากใช้สิ่งนี้


10
นั่นคือการปราบปรามปัญหาไม่ใช่การวินิจฉัยและแก้ไข มันเหมือนกับว่า "หลังจากที่ฉันตัดเท้าฉันไม่มีปัญหากับข้าวโพดและ bunions"
John Machin

10
ฉันเห็นด้วยที่จะระงับปัญหา ดูเหมือนว่าเป็นสิ่งที่คำถามหลังจากนั้น ดูที่บันทึกย่อของเขา: "ฉันสามารถปล่อยรหัสไบต์ใดก็ตามที่ทำให้เกิดปัญหาแทนที่จะได้รับข้อผิดพลาดได้หรือไม่"
Gattster

3
ตรงนี้เหมือนกับการเรียก "some-string" .encode ('ascii', 'ละเว้น')
Joshua Burns

17
ฉันไม่สามารถบอกคุณได้ว่าฉันเป็นคนเหนื่อยมากแค่ไหนในการถามคำถามเกี่ยวกับเรื่องนั้นและได้รับคำตอบจากนักเทศน์เหล่านี้ทั้งหมด "รถของฉันไม่เริ่ม" "ทำไมคุณถึงต้องการสตาร์ทรถคุณควรเดินแทน" หยุดนะ!
shanusmagnus

8
@JohnMachin ไม่มีใครสนใจ ฉันไม่สนใจสิ่งที่คนอึที่ปัญญาอ่อนใส่ไว้ในตัวดึงข้อมูล RSS ถ้าเป็นตัวละครบางตัวที่ไม่ได้อยู่ใน ASCII ก็สามารถถูกตัดทอนได้ ปัญหาของพวกเขา ฉันแค่ต้องการให้หลามหลุมพรางจริง ๆ และจัดการกับมันไม่ใช่ให้ข้อผิดพลาดทุกครั้งที่ฉันระบุ 'เพิกเฉย' ใครเป็นคนบ้า
user1244215

10

สำหรับคอนโซลที่ใช้งานไม่ได้เช่นcmd.exeเอาต์พุต HTML คุณสามารถใช้:

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

สิ่งนี้จะรักษาตัวอักษรที่ไม่ใช่ ASCII ทั้งหมดในขณะที่ทำให้สามารถพิมพ์ได้ใน ASCII บริสุทธิ์และใน HTML

คำเตือน : หากคุณใช้วิธีนี้ในรหัสการผลิตเพื่อหลีกเลี่ยงข้อผิดพลาดแล้วส่วนใหญ่มีสิ่งผิดปกติในรหัสของคุณ กรณีการใช้งานที่ถูกต้องสำหรับสิ่งนี้คือการพิมพ์ไปยังคอนโซลที่ไม่ใช่ยูนิโค้ดหรือแปลงเป็นเอนทิตี HTML อย่างง่ายในบริบท HTML

และสุดท้ายถ้าคุณอยู่บน windows และใช้ cmd.exe คุณสามารถพิมพ์chcp 65001เพื่อเปิดใช้งานเอาต์พุต utf-8 (ใช้ได้กับฟอนต์ Lucida Console) myUnicodeString.encode('utf8')คุณอาจจะต้องเพิ่ม


6

คุณเขียน "" "ฉันถือว่านั่นหมายความว่า HTML มีความพยายามที่เกิดขึ้นอย่างผิดพลาดที่ Unicode บางแห่ง" ""

HTML ไม่คาดว่าจะมี "ความพยายามที่ unicode" รูปแบบที่ดีหรือไม่ มันจำเป็นที่จะต้องมีตัวอักษร Unicode ที่เข้ารหัสในการเข้ารหัสบางอย่างซึ่งโดยทั่วไปจะให้มาด้านหน้า ... มองหา "charset"

ดูเหมือนว่าคุณจะสมมติว่าชุดอักขระเป็น UTF-8 ... ในพื้นที่ใด? ไบต์ "\ xA0" ที่แสดงในข้อความแสดงข้อผิดพลาดระบุว่าคุณอาจมีชุดอักขระไบต์เดียวเช่น cp1252

หากคุณไม่สามารถรับรู้ได้จากการประกาศในช่วงเริ่มต้นของ HTML ลองใช้chardetเพื่อค้นหาว่าการเข้ารหัสแบบใดที่น่าจะเป็น

ทำไมคุณติดแท็กคำถามของคุณด้วย "regex"

อัปเดตหลังจากคุณแทนที่คำถามทั้งหมดด้วยคำถามที่ไม่ใช่

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

หากคุณมีสตริงlineคุณสามารถใช้.encode([encoding], [errors='strict'])วิธีสำหรับสตริงเพื่อแปลงประเภทการเข้ารหัส

line = 'my big string'

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการจัดการ ASCII และ unicode ใน Python นี่เป็นเว็บไซต์ที่มีประโยชน์จริง ๆ : https://docs.python.org/2/howto/unicode.html


1
สิ่งนี้ไม่ทำงานเมื่อคุณมีอักขระที่ไม่ใช่ ASCII เช่นüในสตริง
sajid

4

ฉันคิดว่าคำตอบนั้นอยู่ที่นั่น แต่ในหน่วยบิตซึ่งทำให้ยากต่อการแก้ไขปัญหาอย่างรวดเร็วเช่น

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

ลองยกตัวอย่างสมมติว่าฉันมีไฟล์ที่มีข้อมูลบางอย่างในรูปแบบต่อไปนี้ (มี ascii และ non-ascii chars)

1/10/17, 21:36 - ที่ดิน: ยินดีต้อนรับ��

และเราต้องการเพิกเฉยและสงวนอักขระ ASCII เพียงตัวเดียว

รหัสนี้จะทำ:

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

และพิมพ์ (rline) จะให้คุณ

>type(rline) 
<type 'str'>

วิธีนี้ใช้ได้กับกรณี "Extended ascii" (ไม่ได้มาตรฐาน)
Oliver Zendel


-5

ดูเหมือนว่าคุณกำลังใช้ python 2.x Python 2.x ใช้ค่าเริ่มต้นเป็น ascii และไม่ทราบเกี่ยวกับ Unicode ดังนั้นข้อยกเว้น

เพียงวางบรรทัดด้านล่างหลังจาก shebang มันจะทำงาน

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

codingความคิดเห็นไม่ได้เป็นความมหัศจรรย์รักษาทั้งหมด คุณจำเป็นต้องรู้สาเหตุที่ทำให้เกิดข้อผิดพลาดซึ่งจะแก้ไขได้เฉพาะเมื่อมีอักขระที่ไม่ดีในแหล่ง Python ของคุณ ดูเหมือนจะไม่เป็นเช่นนั้นสำหรับคำถามนี้
Mark Ransom
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.