Python sys.stdin 抛出 UnicodeDecodeError
Python sys.stdin throws a UnicodeDecodeError
我正在尝试使用 cURL 和 Python 的 BeautifulSoup
库编写一个(非常)基本的网络爬虫(因为这比 GNU awk 和一堆常规的混乱更容易理解表达式)。
目前,我正在尝试使用 cURL(即 curl http://www.example.com/ | ./parse-html.py
)将网页内容通过管道传输到程序
出于某种原因,Python 由于起始字节无效而抛出 UnicodeDecodeError
(我查看了 this answer and this 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
修复了问题
问题出在读取过程中,不是编码;输入资源根本不是用 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
object), examine it, and guess the encoding. The actual algorithm to do that is documented in the HTML standard.
然而,在许多情况下,编码并没有在文件本身中指定,而是通过 HTTP Content-Type
header. Unfortunately, your invocation of curl does not capture this header. Instead of using curl and Python, you can simply use Python only - it already can download URLs. Stealing 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。
我正在尝试使用 cURL 和 Python 的 BeautifulSoup
库编写一个(非常)基本的网络爬虫(因为这比 GNU awk 和一堆常规的混乱更容易理解表达式)。
目前,我正在尝试使用 cURL(即 curl http://www.example.com/ | ./parse-html.py
)将网页内容通过管道传输到程序
出于某种原因,Python 由于起始字节无效而抛出 UnicodeDecodeError
(我查看了 this answer and this 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
修复了问题
问题出在读取过程中,不是编码;输入资源根本不是用 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
object), examine it, and guess the encoding. The actual algorithm to do that is documented in the HTML standard.
然而,在许多情况下,编码并没有在文件本身中指定,而是通过 HTTP Content-Type
header. Unfortunately, your invocation of curl does not capture this header. Instead of using curl and Python, you can simply use Python only - it already can download URLs. Stealing 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。