Python urllib.request и вопрос декодирования utf8 - PullRequest
2 голосов
/ 05 января 2011

Я пишу простой скрипт 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)

Ответы [ 3 ]

5 голосов
/ 05 января 2011

Когда вы печатаете его в командной строке, вы выводите строку Unicode на терминал. Терминал имеет кодировку, поэтому Python закодирует вашу строку Unicode в эту кодировку. Это будет хорошо работать.

Когда вы используете его в CGI, вы в конечном итоге печатаете на стандартный вывод, у которого нет кодировки. Поэтому 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.

1 голос
/ 05 января 2011

Возможно, сайт, который вы пытаетесь открыть, не имеет кодировки UTF-8Попробуйте передать "iso-8859-1" в метод декодирования.

0 голосов
/ 24 июля 2013

Вместо того, чтобы бороться с внутренностями 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

...