Въпрос за декодиране на Python urllib.request и utf8

Пиша прост Python CGI скрипт, който хваща уеб страница и показва HTML файла в уеб браузъра (действайки като прокси). Ето го скрипта:

#!/usr/bin/env python3.0

import urllib.request

site = "http://reddit.com/"
site = urllib.request.urlopen(site)
site = site.read()
site = site.decode('utf8')

print("Content-type: text/html\n\n")
print(site)

Този скрипт работи добре, когато се изпълнява от командния ред, но когато стигне до прегледа му с уеб браузър, показва празна страница. Ето грешката, която получавам в error_log на Apache:

Traceback (most recent call last):
  File "/home/public/projects/proxy/script.cgi", line 11, in <module>
    print(site)
  File "/usr/local/lib/python3.0/io.py", line 1491, in write
    b = encoder.encode(s)
  File "/usr/local/lib/python3.0/encodings/ascii.py", line 22, in encode
    return codecs.ascii_encode(input, self.errors)[0]
UnicodeEncodeError: 'ascii' codec can't encode character '\u2019' in position 33777: ordinal not in range(128)

person Corey Farwell    schedule 05.01.2011    source източник


Отговори (3)


Когато го отпечатате в командния ред, вие отпечатвате Unicode низ към терминала. Терминалът има кодиране, така че Python ще кодира вашия Unicode низ към това кодиране. Това ще работи добре.

Когато го използвате в CGI, в крайна сметка печатате на stdout, който няма кодиране. Следователно Python се опитва да кодира низа с ASCII. Това е неуспешно, тъй като ASCII не съдържа всички знаци, които се опитвате да отпечатате, така че получавате горната грешка.

Решението за това е да кодирате низа си в някакво кодиране (защо не UTF8?) и също да го кажете в заглавката.

Така че нещо като това:

sys.stdout.buffer.write(b"Content-type: text/html;encoding=UTF-8\n\n") # Not 100% sure about the spelling.
sys.stdout.buffer.write(site.encode('UTF8'))

Под Python 2 това също ще работи:

print("Content-type: text/html;encoding=UTF-8\n\n") # Not 100% sure about the spelling.
print(site.encode('UTF8'))

Но под Python 3 кодираните данни в байтове, така че няма да се отпечатат добре.

Разбира се, ще забележите, че сега първо декодирате от UTF8 и след това го кодирате отново. Не е нужно да правите това, строго погледнато. Но ако искате да промените HTML между тях, всъщност може да е добра идея да го направите и да запазите всички модификации в Unicode.

person Lennart Regebro    schedule 05.01.2011
comment
Опитах това. Освен всичко друго, той отпечатва: b'00004000\r\n преди началния таг ‹html›. Трябва ли да прави това? Ако не греша, това просто означава, че е байт код? - person Corey Farwell; 05.01.2011
comment
@Corey Farwell: О, вие използвате Python 3, не забелязах това. Моя грешка. Да, тогава не можете да го отпечатате, трябва да го напишете на stdout. Ще се актуализира. - person Lennart Regebro; 05.01.2011
comment
sys.stdout.buffer.write() не харесва Strings, така че първо трябва да кодирате Content-type в utf8 и след това да напишете и двете. Почти всичко работи с изключение на няколко реда (включително първия ред) на уеб страницата, който има „00004000“, а последният ред има „00000000“. Няма ли по-добър начин за това? Чувствам, че използването на stdout е просто хак. Улеснява ли wsgi това? - person Corey Farwell; 05.01.2011
comment
@Corey Farwell: Усещането е като хак, защото CGI е хак. :) WSGI все още не е стандартизиран за Python 3. - person Lennart Regebro; 05.01.2011

Възможно е сайтът, който се опитвате да отворите, да не е UTF-8 кодиран. Опитайте да предадете "iso-8859-1" на метода за декодиране.

person chris    schedule 05.01.2011
comment
Не, това ще му даде грешка при декодиране, а не грешка при кодиране. - person Lennart Regebro; 05.01.2011

Вместо да се борите с вътрешните елементи на sys.stdout, много по-лесно е уеб сървърът (1) да настрои променливата на CGI средата PYTHONIOENCODING (2) на UTF8.

За Apache2 ще трябва да разрешите зареждането на mod_env.so. В инсталация на Debian това се равнява на създаване на символна връзка в /etc/apache2/mods-enabled до /etc/apache2/mods-available/env.load и създаване на конфигурация /etc/apache2/conf-available/env.conf и символна връзка в /etc/apache2/conf-enabled към това, ако желаете да запазите структурата същата като при всички други зареждащи модули и конфигурации.

Съдържанието на файла env_mod.conf, който създадох, е:

<IfModule mod_env.c>
  SetEnv PYTHONIOENCODING UTF8
</IfModule>

Преди да направя това, моят скрипт съобщаваше, че sys.stdout.encoding е "ANSI ..." и извеждаше грешка при опит за отпечатване на низ, съдържащ Unicode знаци, след това беше "UTF8" и правилно изпращаше желания UTF-8 към браузъра.

(1) http://httpd.apache.org/docs/2.2/howto/cgi.html#env

(2) http://docs.python.org/3.3/library/sys.html#sys.stdin

person Bolan Meek    schedule 23.07.2013