Ubah Unicode menjadi ASCII tanpa kesalahan dalam Python

178

Kode saya hanya mengikis halaman web, lalu mengubahnya menjadi Unicode.

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

Tapi saya mendapat 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)

Saya berasumsi itu berarti HTML berisi beberapa upaya yang salah di Unicode. Bisakah saya melepaskan kode byte apa pun yang menyebabkan masalah alih-alih mendapatkan kesalahan?

kaca
sumber
2
Saya menganggapnya sebagai kesalahan jika karakter penting dibuang! (Juga, di mana pertanyaannya?)
Arafangion
Sepertinya Anda mungkin menjumpai "no break space" di halaman web? perlu didahului dengan c2byte atau Anda mungkin akan mendapatkan kesalahan decode: hexutf8.com/?q=C2A0
jar

Jawaban:

105

Pembaruan 2018:

Pada Februari 2018, menggunakan kompresi seperti gziptelah menjadi sangat populer (sekitar 73% dari semua situs web menggunakannya, termasuk situs besar seperti Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow, dan Stack Exchange Network).
Jika Anda melakukan decode sederhana seperti pada jawaban asli dengan respons yang di-gzip, Anda akan mendapatkan kesalahan seperti atau mirip dengan ini:

UnicodeDecodeError: codec 'utf8' tidak dapat mendekode byte 0x8b di posisi 1: byte kode yang tidak terduga

Untuk men-decode respons gzpipped, Anda perlu menambahkan modul berikut (dengan Python 3):

import gzip
import io

Catatan: Dalam Python 2 Anda akan menggunakan StringIOsebagai gantinyaio

Kemudian Anda dapat menguraikan konten seperti ini:

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

Kode ini membaca respons, dan menempatkan byte dalam buffer. The gzipmodul kemudian membaca buffer menggunakan GZipFilefungsi. Setelah itu, file yang di-gzip dapat dibaca menjadi byte lagi dan diterjemahkan ke teks yang biasanya dapat dibaca pada akhirnya.

Jawaban Asli dari 2010:

Bisakah kita mendapatkan nilai aktual yang digunakan link?

Selain itu, kami biasanya menghadapi masalah ini di sini ketika kami mencoba untuk .encode()byte string yang sudah dikodekan. Jadi, Anda dapat mencoba memecahkan kode itu terlebih dahulu seperti pada

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

Sebagai contoh:

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

Gagal dengan

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

Sementara:

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

Berhasil tanpa kesalahan. Perhatikan bahwa "windows-1252" adalah sesuatu yang saya gunakan sebagai contoh . Saya mendapat ini dari chardet dan memiliki 0,5 keyakinan bahwa itu benar! (well, seperti yang diberikan dengan string 1-karakter-panjang, apa yang Anda harapkan) Anda harus mengubahnya ke pengkodean string byte yang dikembalikan dari .urlopen().read()ke apa yang berlaku untuk konten yang Anda ambil.

Masalah lain yang saya lihat di sana adalah bahwa .encode()metode string mengembalikan string yang dimodifikasi dan tidak mengubah sumber di tempatnya. Jadi agak tidak berguna untuk memiliki self.response.out.write(html)html bukan string yang disandikan dari html.encode (jika itu yang awalnya Anda tuju).

Seperti yang disarankan Ignacio, periksa halaman web sumber untuk pengkodean sebenarnya dari string yang dikembalikan read(). Entah itu di salah satu tag Meta atau di header ContentType dalam respons. Gunakan itu sebagai parameter untuk .decode().

Namun perlu dicatat bahwa tidak boleh diasumsikan bahwa pengembang lain cukup bertanggung jawab untuk memastikan deklarasi header dan / atau karakter meta cocok dengan konten yang sebenarnya. (Yang merupakan PITA, ya, saya harus tahu, saya adalah salah satu dari mereka sebelumnya).

Vin-G
sumber
1
Dalam contoh Anda, saya pikir Anda bermaksud untuk baris terakhir menjadi encoded_str = decoded_str.encode("utf8")
Ajith Antony
1
Saya mencoba dengan Python 2.7.15, dan saya mendapat pesan ini raise IOError, 'Not a gzipped file'. Apa kesalahan saya?
Hyun-geun Kim
222
>>> u'aあä'.encode('ascii', 'ignore')
'a'

Dekode ulang string yang Anda dapatkan kembali, menggunakan charset di metatag yang sesuai di respons atau di Content-Typeheader, lalu menyandikan.

Metode encode(encoding, errors)menerima penangan khusus untuk kesalahan. Nilai defaultnya, selain itu ignore, adalah:

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

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

Ignacio Vazquez-Abrams
sumber
119

Sebagai perpanjangan dari jawaban Ignacio Vazquez-Abrams

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

Kadang-kadang diinginkan untuk menghapus aksen dari karakter dan mencetak formulir dasar. Ini bisa dicapai dengan

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

Anda mungkin juga ingin menerjemahkan karakter lain (seperti tanda baca) ke padanan terdekatnya, misalnya karakter unicode MARK QUOTATION QUOTATION RIGHT SINGLE tidak dapat dikonversi ke ascii APOSTROPHE saat penyandian.

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

Meskipun ada cara yang lebih efisien untuk mencapai ini. Lihat pertanyaan ini untuk perincian lebih lanjut Di mana basis data "ASCII terbaik untuk Unicode" Python ini?

Peter Gibson
sumber
4
Keduanya membantu dalam menjawab pertanyaan yang diajukan, dan praktis untuk mengatasi masalah yang mungkin mendasari pertanyaan yang diajukan. Ini adalah jawaban model untuk pertanyaan seperti ini.
shanusmagnus
96

Gunakan unidecode - bahkan mengubah karakter aneh menjadi ascii secara instan, dan bahkan mengubah bahasa Mandarin menjadi fonetik ascii.

$ pip install unidecode

kemudian:

>>> from unidecode import unidecode
>>> unidecode(u'北京')
'Bei Jing'
>>> unidecode(u'Škoda')
'Skoda'
Nimo
sumber
3
halle-freakin-lujah - sudah saatnya saya menemukan jawaban yang bekerja untuk saya
Aurielle Perlmann
10
Terpilih untuk nilai kesenangan. Perhatikan bahwa kata-kata ini tidak sesuai dalam semua bahasa yang ditekankan. Škoda bukan Skoda. Skoda kemungkinan besar berarti sesuatu yang kotor dengan belut dan hovercrafts.
Sylvain
1
Saya telah menjelajahi internet selama berhari-hari sampai sekarang .... terima kasih, terima kasih banyak
Stephen
23

Saya menggunakan fungsi pembantu ini di seluruh proyek saya. Jika tidak dapat mengonversi unicode, ia mengabaikannya. Ini mengikat ke perpustakaan Django, tetapi dengan sedikit riset Anda bisa melewati itu.

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

Saya tidak lagi mendapatkan kesalahan unicode setelah menggunakan ini.

Gattster
sumber
10
Itulah SUPPRESSING masalahnya, bukan diagnosa dan perbaikan. Ini seperti mengatakan "Setelah saya memotong kaki saya, saya tidak lagi memiliki masalah dengan jagung dan roti".
John Machin
10
Saya setuju itu menekan masalah. Sepertinya memang itulah pertanyaannya. Lihatlah catatannya: "Bisakah saya meninggalkan kode byte apa pun yang menyebabkan masalah alih-alih mendapatkan kesalahan?"
Gattster
3
ini persis sama dengan hanya memanggil "some-string" .encode ('ascii', 'abaikan')
Joshua Burns
17
Saya tidak bisa memberi tahu Anda betapa lelahnya saya terhadap seseorang yang mengajukan pertanyaan tentang SO, dan mendapatkan semua tanggapan khotbah ini. "Mobilku tidak mau hidup." "Mengapa kamu ingin menyalakan mobilmu? Kamu harus berjalan kaki saja." Hentikan!
shanusmagnus
8
@JohnMachin Tidak ada yang peduli. Saya tidak peduli apa yang dimasukkan orang terbelakang omong kosong ke dalam RSS feeds, jika beberapa karakter tidak dalam ascii itu dapat dipotong. Masalah mereka. Saya hanya ingin python benar-benar mencekiknya dan menghadapinya, tidak memberi saya kesalahan setiap kali saya menentukan 'abaikan'. Siapa yang datang dengan omong kosong itu ?!
user1244215
10

Untuk konsol yang rusak seperti cmd.exedan output HTML Anda selalu dapat menggunakan:

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

Ini akan mempertahankan semua karakter non-ascii sambil membuatnya dicetak dalam ASCII murni dan dalam HTML.

PERINGATAN : Jika Anda menggunakan ini dalam kode produksi untuk menghindari kesalahan maka kemungkinan besar ada sesuatu yang salah dalam kode Anda . Satu-satunya kasus penggunaan yang valid untuk ini adalah mencetak ke konsol non-unicode atau konversi mudah ke entitas HTML dalam konteks HTML.

Dan akhirnya, jika Anda berada di windows dan menggunakan cmd.exe maka Anda dapat mengetik chcp 65001untuk mengaktifkan output utf-8 (berfungsi dengan font Konsol Lucida). Anda mungkin perlu menambahkan myUnicodeString.encode('utf8').

ccpizza
sumber
6

Anda menulis "" "Saya berasumsi itu berarti HTML berisi beberapa upaya salah unicode di suatu tempat." ""

HTML TIDAK diharapkan berisi segala jenis "upaya unicode", baik atau tidak. Pasti berisi karakter Unicode dikodekan dalam beberapa pengkodean, yang biasanya disediakan di depan ... cari "charset".

Anda tampaknya menganggap bahwa rangkaian karakter adalah UTF-8 ... atas dasar apa? Byte "\ xA0" yang ditampilkan dalam pesan kesalahan Anda menunjukkan bahwa Anda mungkin memiliki charset byte tunggal misalnya cp1252.

Jika Anda tidak bisa memahami pernyataan di awal HTML, coba gunakan chardet untuk mencari tahu apa yang dimaksud dengan pengkodean.

Mengapa Anda menandai pertanyaan Anda dengan "regex"?

Perbarui setelah Anda mengganti seluruh pertanyaan Anda dengan non-pertanyaan:

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) ;-)
John Machin
sumber
4

Jika Anda memiliki string line, Anda dapat menggunakan .encode([encoding], [errors='strict'])metode untuk string untuk mengonversi jenis penyandian.

line = 'my big string'

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

Untuk informasi lebih lanjut tentang penanganan ASCII dan unicode dengan Python, ini adalah situs yang sangat berguna: https://docs.python.org/2/howto/unicode.html

Jama22
sumber
1
Ini tidak berfungsi ketika Anda memiliki karakter non ascii seperti ü di string.
sajid
4

Saya pikir jawabannya ada tetapi hanya dalam potongan - potongan, yang membuatnya sulit untuk memperbaiki masalah seperti

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

Mari kita ambil contoh, Misalkan saya memiliki file yang memiliki beberapa data dalam bentuk berikut (berisi karakter ascii dan non-ascii)

1/10/17, 21:36 - Tanah: Selamat datang ��

dan kami ingin mengabaikan dan hanya melestarikan karakter ascii.

Kode ini akan berfungsi:

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

dan ketik (rline) akan memberi Anda

>type(rline) 
<type 'str'>
Somum
sumber
Ini juga berfungsi untuk kasus "extended ascii" (tidak standar)
Oliver Zendel
1
unicodestring = '\xa0'

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

Bekerja untukku

HimalayanCoder
sumber
-5

Sepertinya Anda menggunakan python 2.x. Python 2.x default ke ascii dan tidak tahu tentang Unicode. Karena itu pengecualian.

Cukup salin baris di bawah ini setelah shebang, itu akan berhasil

# -*- coding: utf-8 -*-
Haroon Rashedu
sumber
The codingkomentar bukan obat-semua sihir. Anda perlu tahu mengapa kesalahan dibuat, ini hanya memperbaiki hal-hal ketika ada karakter buruk di sumber Python Anda. Tampaknya tidak demikian halnya dengan pertanyaan ini.
Mark Ransom