首先请注意,u'\xc3\xa8'
是具有 2 个代码点的 python2 unicode 字符串,Ã
和 ¨
。接下来注意'\xc3\xa8'
是代表字符è
的utf8编码的python2字节str。所以 u'\xc3\xa8'
和 '\xc3\xa8'
,尽管看起来非常相似,但却是两个截然不同的野兽。
现在,如果我们尝试在浏览器中访问 https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premièr-cru-brocard-75cl
所有应该顺利。
如果我在 ipython session 中定义:
unicode_url = u'https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premièr-cru-brocard-75cl'
然后我可以打印它并看到我在浏览器的 URL 栏中输入的相同内容,太棒了。让我们尝试使用 python 请求获取它。
首先,我天真地只是尝试抛出 unicode url 以查看请求是否可以处理它:requests.get(unicode_url)
。不,404,没问题,URL 应该被编码,所以我尝试 requests.get(unicode_url.encode('utf8'))
。又不是404。没问题,也许我也需要做 URL 编码,所以我尝试 requests.get(urllib.quote(unicode_url.encode('utf8')))
....它不喜欢那根本。
不过,回想起我一开始提到的 unicode 和 byte str 对象的相似之处,我也尝试过:
requests.get('http://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premièr-cru-brocard-75cl')
令我惊讶的是它有效并成功给出了 200。
这里的请求是怎么回事?
编辑:就像另一个实验(这次在 Scrapy shell 中)
from scrapy.http import Request
unicode_url = u'https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premièr-cru-brocard-75cl'
fetch(Request(unicode_url))
绝对没问题!那么为什么 Scrapy 和浏览器可以毫无问题地处理它而不是 python 请求?以及为什么备用 url 在 python 请求中有效,但在浏览器或 Scrapy 中无效。
Latin1 与 UTF8
这也是事实
print unicode_url.encode('utf8').decode('latin1')
u'https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premièr-cru-brocard-75cl'
一般来说,我相信仅对于拉丁 unicode 字符来说,如果你有一个像 u'\xe8'
这样的 unicode str,那么你可以将它转换为相同形式的字节 str编码为 latin1,即 u'è'=u'\xe8'
和 u'\xe8'.encode('latin1') = '\xe8'
(对象右边是 latin1 中的字节 str 编码,其形式与表示 è
)
所以
In [95]: print u'è'.encode('utf8').decode('latin1')
è
同样,
In [94]: print u'è'.encode('latin1').decode('utf8')
è
不知是不是罪魁祸首
def prepare_url(self, url, params):
"""Prepares the given HTTP URL."""
#: Accept objects that have string representations.
#: We're unable to blindly call unicode/str functions
#: as this will include the bytestring indicator (b'')
#: on python 3.x.
#: https://github.com/kennethreitz/requests/pull/2238
if isinstance(url, bytes):
url = url.decode('utf8')
else:
url = unicode(url) if is_py2 else str(url)
来自 requests/models.py
。
最佳答案
问题是站点上的 URL 实际上使用 latin1 编码来表示“è”字符 - 出于某种原因,Python 2 请求库试图在创建之前“自动清理 url”调用,在 utf-8 中对“è”字符进行编码 - 这就是导致 404 错误的原因。
在调用 requests.get 之前尝试将 unicode_url 编码为 latin1 也无济于事 - 它试图在“清理”之前将其解码为 unicode,并且它们在无效的 utf=8 序列上出错,该序列是“è"使用 latin-1 时("\xe8"字符)。
在这一点上值得注意的是,使用 Python 3 的请求也完全没有问题——因为该语言自动处理文本,请求需要更少的文本编码来回跳动——在我第一次尝试使用 Python 3 时,我只是得到:
In [13]: requests.get(unicode_url)
Out[13]: <Response [200]>
现在,很难找到 Python 2.7 和请求的解决方法 - 无需猴子修补请求中的某些特定代码以使其做正确的事情。然而,即使在 Python2 中,使用手动编码为 latin-1 的 unicode_url,并使用 urllib.open
而不是 requests 也可以工作——如果你真的需要 Python 2,也许这是最适合你的方法:
In [28]: a = urllib.urlopen(unicode_url.encode("latin1"))
In [29]: a.code
Out[29]: 200
(真的 - 如果这只是您为某些特定工具所做的一些脚本,我建议您只切换到 Python 3.6 - 当您使用它时处理数据也会容易得多)
关于python - unicode 和 python 请求发生了一些有趣的事情,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43948639/