Python urllib2,基本HTTP身份验证和tr.im

87

我正在尝试编写一些代码,使用 tr.im APIs 来缩短URL。

在阅读了http://docs.python.org/library/urllib2.html之后,我尝试了以下代码:

   TRIM_API_URL = 'http://api.tr.im/api'
   auth_handler = urllib2.HTTPBasicAuthHandler()
   auth_handler.add_password(realm='tr.im',
                             uri=TRIM_API_URL,
                             user=USERNAME,
                             passwd=PASSWORD)
   opener = urllib2.build_opener(auth_handler)
   urllib2.install_opener(opener)
   response = urllib2.urlopen('%s/trim_simple?url=%s'
                              % (TRIM_API_URL, url_to_trim))
   url = response.read().strip()

响应代码是 200(我认为应该是 202)。URL 是有效的,但基本的 HTTP 身份验证似乎没有起作用,因为缩短的 URL 不在我的 URL 列表中(位于 http://tr.im/?page=1)。

阅读完http://www.voidspace.org.uk/python/articles/authentication.shtml#doing-it-properly后,我还尝试了:

   TRIM_API_URL = 'api.tr.im/api'
   password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
   password_mgr.add_password(None, TRIM_API_URL, USERNAME, PASSWORD)
   auth_handler = urllib2.HTTPBasicAuthHandler(password_mgr)
   opener = urllib2.build_opener(auth_handler)
   urllib2.install_opener(opener)
   response = urllib2.urlopen('http://%s/trim_simple?url=%s'
                              % (TRIM_API_URL, url_to_trim))
   url = response.read().strip()

但我得到的结果是一样的。(响应代码为200,网址有效, 但未记录在http://tr.im/我的账户中。)

如果我使用查询字符串参数而不是基本的HTTP身份验证,像这样:

   TRIM_API_URL = 'http://api.tr.im/api'
   response = urllib2.urlopen('%s/trim_simple?url=%s&username=%s&password=%s'
                              % (TRIM_API_URL,
                                 url_to_trim,
                                 USERNAME,
                                 PASSWORD))
   url = response.read().strip()

如果url是有效的并且已记录在我的tr.im账户中,那么响应代码仍然为200。

不过,由于我的代码有问题(而不是tr.im的API有问题),因此出现了这种情况。

$ curl -u yacitus:xxxx http://api.tr.im/api/trim_url.json?url=http://www.google.co.uk

...返回:

{"trimpath":"hfhb","reference":"nH45bftZDWOX0QpVojeDbOvPDnaRaJ","trimmed":"11\/03\/2009","destination":"http:\/\/www.google.co.uk\/","trim_path":"hfhb","domain":"google.co.uk","url":"http:\/\/tr.im\/hfhb","visits":0,"status":{"result":"OK","code":"200","message":"tr.im URL Added."},"date_time":"2009-03-11T10:15:35-04:00"}

...而且这个URL确实出现在我在http://tr.im/?page=1上的URL列表中。

如果我运行:

$ curl -u yacitus:xxxx http://api.tr.im/api/trim_url.json?url=http://www.google.co.uk

...再次运行,我得到以下结果:

{"trimpath":"hfhb","reference":"nH45bftZDWOX0QpVojeDbOvPDnaRaJ","trimmed":"11\/03\/2009","destination":"http:\/\/www.google.co.uk\/","trim_path":"hfhb","domain":"google.co.uk","url":"http:\/\/tr.im\/hfhb","visits":0,"status":{"result":"OK","code":"201","message":"tr.im URL Already Created [yacitus]."},"date_time":"2009-03-11T10:15:35-04:00"}

注意,代码是201,消息是“tr.im URL已经被创建[yacitus]”。

我可能没有正确执行基本的HTTP身份验证(在任何一次尝试中)。你能发现我的问题吗?也许我应该查看一下发送到网络的内容?我以前从未这样做过。是否有可以使用的Python API(也许在pdb中)?或者是否有其他工具(最好是适用于Mac OS X)可供使用?


2
只有在urllib2(或httplib2)发送您的凭据之前,站点必须返回“WWW-Authenticate”和代码401。[请参见我下面的答案](https://dev59.com/P3RB5IYBdhLWcg3wZmhz#9698319)。 - Mark Mikofski
注意:此服务似乎已停止运作。 - Laurel
7个回答

247

这似乎非常有效(引用自另一个线程)

import urllib2, base64

request = urllib2.Request("http://api.foursquare.com/v1/user")
base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
request.add_header("Authorization", "Basic %s" % base64string)   
result = urllib2.urlopen(request)

7
使用 base64.standard_b64encode 替代 base64.encodestring 和 replace 函数。 - Paweł Polewicz
5
请求添加头信息:'Authorization': 'Basic ' + base64.b64encode(username + b':' + password)。其中,usernamepassword 是变量,需要根据具体情况进行替换。 - jfs
1
基于这个答案,我创建了一个名为 urllib2_prior_auth 的包,它没有任何 stdlib 之外的依赖,并且我尝试将相关更改推送到 stdlib 中(http://bugs.python.org/issue19494)。 - mcepl
5
甚至更简短/避免导入:request.add_header('Authorization', b'Basic ' + (username + b':' + password).encode('base64')) - makapuf

20

非常便宜的解决方案:

urllib.urlopen('http://user:xxxx@api.tr.im/api')

(你可能会认为它不适合出于各种原因,例如URL的安全性)

Github API示例

>>> import urllib, json
>>> result = urllib.urlopen('https://personal-access-token:x-oauth-basic@api.github.com/repos/:owner/:repo')
>>> r = json.load(result.fp)
>>> result.close()

使用这种方法是否比使用查询字符串参数有任何优势? - Daryl Spitzer
1
Daryl:如果它能够正常工作,我会说这是一个优势,而且可能比查询字符串参数更安全,因为大多数HTTP客户端在处理它们时会更加小心。 - Ali Afshar
我可能会选择这个(所以你会得到我的投票),但我仍然想弄清楚我的代码有什么问题(所以这不会成为我的采纳答案)。 - Daryl Spitzer
37
出现错误...InvalidURL:非数字端口:'xxxx@api.tr.im/api'。 - Nick Bolton
5
请确保您没有使用urllib2.urlopen(url)。 - CantGetANick

14

请查看 这篇SO帖子的答案,并查看来自urllib2缺失手册基本认证教程

为了使urllib2基本认证起作用,HTTP响应必须包含HTTP代码401 Unauthorized一个键"WWW-Authenticate",其值为"Basic",否则Python将不会发送您的登录信息,您将需要使用Requests或在url中使用urllib.urlopen(url)与您的登录,或者像@Flowpoke's 答案中添加头文件。

您可以通过将urlopen放入try块中来查看错误:

try:
    urllib2.urlopen(urllib2.Request(url))
except urllib2.HTTPError, e:
    print e.headers
    print e.headers.has_key('WWW-Authenticate')

这对我很有帮助,因为打印标题让我意识到我在身份验证领域中打错了字。+1 - freespace

10

推荐的方式是使用requests模块

#!/usr/bin/env python
import requests # $ python -m pip install requests
####from pip._vendor import requests # bundled with python

url = 'https://httpbin.org/hidden-basic-auth/user/passwd'
user, password = 'user', 'passwd'

r = requests.get(url, auth=(user, password)) # send auth unconditionally
r.raise_for_status() # raise an exception if the authentication fails

这里是一个单一源代码,兼容Python 2/3的urllib2变体:

#!/usr/bin/env python
import base64
try:
    from urllib.request import Request, urlopen
except ImportError: # Python 2
    from urllib2 import Request, urlopen

credentials = '{user}:{password}'.format(**vars()).encode()
urlopen(Request(url, headers={'Authorization': # send auth unconditionally
    b'Basic ' + base64.b64encode(credentials)})).close()

Python 3.5+ 引入了HTTPPasswordMgrWithPriorAuth(),它可以:

消除不必要的401响应处理,或者在首次请求时无条件发送凭据,以便与返回404响应而不是401响应的服务器进行通信(如果未发送Authorization标头)。

#!/usr/bin/env python3
import urllib.request as urllib2

password_manager = urllib2.HTTPPasswordMgrWithPriorAuth()
password_manager.add_password(None, url, user, password,
                              is_authenticated=True) # to handle 404 variant
auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)
opener = urllib2.build_opener(auth_manager)

opener.open(url).close()

在这种情况下,如果需要,可以轻松地使用ProxyBasicAuthHandler()代替HTTPBasicAuthHandler()


HTTPPasswordMgrWithPriorAuthis_authenticated=True 是关键!!! - Mark

4
我建议采用我的软件包 urllib2_prior_auth,该软件包可以很好地解决这个问题(我正在为将其纳入标准库而努力,详情请见)。

1
它已经被包含在Python 3.5中,作为urrlib.request.HTTPBasicPriorAuthHandler - mcepl

3
Python urllib2基本身份验证问题相同的解决方案适用。
请参见https://dev59.com/W3E95IYBdhLWcg3wN7Ov#24048852;您可以对urllib2.HTTPBasicAuthHandler进行子类化,以向每个匹配已知网址的请求添加Authorization头。
class PreemptiveBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
    '''Preemptive basic auth.

    Instead of waiting for a 403 to then retry with the credentials,
    send the credentials if the url is handled by the password manager.
    Note: please use realm=None when calling add_password.'''
    def http_request(self, req):
        url = req.get_full_url()
        realm = None
        # this is very similar to the code from retry_http_basic_auth()
        # but returns a request object.
        user, pw = self.passwd.find_user_password(realm, url)
        if pw:
            raw = "%s:%s" % (user, pw)
            auth = 'Basic %s' % base64.b64encode(raw).strip()
            req.add_unredirected_header(self.auth_header, auth)
        return req

    https_request = http_request

b64encode 之后调用 strip 是不是多余的? - Mihai Todor

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接