Python sys.stdin выдает ошибку UnicodeDecodeError

Я пытаюсь написать (очень) простой веб-сканер, используя cURL и библиотеку Python BeautifulSoup (поскольку это намного проще понять, чем GNU awk и беспорядок регулярных выражений).

В настоящее время я пытаюсь передать содержимое веб-страницы в программу с помощью cURL (т.е. curl http://www.example.com/ | ./parse-html.py)

По какой-то причине Python выдает UnicodeDecodeError из-за недопустимого начального байта (я просмотрел этот ответ и этот ответ о недопустимых стартовых байтах, но не понял, как решить проблему из них).

В частности, я пытался использовать a.encode('utf-8').split() из первого ответа. Второй ответ просто объяснил проблему (что Python нашел недопустимый начальный байт), хотя и не дал решения.

Я попытался перенаправить вывод cURL в файл (т.

Я проверил, и вывод locale charmap равен UTF-8, что, насколько я знаю, означает, что моя система кодирует символы в UTF-8 (что меня особенно смущает в этом UnicodeDecodeError.

На данный момент точная строка, вызывающая ошибку, — html_doc = sys.stdin.readlines().encode('utf-8').strip(). Я попытался переписать это как цикл for, хотя у меня возникла та же проблема.

Что именно вызывает UnicodeDecodeError и как мне решить эту проблему?

EDIT: изменение строки html_doc = sys.stdin.readlines().encode('utf-8').strip() на html_doc = sys.stdin устраняет проблему.


person Charles German    schedule 20.01.2016    source источник


Ответы (1)


Проблема во время чтения, не кодирования; входной ресурс просто закодирован не UTF-8, а другой кодировкой. В оболочке UTF-8 вы можете легко воспроизвести проблему с помощью

$ echo 2¥ | iconv -t iso8859-1 | python3 -c 'import sys;sys.stdin.readline()'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python3.5/codecs.py", line 321, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa5 in position 1: invalid start byte

Вы можете прочитать файл (sys.stdin.buffer.read() или with open(..., 'rb') as f: f.read()) как двоичный (вы получите bytes объект), исследуйте его и угадайте кодировку. Фактический алгоритм для этого задокументирован в стандарте HTML.

Однако во многих случаях кодировка указывается не в самом файле, а через HTTP Content-Type заголовок. К сожалению, ваш вызов curl не фиксирует этот заголовок. Вместо использования curl и Python вы можете просто использовать только Python — он уже может загружать URL-адреса. Кража алгоритма обнаружения кодировки из youtube-dl, мы получаем что-то вроде:

import re
import urllib.request


def guess_encoding(content_type, webpage_bytes):
    m = re.match(
        r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset="?([a-zA-Z0-9_-]+)"?',
        content_type)
    if m:
        encoding = m.group(1)
    else:
        m = re.search(br'<meta[^>]+charset=[\'"]?([a-zA-Z0-9_-]+)[ /\'">]',
                      webpage_bytes[:1024])
        if m:
            encoding = m.group(1).decode('ascii')
        elif webpage_bytes.startswith(b'\xff\xfe'):
            encoding = 'utf-16'
        else:
            encoding = 'utf-8'

    return encoding


def download_html(url):
    with urllib.request.urlopen(url) as urlh:
        content = urlh.read()
        encoding = guess_encoding(urlh.getheader('Content-Type'), content)
        return content.decode(encoding)

print(download_html('https://phihag.de/2016/iso8859.php'))

Есть также некоторые библиотеки (хотя и не в стандартной библиотеке), которые поддерживают это из коробки, а именно запросы.

Я также рекомендую вам ознакомиться с основами кодировок.

person phihag    schedule 20.01.2016