Python sys.stdin抛出UnicodeDecodeError

时间:2016-01-20 02:19:03

标签: python-3.x encoding utf-8 sys

我尝试使用cURL和Python的BeautifulSoup库编写一个(非常)基本的Web爬虫(因为这比GNU awk和一堆正则表达式更容易理解)

目前,我正尝试使用cURL将网页内容传播到该程序(即curl http://www.example.com/ | ./parse-html.py

出于某种原因,Python因为起始字节无效而引发UnicodeDecodeError(我已查看this answerthis answer关于无效的起始字节,但未弄清楚如何从他们那里解决问题)。

具体来说,我尝试使用第一个答案中的a.encode('utf-8').split()。第二个答案只是解释了这个问题(Python找到了一个无效的起始字节),尽管它没有给出解决方案。

我尝试将cURL的输出重定向到文件(即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循环,但我遇到了同样的问题。

究竟是什么导致UnicodeDecodeError以及我该如何解决问题?

修改 通过将行html_doc = sys.stdin.readlines().encode('utf-8').strip()更改为html_doc = sys.stdin来解决问题

1 个答案:

答案 0 :(得分:3)

问题出在阅读期间,不是编码;输入资源根本不是用UTF-8编码的,而是另一种编码。在UTF-8 shell中,您可以使用

轻松重现问题
$ 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