新浪微博API OAuth 2 Python客户端
来源:互联网 发布:apache auth身份认证 编辑:程序博客网 时间:2024/05/11 18:51
最新源码
https://github.com/michaelliao/sinaweibopy/blob/master/weibo.py
源码如下:
#!/usr/bin/env python# -*- coding: utf-8 -*-__version__ = '1.1.4'__author__ = 'Liao Xuefeng (askxuefeng@gmail.com)''''Python client SDK for sina weibo API using OAuth 2.'''try: from cStringIO import StringIOexcept ImportError: from StringIO import StringIOimport gzip, time, json, hmac, base64, hashlib, urllib, urllib2, logging, mimetypes, collectionsclass APIError(StandardError): ''' raise APIError if receiving json message indicating failure. ''' def __init__(self, error_code, error, request): self.error_code = error_code self.error = error self.request = request StandardError.__init__(self, error) def __str__(self): return 'APIError: %s: %s, request: %s' % (self.error_code, self.error, self.request)def _parse_json(s): ' parse str into JsonDict ' def _obj_hook(pairs): ' convert json object to python object ' o = JsonDict() for k, v in pairs.iteritems(): o[str(k)] = v return o return json.loads(s, object_hook=_obj_hook)class JsonDict(dict): ' general json object that allows attributes to be bound to and also behaves like a dict ' def __getattr__(self, attr): try: return self[attr] except KeyError: raise AttributeError(r"'JsonDict' object has no attribute '%s'" % attr) def __setattr__(self, attr, value): self[attr] = valuedef _encode_params(**kw): ''' do url-encode parameters >>> _encode_params(a=1, b='R&D') 'a=1&b=R%26D' >>> _encode_params(a=u'\u4e2d\u6587', b=['A', 'B', 123]) 'a=%E4%B8%AD%E6%96%87&b=A&b=B&b=123' ''' args = [] for k, v in kw.iteritems(): if isinstance(v, basestring): qv = v.encode('utf-8') if isinstance(v, unicode) else v args.append('%s=%s' % (k, urllib.quote(qv))) elif isinstance(v, collections.Iterable): for i in v: qv = i.encode('utf-8') if isinstance(i, unicode) else str(i) args.append('%s=%s' % (k, urllib.quote(qv))) else: qv = str(v) args.append('%s=%s' % (k, urllib.quote(qv))) return '&'.join(args)def _encode_multipart(**kw): ' build a multipart/form-data body with randomly generated boundary ' boundary = '----------%s' % hex(int(time.time() * 1000)) data = [] for k, v in kw.iteritems(): data.append('--%s' % boundary) if hasattr(v, 'read'): # file-like object: filename = getattr(v, 'name', '') content = v.read() data.append('Content-Disposition: form-data; name="%s"; filename="hidden"' % k) data.append('Content-Length: %d' % len(content)) data.append('Content-Type: %s\r\n' % _guess_content_type(filename)) data.append(content) else: data.append('Content-Disposition: form-data; name="%s"\r\n' % k) data.append(v.encode('utf-8') if isinstance(v, unicode) else v) data.append('--%s--\r\n' % boundary) return '\r\n'.join(data), boundarydef _guess_content_type(url): n = url.rfind('.') if n==(-1): return 'application/octet-stream' ext = url[n:] return mimetypes.types_map.get(ext, 'application/octet-stream')_HTTP_GET = 0_HTTP_POST = 1_HTTP_UPLOAD = 2def _http_get(url, authorization=None, **kw): logging.info('GET %s' % url) return _http_call(url, _HTTP_GET, authorization, **kw)def _http_post(url, authorization=None, **kw): logging.info('POST %s' % url) return _http_call(url, _HTTP_POST, authorization, **kw)def _http_upload(url, authorization=None, **kw): logging.info('MULTIPART POST %s' % url) return _http_call(url, _HTTP_UPLOAD, authorization, **kw)def _read_body(obj): using_gzip = obj.headers.get('Content-Encoding', '')=='gzip' body = obj.read() if using_gzip: gzipper = gzip.GzipFile(fileobj=StringIO(body)) fcontent = gzipper.read() gzipper.close() return fcontent return bodydef _http_call(the_url, method, authorization, **kw): ''' send an http request and return a json object if no error occurred. ''' params = None boundary = None if method==_HTTP_UPLOAD: # fix sina upload url: the_url = the_url.replace('https://api.', 'https://upload.api.') params, boundary = _encode_multipart(**kw) else: params = _encode_params(**kw) if '/remind/' in the_url: # fix sina remind api: the_url = the_url.replace('https://api.', 'https://rm.api.') http_url = '%s?%s' % (the_url, params) if method==_HTTP_GET else the_url http_body = None if method==_HTTP_GET else params req = urllib2.Request(http_url, data=http_body) req.add_header('Accept-Encoding', 'gzip') if authorization: req.add_header('Authorization', 'OAuth2 %s' % authorization) if boundary: req.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boundary) try: resp = urllib2.urlopen(req, timeout=5) body = _read_body(resp) r = _parse_json(body) if hasattr(r, 'error_code'): raise APIError(r.error_code, r.get('error', ''), r.get('request', '')) return r except urllib2.HTTPError, e: try: r = _parse_json(_read_body(e)) except: r = None if hasattr(r, 'error_code'): raise APIError(r.error_code, r.get('error', ''), r.get('request', '')) raise eclass HttpObject(object): def __init__(self, client, method): self.client = client self.method = method def __getattr__(self, attr): def wrap(**kw): if self.client.is_expires(): raise APIError('21327', 'expired_token', attr) return _http_call('%s%s.json' % (self.client.api_url, attr.replace('__', '/')), self.method, self.client.access_token, **kw) return wrapclass APIClient(object): ''' API client using synchronized invocation. ''' def __init__(self, app_key, app_secret, redirect_uri=None, response_type='code', domain='api.weibo.com', version='2'): self.client_id = str(app_key) self.client_secret = str(app_secret) self.redirect_uri = redirect_uri self.response_type = response_type self.auth_url = 'https://%s/oauth2/' % domain self.api_url = 'https://%s/%s/' % (domain, version) self.access_token = None self.expires = 0.0 self.get = HttpObject(self, _HTTP_GET) self.post = HttpObject(self, _HTTP_POST) self.upload = HttpObject(self, _HTTP_UPLOAD) def parse_signed_request(self, signed_request): ''' parse signed request when using in-site app. Returns: dict object like { 'uid': 12345, 'access_token': 'ABC123XYZ', 'expires': unix-timestamp }, or None if parse failed. ''' def _b64_normalize(s): appendix = '=' * (4 - len(s) % 4) return s.replace('-', '+').replace('_', '/') + appendix sr = str(signed_request) logging.info('parse signed request: %s' % sr) enc_sig, enc_payload = sr.split('.', 1) sig = base64.b64decode(_b64_normalize(enc_sig)) data = _parse_json(base64.b64decode(_b64_normalize(enc_payload))) if data['algorithm'] != u'HMAC-SHA256': return None expected_sig = hmac.new(self.client_secret, enc_payload, hashlib.sha256).digest(); if expected_sig==sig: data.user_id = data.uid = data.get('user_id', None) data.access_token = data.get('oauth_token', None) expires = data.get('expires', None) if expires: data.expires = data.expires_in = time.time() + expires return data return None def set_access_token(self, access_token, expires): self.access_token = str(access_token) self.expires = float(expires) def get_authorize_url(self, redirect_uri=None, **kw): ''' return the authorization url that the user should be redirected to. ''' redirect = redirect_uri if redirect_uri else self.redirect_uri if not redirect: raise APIError('21305', 'Parameter absent: redirect_uri', 'OAuth2 request') response_type = kw.pop('response_type', 'code') return '%s%s?%s' % (self.auth_url, 'authorize', \ _encode_params(client_id = self.client_id, \ response_type = response_type, \ redirect_uri = redirect, **kw)) def _parse_access_token(self, r): ''' new:return access token as a JsonDict: {"access_token":"your-access-token","expires_in":12345678,"uid":1234}, expires_in is represented using standard unix-epoch-time ''' current = int(time.time()) expires = r.expires_in + current remind_in = r.get('remind_in', None) if remind_in: rtime = int(remind_in) + current if rtime < expires: expires = rtime return JsonDict(access_token=r.access_token, expires=expires, expires_in=expires, uid=r.get('uid', None)) def request_access_token(self, code, redirect_uri=None): redirect = redirect_uri if redirect_uri else self.redirect_uri if not redirect: raise APIError('21305', 'Parameter absent: redirect_uri', 'OAuth2 request') r = _http_post('%s%s' % (self.auth_url, 'access_token'), \ client_id = self.client_id, \ client_secret = self.client_secret, \ redirect_uri = redirect, \ code = code, grant_type = 'authorization_code') return self._parse_access_token(r) def refresh_token(self, refresh_token): req_str = '%s%s' % (self.auth_url, 'access_token') r = _http_post(req_str, \ client_id = self.client_id, \ client_secret = self.client_secret, \ refresh_token = refresh_token, \ grant_type = 'refresh_token') return self._parse_access_token(r) def is_expires(self): return not self.access_token or time.time() > self.expires def __getattr__(self, attr): if '__' in attr: return getattr(self.get, attr) return _Callable(self, attr)_METHOD_MAP = { 'GET': _HTTP_GET, 'POST': _HTTP_POST, 'UPLOAD': _HTTP_UPLOAD }class _Executable(object): def __init__(self, client, method, path): self._client = client self._method = method self._path = path def __call__(self, **kw): method = _METHOD_MAP[self._method] if method==_HTTP_POST and 'pic' in kw: method = _HTTP_UPLOAD return _http_call('%s%s.json' % (self._client.api_url, self._path), method, self._client.access_token, **kw) def __str__(self): return '_Executable (%s %s)' % (self._method, self._path) __repr__ = __str__class _Callable(object): def __init__(self, client, name): self._client = client self._name = name def __getattr__(self, attr): if attr=='get': return _Executable(self._client, 'GET', self._name) if attr=='post': return _Executable(self._client, 'POST', self._name) name = '%s/%s' % (self._name, attr) return _Callable(self._client, name) def __str__(self): return '_Callable (%s)' % self._name __repr__ = __str__if __name__=='__main__': import doctest doctest.testmod()
使用简介
注册微博App后,可以获得app key和app secret,然后定义网站回调地址:
from weibo import APIClientAPP_KEY = '1234567' # app keyAPP_SECRET = 'abcdefghijklmn' # app secretCALLBACK_URL = 'http://www.example.com/callback' # callback url
在网站放置“使用微博账号登录”的链接,当用户点击链接后,引导用户跳转至如下地址:
client = APIClient(app_key=APP_KEY, app_secret=APP_SECRET, redirect_uri=CALLBACK_URL)url = client.get_authorize_url()# TODO: redirect to url
用户授权后,将跳转至网站回调地址,并附加参数code=abcd1234:
# 获取URL参数code:code = your.web.framework.request.get('code')client = APIClient(app_key=APP_KEY, app_secret=APP_SECRET, redirect_uri=CALLBACK_URL)r = client.request_access_token(code)access_token = r.access_token # 新浪返回的token,类似abc123xyz456expires_in = r.expires_in # token过期的UNIX时间:http://zh.wikipedia.org/wiki/UNIX%E6%97%B6%E9%97%B4# TODO: 在此可保存access tokenclient.set_access_token(access_token, expires_in)
然后,可调用任意API:
print client.statuses.user_timeline.get()print client.statuses.update.post(status=u'测试OAuth 2.0发微博')print client.statuses.upload.post(status=u'测试OAuth 2.0带图片发微博', pic=open('/Users/michael/test.png'))
API调用规则
首先查看新浪微博API文档,例如:
API:statuses/user_timeline
请求格式:GET
请求参数:
source:string,采用OAuth授权方式不需要此参数,其他授权方式为必填参数,数值为应用的AppKey?。
access_token:string,采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得。
uid:int64,需要查询的用户ID。
screen_name:string,需要查询的用户昵称。
(其它可选参数略)
调用方法:将API的“/”变为“.”,根据请求格式是GET或POST,调用get()或post()并传入关键字参数,但不包括source和access_token参数:
r = client.statuses.user_timeline.get(uid=123456)for st in r.statuses: print st.text
若为POST调用,则示例代码如下:
r = client.statuses.update.post(status=u'测试OAuth 2.0发微博')
若需要上传文件,传入file-like object参数,示例代码如下:
f = open('/Users/michael/test.png', 'rb')r = client.statuses.upload.post(status=u'测试OAuth 2.0带图片发微博', pic=f)f.close() # APIClient不会自动关闭文件,需要手动关闭
请注意:上传的文件必须是file-like object,不能是str,因为无法区分一个str是文件还是字段。可以通过StringIO把一个str包装成file-like object。
站内应用
站内应用授权不能作URL跳转,而是由新浪微博POST用户信息至iFrame,需要在iFrame的页面中处理用户信息。
假定iFrame的入口地址是http://app.example.com/index,则新浪微博会POST数据至该URL。
第一步,获取POST数据:
signed_request = your.web.framework.get('signed_request')client = APIClient(APP_ID, APP_SECRET, 'http://app.example.com/callback')data = client.parse_signed_request(signed_request)
第二步,判断用户是否授权,如未授权,返回授权页面:
user_id = data.get('uid', '')auth_token = data.get('oauth_token', '')if not user_id or not auth_token: return Template('auth.html')
授权页auth.html可以是静态页面,需要调用新浪的JS:
<html><head> <title>未授权时的页面</title> <script src="http://tjs.sjs.sinajs.cn/t35/apps/opent/js/frames/client.js"></script></head><script>function authLoad() { App.AuthDialog.show({ client_id: 'YOUR_CLIENT_ID', redirect_uri: 'http://apps.weibo.com/YOUR_APP_ID', height: 40});}</script><body onload="authLoad();"></body></html>
第三步,如果用户已授权,则可直接获得OAuth token:
user_id = data.get('uid', '')auth_token = data.get('oauth_token', '')if not user_id or not auth_token: return Template('auth.html')expires = data.expiresclient.set_access_token(auth_token, expires)
现在,已获取到用户的auth_token和expires,可以按照API进行正常调用。
使用限制
仅支持Web方式调用,不支持口令方式验证。
补充说明
所有API调用均为动态调用,支持链式调用,需要根据新浪API文档由HTTP调用方式(GET,POST)决定APIClient的链式方法名(如statuses.user_timeline)和最后调用的方法是get()还是post(),以及关键字参数。
若调用出错,会抛出APIError异常,该异常包含error_code,error和request三个字段,与新浪返回的出错json对应。具体错误原因请查询新浪文档。
若HTTP响应出错(例如404),会抛出urllib2.HTTPError异常。
- 新浪微博API OAuth 2 Python客户端
- 新浪微博API客户端OAuth认证直接获取pin
- 新浪微博API客户端OAuth认证直接获取pin
- 新浪微博python下api的OAuth登陆
- 新浪微博python下api的OAuth登陆
- 使用Python编写客户端 上传文字or图片至新浪微博 by OAuth 2.0
- 新浪微博API OAuth2 python客户端改进
- 新浪微博Android客户端开发之OAuth认证篇
- 新浪微博Oauth授权及API的使用
- 利用ASIHTTPRequest 发送数据到(新浪微博)以及新浪API Oauth认证
- 利用ASIHTTPRequest 发送数据到(新浪微博)以及新浪API Oauth认证
- 新浪微博OAUTH方法
- 新浪微博 oAuth 解决方案
- 新浪微博Oauth认证
- 新浪微博OAuth认证
- 新浪微博OAuth认证
- 新浪微博API OAuth1 Python3客户端
- 新浪微博API OAuth1 Python3客户端
- ae中像ps的变形工具
- tomcat 远程调试
- [Java]无脑理解JAVA策略模式
- 无法嵌入互操作类型“Excel.ApplicationClass”。请改用适用的接口
- 小记
- 新浪微博API OAuth 2 Python客户端
- 浅述NoSQl之Redis+内部机制
- android修改tab 导航 指示器颜色
- svn安装配置
- MyBatis学习 之 二、SQL语句映射文件(2)增删改查、参数、缓存
- 隐马尔可夫模型及的评估和解码问题
- 记第五届山东省ACM程序设计比赛——遗憾并不是遗憾
- 百度拼音---基于国内最大搜索引擎百度带来的精确词库打字体验
- c语言之输入一个数并判断它是几位数