unicode和python请求正在进行的有趣的事情

fpg*_*ost 5 python unicode utf-8 python-2.7 python-requests

首先请注意,它u'\xc3\xa8'是带有2个代码点的python2 unicode字符串,è。接下来要注意的'\xc3\xa8'是python2字节的str,它表示字符的utf8编码è。所以u'\xc3\xa8''\xc3\xa8',尽管看起来非常相似的是2个完全不同的野兽。

现在,如果我们尝试https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premièr-cru-brocard-75cl在浏览器中进行访问,一切应该会顺利进行。

如果我在ipython会话中定义:

unicode_url = u'https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premièr-cru-brocard-75cl'
Run Code Online (Sandbox Code Playgroud)

然后我可以打印它,并看到我输入到浏览器URL栏中的相同内容,很好。让我们尝试通过python请求获取它。

首先,我天真地尝试抛出unicode网址,以查看请求是否可以处理它: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')
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,它成功了,并获得了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))
Run Code Online (Sandbox Code Playgroud)

绝对没有问题!那么,为什么Scrapy和浏览器可以毫无问题地处理它,却不能处理python请求?以及为什么备用网址在python请求中有效,但在浏览器或Scrapy中却无效。

拉丁1 vs UTF8

也是这样

print unicode_url.encode('utf8').decode('latin1')
u'https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premièr-cru-brocard-75cl'
Run Code Online (Sandbox Code Playgroud)

通常,我相信这是真的,仅适用于拉丁Unicode字符,如果您具有Unicode字符串,u'\xe8'则可以通过编码为latin1将其转换为相同形式的字节字符串,即u'è'=u'\xe8'u'\xe8'.encode('latin1') = '\xe8'(右侧的对象为字节STR在latin1编码具有相同的形式作为表示Unicode代码点è

所以

In [95]: print u'è'.encode('utf8').decode('latin1')
è
Run Code Online (Sandbox Code Playgroud)

同样

In [94]: print u'è'.encode('latin1').decode('utf8')
è
Run Code Online (Sandbox Code Playgroud)

我想知道罪魁祸首是

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)
Run Code Online (Sandbox Code Playgroud)

来自requests/models.py

fpg*_*ost 0

我观察到一些奇怪的事情:

\n\n
In [1]: import requests\n\nIn [2]: s = requests.Session()\n\nIn [3]: unicode_url = u\'https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premi\xc3\xa8r-cru-brocard-75cl\'\n\nIn [4]: s.get(unicode_url)\nOut[4]: <Response [404]>\n\nIn [5]: s.get(unicode_url)\nOut[5]: <Response [200]>\n
Run Code Online (Sandbox Code Playgroud)\n\n

似乎第二次就可以在一个会话中工作了!

\n\n

可能可以说这实际上是关于cookie的。第一个请求没有 cookie,因此网络服务器 404s,同时仍然设置一些会话 cookie。下一个请求发送 cookie 和网络服务器 200。

\n\n

但是,请注意第二个请求现在不需要重定向;您可以将第二个请求替换为s.get(unicode_url, allow_redirects=False),但仍然会得到 200,而不是 302。而第一个请求会通过重定向链。因此,它现在起作用的唯一原因是通过 cookie 绕过了重定向。这表明编码问题发生在重定向链中的某个位置。

\n\n

注意:这与清除 cookie 的 Chrome 干净会话完全相同。如果您清除 cookie,然后转到该 url,则会出现 404。如果您重新输入并重试,则会出现 200,没有任何问题(cookie 是由您的第一个请求设置的,避免了导致 404 的麻烦重定向)

\n\n

还有一件事也很奇怪:

\n\n
In [11]:   requests.get(u\'http://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premi\xc3\x83\xc2\xa8r-cru-brocard-75cl\')\nOut[11]: <Response [200]>\n
Run Code Online (Sandbox Code Playgroud)\n\n

尽管没有cookie/会话。我很难理解这一点。在这种情况下,重定向的位置标头如下所示:

\n\n
 \'Location\':  \'http://www.sainsburys.co.uk/webapp/wcs/stores/servlet/gb/groceries/chablis/chablis-premi\\xc3\\xa8r-cru-brocard-75cl?langId=44&storeId=10151&krypto=dZB7Mt97QsHQQ%2BGMpb1iMZwdVfmbg%2BbRUdkh%2FciAItm7%2F4VSUi8NRUiszN3mSofKSCyAv%2F0QRKSsjhHzoo1x7in7Ctd4vzPIDIW5CcjiksLKE48%2BFU9nLNGkVzGj92PknAgP%2FmIFz63xpKhvPkxbJrtUmwi%2FUpbXNW9XIygHyTA%3D&ddkey=http%3Agb%2Fgroceries%2Fchablis%2Fchablis-premi%C3%83%C2%A8r-cru-brocard-75cl\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

即我们有 utf8 编码的 u\'\xc3\xa8\' 而不是 latin1 编码。

\n\n

概括

\n\n

鉴于第一个请求(实际上没有 cookie 并依赖于重定向的请求)在我尝试过的每个平台(Chrome、Scrapy、python-requests)中都失败了,我会将其归因于主机服务器本身的错误。它在重定向中对其位置标头进行编码,但当浏览器实际请求重定向位置 URL 时,需要 utf8 编码的 URL 和 404,因为服务器实际上需要 utf8 编码的 URL。它实际上应该是 utf8 对其重定向响应的位置标头进行编码,以便与其使用的 URL 编码保持一致。

\n\n

这就是为什么当你作弊并使用时,u\'http://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premi\xc3\x83\xc2\xa8r-cru-brocard-75cl\'你实际上会在重定向中得到正确的 utf8 编码位置标头,因为u\'https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premi\xc3\x83\xc2\xa8r-cru-brocard-75cl\'.encode(\'latin1\')is \'https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premi\\xc3\\xa8r-cru-brocard-75cl\',它恰好是 的正确 utf8 编码字节 str u\'https://www.sainsburys.co.uk/shop/gb/groceries/chablis/chablis-premi\xc3\xa8r-cru-brocard-75cl\',所以当浏览器重定向时它可以工作。

\n\n

如果您已经通过访问 URL 或网站上的其他位置设置了 cookie,则可以避免重定向的需要,并避免中断的重定向过程。

\n\n

另请参阅https://github.com/kennethreitz/requests/blob/eae38b8d131e8b51c3daf3583e69879d1c02f9a4/requests/sessions.py#L101-L114了解其在 python3 请求中的工作原理。

\n