2016-01-20 2 views
2

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

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

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

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

Я попытался перенаправив вывод завитка в файл (то есть, curl http://www.example.com/ > foobar.html и внесения изменений в программу, чтобы принять файл в качестве аргумента командной строки, хотя это приводит к тому же UnicodeDecodeError.

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

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

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

РЕДАКТИРОВАТЬ: Изменяя линию html_doc = sys.stdin.readlines().encode('utf-8').strip() к html_doc = sys.stdin устраняет проблему

ответ

0

Проблема в том, во время чтения, не кодирования; входной ресурс просто не кодируется с помощью 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 объект), изучить его, и думаю, кодирование. Фактический алгоритм для этого is documented in the HTML standard.

Однако во многих случаях кодирование не указывается в самом файле, а через HTTP Content-Type header. К сожалению, ваш вызов curl не захватывает этот заголовок. Вместо использования curl и Python, вы можете просто использовать только Python - это уже can download URLs. Кража the encoding detection algorithm from 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')) 

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

Я также рекомендую вам прочитать на basics of what encodings are.