原文链接:
本意是在注销账号前保留之前的一些数据。决定用python 爬取收藏。可是未登录无法爬取。想要登录有两种办法,伪造浏览器登录。第二就是注册新浪开发者账号,通过Oauth认证调用其API。
Oauth 的原理搞了一天才明白。很多网站都提供多语言的Oauth。而 1.0 和 2.0 的最大差别就是多了一个 callback 回调页面。关于这方面的说明很少,搞得我一头雾水折腾了好久。总算明白了。
Oauth的原理
现在多用 oauth2.0. 具体可以看其。用下面的图简单说明:
Consumer指应用程序,User是用户,Service是服务器。大致过程为,User使用程序Consumer。C 向 Service请求一个未授权的令牌(Request_token),这个时候 S 验证 C的合法性(通过开发者的 APP_KEY 和 APP_SECREST)。然后重定向到一个授权页面(通常由服务方提供)。此时用户进行授权,即输入用户名和密码,用户会自动向服务器发送 authorize request_token。得到授权的 request_token。再然后,授权通过后,跳转到一个 callback 页面。通过request_token用来向服务器请求,换取 acess_token。至此,认证结束。接下来要请求数据,只需要根据文档 把 acess_token 当参数传给服务器。
这样说比较抽象,举个简单的例子:
有一个人 U 想去银行 S 取钱。U 没事情去柜台,就委托朋友 C 去柜台。当C 去了银行S,给S说:我要帮 U 取钱。S 先验证 C 的身份,是合法公民。然后 S 打电话给 U。说:C 要帮你取钱,你确定么,确定的话就输入用户名和密码。 U 确定了。此时,S 就给了一个 钥匙给 C。说:我们不提供自动服务,给你钥匙,自己去库房取。然后 C 拿着钥匙,就去取钱了。取完之后给力 U。U 很感激。
通过上面的解释,应该可以知道整个过程中有三个URL请求地址,如下图:
这写地址由服务方提供,上面那个就是 qq 公共平台的url。新浪有新浪自己的。关于第一步,请求 request_token的时候,服务器也要认证开发者帐号,也就是银行认证 C 的合法身份。
每个 开发者都有app_key 和 app_secret. app_key称为密钥, app_secret为密匙。开发者和服务都知道。然后 ,开发者通过 app_key 和 app_secret 通过 一种加密(sha1)得到一个字符串secretcode 公钥。再把 app_key 和 secretcode 发送给服务器。服务器接收之后,将发送到 app_key和服务器存储的 app_secret 通过同样的加密手段得到一个 secretcode2 和发送过来的secretcode进行对比,如果一样,则可以证明是通过。这一点好处是安全。不怕被抓包。
新浪 Oauth的运用
简单知道原理,就可以进行认证。
准备工作
需要向新浪申请开发者帐号,然后创建一个应用。之后会得到一个 app_key 和 app_secret。需要注意是一定要填写下面的回调地址:
新浪的是可以设置 本地的,例如
豆瓣的需要公网可以访问的回调地址,不同api不一样。
布署
可以下载 ,根据语言选择。这里选择 php。理由是 php的web环境可以一键安装。需要注意到是,新浪的 SDK需要 php 开启 curl。不然会报错。
文档结构如下:
config.php 开发者配置文件
index.php 登录主入口
callback.php 回调处理
saetv2.ex.class.php SDK 主文件,提供认证和api调用
weibolist.php 认证成功之后的数据请求和展示
引用官方的 SDK ,根据demo使用就行。这里重写一下 SDK 的请求过程。
index.php
主要做的事情和I获取 请求 request_token 的url。然后跳转到官方提供的授权页面:
授权之后会重定向到回调 callback.pho.
1 getAccessToken( 'code', $keys ) ;15 } catch (OAuthException $e) {16 }17 }18 19 if ($token) {20 $_SESSION['token'] = $token;21 setcookie( 'weibojs_'.$o->client_id, http_build_query($token) );22 ?>23 授权完成,进入你的微博列表页面24 27 授权失败。28
这一步主要是根据授权之后返回的 code 进行调用,请求 acess_token。
前面两部的方法 ,都在
saetv2.ex.class.php里实现,代码如下:
1 client_id = $client_id; 54 $this->client_secret = $client_secret; 55 $this->access_token = $access_token; 56 } 57 58 /** 59 * authorize接口 60 * 61 * 对应API:{@link http://open.weibo.com/wiki/Oauth2/authorize Oauth2/authorize} 62 * 63 * @param string $url 授权后的回调地址,站外应用需与回调地址一致,站内应用需要填写canvas page的地址 64 * @param string $response_type 支持的值包括 code 和token 默认值为code 65 * @param string $state 用于保持请求和回调的状态。在回调时,会在Query Parameter中回传该参数 66 * @param string $display 授权页面类型 可选范围: 67 * - default 默认授权页面 68 * - mobile 支持html5的手机 69 * - popup 弹窗授权页 70 * - wap1.2 wap1.2页面 71 * - wap2.0 wap2.0页面 72 * - js js-sdk 专用 授权页面是弹窗,返回结果为js-sdk回掉函数 73 * - apponweibo 站内应用专用,站内应用不传display参数,并且response_type为token时,默认使用改display.授权后不会返回access_token,只是输出js刷新站内应用父框架 74 * @return array 75 */ 76 function getAuthorizeURL( $url, $response_type = 'code', $state = NULL, $display = NULL ) { 77 $params = array(); 78 $params['client_id'] = $this->client_id; 79 $params['redirect_uri'] = $url; 80 $params['response_type'] = $response_type; 81 $params['state'] = $state; 82 $params['display'] = $display; 83 return $this->authorizeURL() . "?" . http_build_query($params); 84 } 85 86 /** 87 * access_token接口 88 * 89 * 对应API:{@link http://open.weibo.com/wiki/OAuth2/access_token OAuth2/access_token} 90 * 91 * @param string $type 请求的类型,可以为:code, password, token 92 * @param array $keys 其他参数: 93 * - 当$type为code时: array('code'=>..., 'redirect_uri'=>...) 94 * - 当$type为password时: array('username'=>..., 'password'=>...) 95 * - 当$type为token时: array('refresh_token'=>...) 96 * @return array 97 */ 98 function getAccessToken( $type = 'code', $keys ) { 99 $params = array();100 $params['client_id'] = $this->client_id;101 $params['client_secret'] = $this->client_secret;102 if ( $type === 'code' ) {103 $params['grant_type'] = 'authorization_code';104 $params['code'] = $keys['code'];105 $params['redirect_uri'] = $keys['redirect_uri'];106 } else {107 throw new OAuthException("wrong auth type");108 }109 110 $response = $this->oAuthRequest($this->accessTokenURL(), 'POST', $params);111 $token = json_decode($response, true);112 if ( is_array($token) && !isset($token['error']) ) {113 $this->access_token = $token['access_token'];114 } else {115 throw new OAuthException("get access token failed." . $token['error']);116 }117 return $token;118 }119 120 /**121 * Format and sign an OAuth / API request122 *123 * @return string124 * @ignore125 */126 function oAuthRequest($url, $method, $parameters, $multi = false) {127 128 if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) {129 $url = "{ $this->host}{ $url}.{ $this->format}";130 }131 132 switch ($method) {133 case 'GET':134 $url = $url . '?' . http_build_query($parameters);135 return $this->http($url, 'GET');136 default:137 $headers = array();138 if (!$multi && (is_array($parameters) || is_object($parameters)) ) {139 $body = http_build_query($parameters);140 } else {141 $body = self::build_http_query_multi($parameters);142 $headers[] = "Content-Type: multipart/form-data; boundary=" . self::$boundary;143 }144 return $this->http($url, $method, $body, $headers);145 }146 }147 148 /**149 * Make an HTTP request150 *151 * @return string API results152 * @ignore153 */154 function http($url, $method, $postfields = NULL, $headers = array()) {155 $this->http_info = array();156 $ci = curl_init();157 /* Curl settings */158 curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);159 curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent);160 curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);161 curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout);162 curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);163 curl_setopt($ci, CURLOPT_ENCODING, "");164 curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);165 curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader'));166 curl_setopt($ci, CURLOPT_HEADER, FALSE);167 168 switch ($method) {169 case 'POST':170 curl_setopt($ci, CURLOPT_POST, TRUE);171 if (!empty($postfields)) {172 curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);173 $this->postdata = $postfields;174 }175 break;176 case 'DELETE':177 curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE');178 if (!empty($postfields)) {179 $url = "{ $url}?{ $postfields}";180 }181 }182 183 if ( isset($this->access_token) && $this->access_token )184 $headers[] = "Authorization: OAuth2 ".$this->access_token;185 186 $headers[] = "API-RemoteIP: " . $_SERVER['REMOTE_ADDR'];187 curl_setopt($ci, CURLOPT_URL, $url );188 curl_setopt($ci, CURLOPT_HTTPHEADER, $headers );189 curl_setopt($ci, CURLINFO_HEADER_OUT, TRUE );190 191 $response = curl_exec($ci);192 $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);193 $this->http_info = array_merge($this->http_info, curl_getinfo($ci));194 $this->url = $url;195 196 if ($this->debug) {197 echo "=====post data======\r\n";198 var_dump($postfields);199 200 echo '=====info====='."\r\n";201 print_r( curl_getinfo($ci) );202 203 echo '=====$response====='."\r\n";204 print_r( $response );205 }206 curl_close ($ci);207 return $response;208 }209 210 /**211 * Get the header info to store.212 *213 * @return int214 * @ignore215 */216 function getHeader($ch, $header) {217 $i = strpos($header, ':');218 if (!empty($i)) {219 $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));220 $value = trim(substr($header, $i + 2));221 $this->http_header[$key] = $value;222 }223 return strlen($header);224 }225 226 /**227 * @ignore228 */229 public static function build_http_query_multi($params) {230 if (!$params) return '';231 232 uksort($params, 'strcmp');233 234 $pairs = array();235 236 self::$boundary = $boundary = uniqid('------------------');237 $MPboundary = '--'.$boundary;238 $endMPboundary = $MPboundary. '--';239 $multipartbody = '';240 241 foreach ($params as $parameter => $value) {242 243 if( in_array($parameter, array('pic', 'image')) && $value{0} == '@' ) {244 $url = ltrim( $value, '@' );245 $content = file_get_contents( $url );246 $array = explode( '?', basename( $url ) );247 $filename = $array[0];248 249 $multipartbody .= $MPboundary . "\r\n";250 $multipartbody .= 'Content-Disposition: form-data; name="' . $parameter . '"; filename="' . $filename . '"'. "\r\n";251 $multipartbody .= "Content-Type: image/unknown\r\n\r\n";252 $multipartbody .= $content. "\r\n";253 } else {254 $multipartbody .= $MPboundary . "\r\n";255 $multipartbody .= 'content-disposition: form-data; name="' . $parameter . "\"\r\n\r\n";256 $multipartbody .= $value."\r\n";257 }258 259 }260 261 $multipartbody .= $endMPboundary;262 return $multipartbody;263 }264 }265 ?>
getAuthorizeURL 方法是用来获取请求 request_token的地址。
getAccessToken 方法是获取 access_token
oAuthRequest 方法是用来发送请求
getHeader 我没发现有地方调用,但是没有他有不行,暂时不知道为什么。
至此Oauth认证结束。
认证就是为了得倒 access_token。本来我是要爬数据。才为了登录。后来发现直接使用 新浪的 ,自动生成access_token。爬虫就直接用。当然,早没有发现,才促使我去研究了 Oauth认证,额外的收获吧。总而言之,人总是在逼迫中才能进步。