关于作者

使用新浪微博API:创建SDK

廖雪峰 / 编程 / ... / Reads: 607

新浪微博无疑是中国最流行的微博社区,和国外流行的tweeter类似,新浪微博也提供了一整套完善的REST API接口。借助新浪微博的开放API,您可以很容易地将自己的网站接入新浪微博,让数以亿计的新浪微博用户直接登录您的网站而不必注册,您还可以借助新浪微博的开放API获取到用户发表的微博、好友关系等社交信息,从而为用户提供更具个性化的服务。

本文将为您演示如何通过新浪微博API接入您的网站,实现发布和获取微博等基本社交功能。在此之前,我们会首先讨论几个基本概念。

新浪微博API简介

新浪微博不仅通过网站让用户能直接阅读和发布微博,它也提供了一整套比较完善的REST API。REST是Representational State Transfer的缩写,本文不打算详细讨论REST的概念,您可以简单理解一个REST服务是通过标准的HTTP请求和响应来完成资源的访问,比如,我们希望获取某个用户发布的最新的微博消息,该REST API的URL地址:

https://api.weibo.com/2/statuses/user_timeline.json

向以上地址发送标准的HTTP GET请求,并在URL参数中填入相应的参数,就可以从该HTTP响应中获得最新的微博消息,加上参数的URL地址:

https://api.weibo.com/2/statuses/user_timeline.json?uid=1642634100&source=939120710

得到的响应如下(省略了部分内容):

{"statuses":[{"created_at":"Thu Nov 15 16:10:14 +0800 2012","id":3512660609653668,"mid":"3512660609653668","idstr":"3512660609653668","text":"【再见,莲花】IBM宣布,将停用Lotus品牌,Lotus品牌由IBM于1995年收购Lotus公司获得,Lotus的经典产品Lotus 1-2-3于1983年推出,该软件堪称电子表格软件的开山鼻祖。在微软的Excel推出之前,Lotus 1-2-3是电子表格软件市场的王者,直到Excel 2010都可兼容Lotus 1-2-3的格式。http://t.cn/zjZ3EF0","source":"<a href=\"http://e.weibo.com\" rel=\"nofollow\">专业版微博</a>","favorited":false,"truncated":false,"in_reply_to_status_id":"","in_reply_to_user_id":"","in_reply_to_screen_name":"","thumbnail_pic":"http://ww3.sinaimg.cn/thumbnail/61e89b74jw1dyvslw65fhj.jpg","bmiddle_pic":"http://ww3.sinaimg.cn/bmiddle/61e89b74jw1dyvslw65fhj.jpg","original_pic":"http://ww3.sinaimg.cn/large/61e89b74jw1dyvslw65fhj.jpg","geo":null,"user":{"id":1642634100,"idstr":"1642634100","screen_name":"新浪科技","name":"新浪科技","province":"11","city":"8","location":"北京 海淀区","description":"欢迎在微信上关注新浪科技公众号:搜索techsina,或扫描链接中的二维码http://e.weibo.com/1642634100/y...","url":"http://tech.sina.com.cn/","profile_image_url":"http://tp1.sinaimg.cn/1642634100/50/5613120647/0","profile_url":"sinatech","domain":"sinatech","weihao":"","gender":"f","followers_count":2373806,"friends_count":844,"statuses_count":33176,"favourites_count":19,"created_at":"Fri Aug 28 21:07:40 +0800 2009","following":true,"allow_all_act_msg":true,"geo_enabled":true,"verified":true,"verified_type":3,"remark":"","allow_all_comment":true,"avatar_large":"http://tp1.sinaimg.cn/1642634100/180/5613120647/0","verified_reason":"新浪科技官方微博","follow_me":false,"online_status":1,"bi_followers_count":591,"lang":"zh-cn","star":0,"mbtype":0,"mbrank":0,"block_word":0},"reposts_count":117,"comments_count":62,"attitudes_count":1,"mlevel":0,"visible":{"type":0,"list_id":0}},...

JSON数据格式

要与REST API交互,就需要定义数据交换格式。早期的REST API通常支持XML格式,但近年来JSON变得越来越流行,相比XML,JSON更简洁,解析速度更快,并且可以直接被JavaScript操作而无需任何第三方扩展。越来越多的REST API都首选支持JSON格式,新浪微博API也不例外。

OAuth认证

通过新浪微博的API接入网站,由于用户无需在您的网站上注册,就可以直接使用他/她在新浪微博的帐号和口令登录您的网站,这就需要确保您的网站在无需知道,也不能知道用户口令的情况下确认用户已经登录成功。由于用户的口令存储在新浪微博,因此,认证用户的过程只能由新浪微博完成,但新浪微博如何与您的网站通信并告知您用户是否登录成功呢?这个过程称之为第三方登录,OAuth是一个标准的第三方登录协议,借助OAuth,您的网站就可以安全地接入来自新浪微博登录成功的用户。

OAuth目前主要有1.0和2.0两个版本,2.0版对1.0版做了大量简化,API也更简单。新浪微博最新的API也是采用的OAuth 2.0,整个登录流程如下:

  1. 用户在您的网站上点击“使用新浪微博登录”,您的网站将用户重定向到新浪微博的OAuth认证页,重定向链接中包含client_id参数作为您的网站ID,redirect_uri参数告诉新浪微博当用户登录成功后,将浏览器重定向到您的网站;
  2. 用户在新浪微博的认证页输入帐号和口令;
  3. 新浪微博认证成功后,将浏览器重定向到您的网站,并附上code参数;
  4. 您的网站通过code参数向新浪微博请求用户的access token;
  5. 您的网站拿到用户的access token后,用户登录完成。

OAuth的access token是提供认证服务的网站(例如新浪微博)生成的令牌,代表一个用户认证信息。在随后的API调用中,传入该access token就代表这个登录用户,这样,通过OAuth协议,您的网站将验证用户的步骤交给新浪微博完成,并由新浪微博告知您用户是否登录成功。

OAuth的安全性是通过步骤4完成的,通过code参数获取access token的过程是您的网站后台到新浪微博网站完成的,用户无法看到获取access token的HTTP请求。如果用户传入伪造的code,则新浪微博会返回一个错误。

准备开发环境

本文的开发环境是Python 2.7。用Python开发Web应用非常简单,Python通过WSGI接口来提供标准的HTTP服务,并且,标准的Python包已自带一个简单的WSGI服务器,可以用于本地开发与测试。

编写Python SDK

虽然可以直接使用urllib或httplib包调用新浪微博的API,但这会使我们无法抽象出新浪微博API调用的业务接口。因此,我们第一步的目标是把新浪微博的REST API通过SDK封装,并将其转换为Python的函数调用。

新浪微博提供了非常丰富的基于JSON的API,并且还在不断添加和更新中。编写SDK的挑战在于,SDK的接口要简单易用,这样客户端调用起来才能很方便,如果新浪微博API有了更新,SDK能不必更新就能支持新的API,那就大大降低了SDK的维护成本。此外,不同语言的SDK要结合语言本身的特点和约定俗成的编程模式。我们的SDK实现需要充分利用Python语言的动态特性,一切API调用都是动态调用,API参数采用关键字参数传入,这样,SDK本身不必修改,就可以支持新的API。

下面,我们来一步一步实现SDK。

申请App

要通过新浪微博连接您的网站,首先需要在新浪微博注册一个App,作为您的网站的ID标识。新浪微博会分配一个App ID和一个App Secret,App Secret是您的网站和新浪微博共享的密钥,用于API通信时让新浪微博验证API请求确实是从您的网站发出的而不是他人假冒的。

请注意,在您的网站未通过审核前,您需要手动添加测试微博帐号,只有测试用户才能在审核前正常访问API。

准备工作:

您需要准备好您的App ID,App Secret,以及您的网站回调地址。以下代码是网站sinaweibopy.sinaapp.com的定义常量。代码如下:

# 请根据自己的应用修改常量:
_APP_ID = '123456'
_APP_SECRET = 'abc123xyz456'
_REDIRECT_URI = 'http://sinaweibopy.sinaapp.com/auth/callback'

需要根据您的设置修改常量值。

我们定义一个APIClient类,保存app id,app secret和redirect uri,代码如下:

class APIClient(object):
    def __init__(self, app_key, app_secret, redirect_uri):
        self.client_id = app_key
        self.client_secret = app_secret
        self.redirect_uri = redirect_uri

有了class,我们需要添加的第一个方法是获取新浪微博认证的URL,可以参考新浪微博的相关API文档,代码如下:

class APIClient(object):

    ...

    def get_authorize_url(self):
        return 'https://api.weibo.com/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=%s' % (self.client_id, _encode(self.redirect_uri))

get_authorize_url()方法返回新浪微博的认证页面,我们可以将其打印出来,地址类似:

https://api.weibo.com/oauth2/authorize?redirect_uri=http%3A//sinaweibopy.sinaapp.com/auth/callback&response_type=code&client_id=2373761036

粘贴到浏览器的地址栏中,就可以看到新浪微博的认证页,如图所示:

weibo-signin-page

当用户成功认证后,新浪微博将跳转至我们传入的redirect_uri,并附上code参数,可以在浏览器地址栏中看到该地址,如图所示:

weibo-signin-redirect

如果域名不存在,或者Web服务器没有运行,浏览器会显示404错误,可以在本地hosts文件中把域名映射到本机,然后在本地80端口启动Web服务器。不过,目前没有Web服务器并不妨碍我们继续编写SDK。我们从浏览器地址栏粘贴出code参数后,就可以用它来交换access token,方法request_access_token()就用于请求一个access token,代码如下:

class APIClient(object):

    ...

    def request_access_token(self, code):
        resp = _post('https://api.weibo.com/oauth2/access_token', \
                client_id = self.client_id, \
                client_secret = self.client_secret, \
                redirect_uri = self.redirect_uri, \
                code = code, \
                grant_type = 'authorization_code')
        logging.info(resp)
        current = int(time.time())
        r = json.loads(resp)
        return JsonDict(access_token=r.access_token, expires=r.expires_in + current, uid=r.uid)

其中,_post函数将发送一个HTTP POST请求,传入参数client_id、client_secret、redirect_uri、code和grant_type,新浪微博返回一个JSON格式的字符串,将它打印出来类似:

{
    "access_token": "123ABC456XYZ",
    "expires_in": 3600,
    "uid": "12345678"
}

我们将过期时间expires_in转化为绝对时间后,以JsonDict返回,并包含用户的ID。

JsonDict是一个继承自dict的类,不但可用d['key']访问,也可直接用d.key访问,代码如下:

class JsonDict(dict):

    def __getattr__(self, attr):
        return self[attr]

    def __setattr__(self, attr, value):
        self[attr] = value

可以使用代码在命令行获得access token:

client = APIClient(_APP_ID, _APP_SECRET, _REDIRECT_URI)
# 从浏览器地址栏粘贴code参数:
code = raw_input('Input code: ')
print (client.request_access_token(code))

一旦获得了access token,就可以开始调用API了。我们通过set_access_token函数来为APIClient保存access token,代码如下:

class APIClient(object):

    ...

    def set_access_token(access_token, expires):
        self.access_token = str(access_token)
        self.expires = float(expires)

新浪微博提供了100多个API接口,如果我们为每个API都编写一个函数来处理不同的API请求,不但工作量巨大,而且我们需要不断维护新浪微博新增的和修改的API接口,费时费力,还容易出错。

考虑到Python是一个动态语言,与传统的静态语言Java、C++等不同,我们可以充分利用动态语言的特性,在不必为每个API都编写函数的情况下,允许客户端按照以下规则调用API。

首先,通过新浪微博文档可以查询到每个API的URL和参数,例如,获取当前登录用户及其所关注用户的最新微博:

URL:statuses/home_timeline
HTTP请求格式:GET
请求参数:
source:采用OAuth授权不需要此参数;
access_token:OAuth授权的参数;
since_id:可选,返回ID比since_id大的微博;
max_id:可选,返回ID小于或等于max_id的微博;
count:可选,单页返回的记录条数;
page:可选,返回结果的页码;
…

其中,access_token对每个API都是必填参数,由APIClient内部处理,然后,将URL的“/”变为“.”,实现链式调用,最后,根据HTTP请求格式调用get()或post(),并传入**kw关键字参数。例如,我们希望能通过如下代码方式调用该API:

r = client.statuses.home_timeline.get(count=50, page=1)
print r
# {"statuses":[{"created_at":"Thu Nov 15 16:10:14 +0800 2012","id":3512660609653668,"idstr":"3512660609653668","text":"…"}]

这样,用户根据新浪微博的API文档,就可以自己构造出API调用。每个API调用返回值自动转化为JSON对象后返回。

下面,我们来实现该调用。

由于Python的动态特性,调用一个class的方法或字段实际上都是查找相应的属性,当该属性不存在时,Python会调用特殊方法__getattr__()试图获得该属性,因此,我们重写__getattr__()并返回一个_Callable对象即可。代码如下:

class APIClient(object):

    ...

    def __getattr__(self, attr):
        return _Callable(self.access_token, attr)

_Callable对象也重写了__getattr__()以支持动态调用,并在调用get()或post()方法时返回_Executable对象,代码如下:

class _Callable(object):

    def __init__(self, access_token, path):
        self.access_token = access_token
        self.path = path

    def __getattr__(self, attr):
        if attr=='get' or attr=='post':
            return _Executable(self.access_token, attr.upper(), self.path)
        return _Callable(self.access_token, '%s/%s' % (self.path, attr))

_Executable对象虽然是class,但重写了__call__()方法,因此是一个可调用对象,可以直接实现函数调用。代码如下:

class _Executable(object):

    def __init__(self, access_token, method, path):
        self.access_token = access_token
        self.method = method
        self.path = path

    def __call__(self, **kw):
        url = 'https://api.weibo.com/2/%s.json' % self.path
        return _http_call(url, self.method, self.access_token, **kw)

这样,用户通过API文档,就可以自行构造出任意API调用的函数链,不但极大地简化了SDK的编写,而且调用接口简单明了。

通过access token发送微博的代码如下:

client = APIClient(_APP_ID, _APP_SECRET, _REDIRECT_URI)
client.set_access_token(access_token, expires)
client.statuses.update.post(status=u'通过Python SDK发微博')

本文的SDK源码可以从GitHub下载:

https://github.com/michaelliao/sinaweibopy/blob/master/weibo.py

参考资料

新浪微博开发文档:http://open.weibo.com/wiki/

Comments

Make a comment

WARNING: You are using an old browser that does not support HTML5. Please choose a modern browser (Chrome / Microsoft Edge / Firefox / Sarafi) to get a good experience.