├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── src ├── BiliAuth.php └── HttpClient.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .idea/ 4 | test.php 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bili-Auth 2 | (BILIBILI) B站登陆API盒子 3 | 4 | ## 环境依赖 5 | PHP 5.6+ Curl, OpenSSL extension installed. 6 | > **Note:** BiliAuth requires [cURL](http://php.net/manual/en/book.curl.php) and [OpenSSL](http://php.net/manual/en/book.openssl.php) extension in order to work. 7 | 8 | ## 安装程序 9 | 需要安装: [Composer](https://getcomposer.org) 10 | 11 | ```bash 12 | $ composer require lkeme/bili-auth 13 | ``` 14 | 15 | ## 快速开始 16 | 在你的项目中引用代码: 17 | 18 | ```php 19 | use Lkeme\BiliAuth; 20 | 21 | $api = new BiliAuth(); 22 | 23 | $data = $api->login('username','password'); 24 | ``` 25 | 26 | ## 所有方法 27 | 28 | ### 登陆 29 | ```php 30 | $api->login('username', 'password'); 31 | ``` 32 | 33 | |参数名|必须|描述| 34 | | ------ |-------|-----------| 35 | |username|√ |哔哩哔哩账号| 36 | |password|√ |哔哩哔哩密码| 37 | 38 | ```json 39 | { 40 | "code": 0, 41 | "data": { 42 | "uid": 123456, 43 | "userName": "123456", 44 | "accessToken": "81dc9bdb52d04dc20036dbd8313ed055", 45 | "refreshToken": "202cb962ac59075b964b07152d234b70", 46 | "cookieInfo": "bili_jct=...;DedeUserID=...;DedeUserID__ckMd5=...;sid=...;SESSDATA=...;", 47 | "csrfToken": "827ccb0eea8a706c4c34a16891f84e7b", 48 | "expires_in": "2018-11-11 11:11:11" 49 | }, 50 | "message": "账号登陆成功!" 51 | } 52 | ``` 53 | 54 | ### 验证码登陆 55 | > 需要先获取验证码 56 | ```php 57 | $api->login('username', 'password', 'captcha', 'cookie'); 58 | ``` 59 | 60 | |参数名|必须|描述| 61 | | ------ |-------|-----------| 62 | |username|√ |哔哩哔哩账号| 63 | |password|√ |哔哩哔哩密码| 64 | |captcha |√ |验证码 | 65 | |cookie |√ |对应验证码 | 66 | 67 | ```json 68 | { 69 | "code": 0, 70 | "data": { 71 | "uid": 123456, 72 | "userName": "123456", 73 | "accessToken": "81dc9bdb52d04dc20036dbd8313ed055", 74 | "refreshToken": "202cb962ac59075b964b07152d234b70", 75 | "cookieInfo": "bili_jct=...;DedeUserID=...;DedeUserID__ckMd5=...;sid=...;SESSDATA=...;", 76 | "csrfToken": "827ccb0eea8a706c4c34a16891f84e7b", 77 | "expires_in": "2018-11-11 11:11:11" 78 | }, 79 | "message": "账号登陆成功!" 80 | } 81 | ``` 82 | 83 | 84 | ### 获取验证码(验证码登陆前提) 85 | ```php 86 | $api->getCapcha(); 87 | ``` 88 | 89 | |参数名|必须|描述| 90 | | ------ |-------|-----------| 91 | |无 |无 |无 | 92 | 93 | ```json 94 | { 95 | "code": 200, 96 | "cookie": "kxMAJX6f", 97 | "captcha_img": "base64图片", 98 | "bash64_head": "data:image/jpg/png/gif;base64,", 99 | "message": "获取验证码图片(Base64)!" 100 | } 101 | ``` 102 | 103 | 104 | ### 检测COOKIE有效性 105 | ```php 106 | $api->checkCookie($cookie); 107 | ``` 108 | 109 | |参数名|必须|描述| 110 | | ------ |-------|-----------| 111 | |cookie |√ |COOKIE | 112 | 113 | ```json 114 | { 115 | "code": 0, 116 | "status": "valid", 117 | "message": "检测 Cookie 有效!" 118 | } 119 | ``` 120 | 121 | ### 刷新令牌时效 122 | ```php 123 | $api->refreshToken($access, $refresh); 124 | ``` 125 | 126 | |参数名|必须|描述| 127 | | ------ |-------|-----------| 128 | |access |√ |ACCESS_TOEKN| 129 | |refresh |√ |REFRESH_TOEKN| 130 | 131 | ```json 132 | { 133 | "code": 0, 134 | "data": { 135 | "mid": 123456, 136 | "refresh_token": "698d51a19d8a121ce581499d7b701668", 137 | "access_token": "b59c67bf196a4758191e42f76670ceba", 138 | "expires_in": "2018-11-11 11:11:11" 139 | }, 140 | "message": "续签令牌: 成功!" 141 | } 142 | ``` 143 | 144 | ### 检测令牌有效性 145 | ```php 146 | $api->checkToken($access); 147 | ``` 148 | 149 | |参数名|必须|描述| 150 | | ------ |-------|-----------| 151 | |access |√ |ACCESS_TOEKN| 152 | 153 | ```json 154 | { 155 | "code": 0, 156 | "status": 1, 157 | "message": "令牌验证成功,有效期至: 2018-11-11 11:11:11" 158 | } 159 | ``` 160 | 161 | ## 状态 162 | B站的状态码很混乱,后面陆续记录一些 163 | 164 | |状态码|描述| 165 | | ------ | ---------- | 166 | |0 |正常 | 167 | |200 |CURL返回的 | 168 | |-105 |登陆需要验证码 | 169 | |629 |账号密码错误 | 170 | |1024 |超时 | 171 | |403 |IP问题 | 172 | 173 | 174 | ## TODO 175 | - 待定 176 | 177 | 178 | ## Author 179 | 180 | **Bili-Auth** © [lkeme](https://github.com/lkeme), Released under the [MIT](./LICENSE) License.
181 | 182 | > Blog [@lkeme](https://mudew.com) · GitHub [@lkeme](https://github.com/lkeme) 183 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lkeme/bili-auth", 3 | "type": "library", 4 | "description": "BILIBILI网站登陆API盒子.", 5 | "keywords": [ 6 | "bilibili", 7 | "login", 8 | "login api", 9 | "bili" 10 | ], 11 | "homepage": "https://github.com/lkeme/BiliAuth", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Lkeme", 16 | "email": "Useri@live.cn", 17 | "homepage": "https://mudew.com" 18 | } 19 | ], 20 | "support": { 21 | "issues": "https://github.com/lkeme/BiliAuth/issues", 22 | "source": "https://github.com/lkeme/BiliAuth" 23 | }, 24 | "require": { 25 | "php": ">=5.6.0", 26 | "ext-curl": "*", 27 | "ext-openssl": "*" 28 | }, 29 | "suggest": { 30 | "ext-bcmath": "Required to use BC Math calculate RSA.", 31 | "ext-openssl": "Required to use OpenSSL encrypt params." 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Lkeme\\": "src/" 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/BiliAuth.php: -------------------------------------------------------------------------------- 1 | '*/*', 18 | # 'Accept-Encoding' => 'gzip', 19 | 'Accept-Language' => 'zh-cn', 20 | 'Connection' => 'keep-alive', 21 | 'Content-Type' => 'application/x-www-form-urlencoded', 22 | 'User-Agent' => 'bili-universal/8110 CFNetwork/974.2.1 Darwin/18.0.0', 23 | ]; 24 | 25 | /** 26 | * BiliAuth constructor. 27 | */ 28 | public function __construct() 29 | { 30 | $this->client = new HttpClient($this->headers); 31 | } 32 | 33 | /** 34 | * @use login 35 | * @param $username 36 | * @param $password 37 | * @return false|string 38 | */ 39 | public function login($username, $password) 40 | { 41 | $auth = $this->getPublicKey(); 42 | if ($auth['code']) { 43 | return $this->buildData($auth); 44 | } 45 | $key = $auth['key']; 46 | $hash = $auth['hash']; 47 | openssl_public_encrypt($hash . $password, $crypt, $key); 48 | $token = $this->getToken($username, base64_encode($crypt)); 49 | 50 | return $this->buildData($token); 51 | } 52 | 53 | /** 54 | * @use loginByCaptcha 55 | * @param $username 56 | * @param $password 57 | * @param $captcha 58 | * @param $cookie 59 | * @return false|string 60 | */ 61 | public function loginByCaptcha($username, $password, $captcha, $cookie) 62 | { 63 | $auth = $this->getPublicKey(); 64 | if ($auth['code']) { 65 | return $this->buildData($auth); 66 | } 67 | $key = $auth['key']; 68 | $hash = $auth['hash']; 69 | openssl_public_encrypt($hash . $password, $crypt, $key); 70 | 71 | $token = $this->getToken($username, base64_encode($crypt), $captcha, $cookie); 72 | 73 | return $this->buildData($token); 74 | } 75 | 76 | /** 77 | * @use getCapcha 78 | * @return false|string 79 | */ 80 | public function getCapcha() 81 | { 82 | $cookie = "sid=" . $this->getRandomString(); 83 | $payload = $this->sign([]); 84 | $url = 'https://passport.bilibili.com/captcha'; 85 | 86 | $raw = $this->client->get($url, $payload, $headers = [], $cookie); 87 | 88 | $data = [ 89 | 'code' => $raw['code'], 90 | 'cookie' => $cookie, 91 | 'captcha_img' => base64_encode($raw['content']), 92 | 'bash64_head' => "data:image/jpg/png/gif;base64,", 93 | 'message' => '获取验证码图片(Base64)!', 94 | ]; 95 | 96 | return $this->buildData($data); 97 | } 98 | 99 | /** 100 | * @use checkCookie 101 | * @param $cookie 102 | * @return false|string 103 | */ 104 | public function checkCookie($cookie) 105 | { 106 | $headers = [ 107 | 'Cookie' => $cookie, 108 | ]; 109 | $payload = [ 110 | 'ts' => time(), 111 | ]; 112 | $url = 'https://api.live.bilibili.com/User/getUserInfo'; 113 | 114 | $raw = $this->client->get($url, $payload, $headers); 115 | $de_raw = json_decode($raw['content'], true); 116 | 117 | if (isset($de_raw['code']) && $de_raw['code'] != 'REPONSE_OK') { 118 | $data = [ 119 | 'code' => $de_raw['code'], 120 | 'status' => 'invalid', 121 | 'message' => '检测 Cookie 无效!' 122 | ]; 123 | } else { 124 | $data = [ 125 | 'code' => 0, 126 | 'status' => 'valid', 127 | 'message' => '检测 Cookie 有效!' 128 | ]; 129 | } 130 | 131 | return $this->buildData($data); 132 | } 133 | 134 | /** 135 | * @use refreshToken 136 | * @param $access 137 | * @param $refresh 138 | * @return false|string 139 | */ 140 | public function refreshToken($access, $refresh) 141 | { 142 | $payload = [ 143 | 'access_token' => $access, 144 | 'refresh_token' => $refresh, 145 | ]; 146 | $payload = $this->sign($payload); 147 | $url = 'https://passport.bilibili.com/api/oauth2/refreshToken'; 148 | 149 | $raw = $this->client->post($url, $payload); 150 | $de_raw = json_decode($raw['content'], true); 151 | 152 | if (isset($de_raw['code']) && $de_raw['code']) { 153 | $data = [ 154 | 'code' => $de_raw['code'], 155 | 'message' => '续签令牌: ' . $this->buildMsg($de_raw, '失败!') 156 | ]; 157 | } else { 158 | $data = [ 159 | 'code' => $de_raw['code'], 160 | 'data' => [ 161 | 'mid' => $de_raw['data']['mid'], 162 | 'refresh_token' => $de_raw['data']['refresh_token'], 163 | 'access_token' => $de_raw['data']['access_token'], 164 | 'expires_in' => date('Y-m-d H:i:s', $de_raw['ts'] + $de_raw['data']['expires_in']), 165 | ], 166 | 'message' => '续签令牌: ' . $this->buildMsg($de_raw, '成功!') 167 | ]; 168 | } 169 | return $this->buildData($data); 170 | } 171 | 172 | /** 173 | * @use checkToken 174 | * @param $access 175 | * @return false|string 176 | */ 177 | public function checkToken($access) 178 | { 179 | $payload = [ 180 | 'access_token' => $access, 181 | ]; 182 | $payload = $this->sign($payload); 183 | $url = 'https://passport.bilibili.com/api/oauth2/info'; 184 | 185 | $raw = $this->client->get($url, $payload); 186 | $de_raw = json_decode($raw['content'], true); 187 | if (isset($de_raw['code']) && $de_raw['code']) { 188 | $data = [ 189 | 'code' => $de_raw['code'], 190 | 'message' => $this->buildMsg($de_raw, '令牌验证失败!') 191 | ]; 192 | } else { 193 | $data = [ 194 | 'code' => $de_raw['code'], 195 | 'status' => $de_raw['data']['expires_in'] > 86400 ? 1 : 0, 196 | 'message' => '令牌验证成功,有效期至: ' . date('Y-m-d H:i:s', $de_raw['ts'] + $de_raw['data']['expires_in']) 197 | ]; 198 | } 199 | 200 | return $this->buildData($data); 201 | } 202 | 203 | /** 204 | * @use getRandomString 205 | * @param int $len 206 | * @param null $chars 207 | * @return string 208 | */ 209 | private function getRandomString($len = 6, $chars = null) 210 | { 211 | if (is_null($chars)) { 212 | $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 213 | } 214 | mt_srand(10000000 * (double)microtime()); 215 | for ($i = 0, $data = '', $lc = strlen($chars) - 1; $i < $len; $i++) { 216 | $data .= $chars[mt_rand(0, $lc)]; 217 | } 218 | return $data; 219 | } 220 | 221 | /** 222 | * @use getToken 223 | * @param $username 224 | * @param $password 225 | * @param string $captcha 226 | * @param string $cookie 227 | * @param array $params 228 | * @return array 229 | */ 230 | private function getToken($username, $password, $captcha = '', $cookie = '', $params = []) 231 | { 232 | $payload = [ 233 | 'subid' => 1, 234 | 'permission' => 'ALL', 235 | 'username' => $username, 236 | 'password' => $password, 237 | 'captcha' => $captcha, 238 | ]; 239 | $payload = $this->sign($payload); 240 | 241 | $url = 'https://passport.bilibili.com/api/v2/oauth2/login'; 242 | 243 | $raw = $this->client->post($url, $payload, $params, $headers = [], $cookie); 244 | $de_raw = json_decode($raw['content'], true); 245 | 246 | if (isset($de_raw['code']) && $de_raw['code']) { 247 | $data = [ 248 | 'code' => $de_raw['code'], 249 | 'message' => $this->buildMsg($de_raw, '登陆失败!') 250 | ]; 251 | } elseif (isset($de_raw['code']) && $de_raw['code'] == -105) { 252 | $data = [ 253 | 'code' => $de_raw['code'], 254 | 'message' => $this->buildMsg($de_raw, '该账号需要验证码登陆!') 255 | ]; 256 | } else { 257 | $temp = ''; 258 | $cookies = $de_raw['data']['cookie_info']['cookies']; 259 | foreach ($cookies as $cookie) { 260 | $temp .= $cookie['name'] . '=' . $cookie['value'] . ';'; 261 | } 262 | $data = [ 263 | 'code' => $de_raw['code'], 264 | 'data' => [ 265 | 'uid' => $de_raw['data']['token_info']['mid'], 266 | 'userName' => $username, 267 | 'accessToken' => $de_raw['data']['token_info']['access_token'], 268 | 'refreshToken' => $de_raw['data']['token_info']['refresh_token'], 269 | 'cookieInfo' => $temp, 270 | 'csrfToken' => $de_raw['data']['cookie_info']['cookies'][0]['value'], 271 | 'expires_in' => date('Y-m-d H:i:s', $de_raw['ts'] + $de_raw['data']['token_info']['expires_in']) 272 | ], 273 | 'message' => $this->buildMsg($de_raw, '账号登陆成功!') 274 | ]; 275 | } 276 | 277 | return $data; 278 | } 279 | 280 | /** 281 | * @use getPublicKey 282 | * @return array 283 | */ 284 | private function getPublicKey() 285 | { 286 | $payload = $this->sign([]); 287 | $url = 'https://passport.bilibili.com/api/oauth2/getKey'; 288 | 289 | $raw = $this->client->post($url, $payload); 290 | $de_raw = json_decode($raw['content'], true); 291 | 292 | if (isset($de_raw['code']) && $de_raw['code']) { 293 | $data = [ 294 | 'code' => $de_raw['code'], 295 | 'message' => $this->buildMsg($de_raw, '公匙获取失败!') 296 | ]; 297 | } else { 298 | $data = [ 299 | 'code' => $de_raw['code'], 300 | 'key' => $de_raw['data']['key'], 301 | 'hash' => $de_raw['data']['hash'], 302 | 'message' => $this->buildMsg($de_raw, '公匙获取成功!') 303 | ]; 304 | } 305 | return $data; 306 | } 307 | 308 | /** 309 | * @use sign 310 | * @param $payload 311 | * @return array 312 | */ 313 | private function sign($payload) 314 | { 315 | # iOS 6680 316 | $appkey = '27eb53fc9058f8c3'; 317 | $appsecret = 'c2ed53a74eeefe3cf99fbd01d8c9c375'; 318 | # Android 319 | // $appkey = '1d8b6e7d45233436'; 320 | // $appsecret = '560c52ccd288fed045859ed18bffd973'; 321 | # 云视听 TV 322 | // $appkey = '4409e2ce8ffd12b8'; 323 | // $appsecret = '59b43e04ad6965f34319062b478f83dd'; 324 | $default = [ 325 | 'appkey' => $appkey 326 | ]; 327 | $payload = array_merge($payload, $default); 328 | if (isset($payload['sign'])) { 329 | unset($payload['sign']); 330 | } 331 | ksort($payload); 332 | $data = http_build_query($payload); 333 | $payload['sign'] = md5($data . $appsecret); 334 | return $payload; 335 | } 336 | 337 | /** 338 | * @use buildData 339 | * @param $data 340 | * @return false|string 341 | */ 342 | private function buildData($data) 343 | { 344 | return json_encode($data, JSON_UNESCAPED_UNICODE); 345 | } 346 | 347 | /** 348 | * @use buildMsg 349 | * @param $data 350 | * @param $key 351 | * @param $msg 352 | * @return mixed 353 | */ 354 | private function buildMsg($data, $msg, $key = 'message') 355 | { 356 | return array_key_exists($key, $data) ? $data['message'] : $msg; 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/HttpClient.php: -------------------------------------------------------------------------------- 1 | headers = $this->buildHeaders($headers); 26 | $this->connectTimeout = 60000; 27 | $this->socketTimeout = 60000; 28 | } 29 | 30 | /** 31 | * 连接超时 32 | * @param int $ms 毫秒 33 | */ 34 | public function setConnectionTimeoutInMillis($ms) 35 | { 36 | $this->connectTimeout = $ms; 37 | } 38 | 39 | /** 40 | * 响应超时 41 | * @param int $ms 毫秒 42 | */ 43 | public function setSocketTimeoutInMillis($ms) 44 | { 45 | $this->socketTimeout = $ms; 46 | } 47 | 48 | /** 49 | * @param string $url 50 | * @param array $data 51 | * @param array $params 52 | * @param array $headers 53 | * @param null $cookie 54 | * @return array 55 | */ 56 | public function post($url, $data = array(), $params = array(), $headers = array(), $cookie = null) 57 | { 58 | $url = $this->buildUrl($url, $params); 59 | $headers = array_merge($this->headers, $this->buildHeaders($headers)); 60 | 61 | $ch = curl_init(); 62 | curl_setopt($ch, CURLOPT_URL, $url); 63 | curl_setopt($ch, CURLOPT_POST, 1); 64 | curl_setopt($ch, CURLOPT_HEADER, false); 65 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 66 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 67 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 68 | curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? http_build_query($data) : $data); 69 | curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->socketTimeout); 70 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->connectTimeout); 71 | if (!is_null($cookie)) { 72 | curl_setopt($ch, CURLOPT_COOKIE, $cookie); 73 | } 74 | if ($this->debug) { 75 | curl_setopt($ch, CURLOPT_PROXY, "127.0.0.1"); //代理服务器地址 76 | curl_setopt($ch, CURLOPT_PROXYPORT, "8888"); //代理服务器端口 77 | } 78 | 79 | $content = curl_exec($ch); 80 | $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 81 | 82 | if ($code === 0) { 83 | throw new Exception(curl_error($ch)); 84 | } 85 | 86 | curl_close($ch); 87 | return array( 88 | 'code' => $code, 89 | 'content' => $content, 90 | ); 91 | } 92 | 93 | /** 94 | * @param string $url 95 | * @param array $datas HTTP POST BODY 96 | * @param array $params HTTP URL 97 | * @param array $headers HTTP header 98 | * @return array 99 | */ 100 | public function multi_post($url, $datas = array(), $params = array(), $headers = array()) 101 | { 102 | $url = $this->buildUrl($url, $params); 103 | $headers = array_merge($this->headers, $this->buildHeaders($headers)); 104 | 105 | $chs = array(); 106 | $result = array(); 107 | $mh = curl_multi_init(); 108 | foreach ($datas as $data) { 109 | $ch = curl_init(); 110 | $chs[] = $ch; 111 | curl_setopt($ch, CURLOPT_URL, $url); 112 | curl_setopt($ch, CURLOPT_POST, 1); 113 | curl_setopt($ch, CURLOPT_HEADER, false); 114 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 115 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 116 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 117 | curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? http_build_query($data) : $data); 118 | curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->socketTimeout); 119 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->connectTimeout); 120 | curl_multi_add_handle($mh, $ch); 121 | } 122 | 123 | $running = null; 124 | do { 125 | curl_multi_exec($mh, $running); 126 | usleep(100); 127 | } while ($running); 128 | 129 | foreach ($chs as $ch) { 130 | $content = curl_multi_getcontent($ch); 131 | $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 132 | $result[] = array( 133 | 'code' => $code, 134 | 'content' => $content, 135 | ); 136 | curl_multi_remove_handle($mh, $ch); 137 | } 138 | curl_multi_close($mh); 139 | 140 | return $result; 141 | } 142 | 143 | 144 | /** 145 | * @param string $url 146 | * @param array $params 147 | * @param array $headers 148 | * @param null $cookie 149 | * @return array 150 | */ 151 | public function get($url, $params = array(), $headers = array(), $cookie = null) 152 | { 153 | $url = $this->buildUrl($url, $params); 154 | $headers = array_merge($this->headers, $this->buildHeaders($headers)); 155 | 156 | $ch = curl_init(); 157 | curl_setopt($ch, CURLOPT_URL, $url); 158 | curl_setopt($ch, CURLOPT_HEADER, false); 159 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 160 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 161 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 162 | curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->socketTimeout); 163 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->connectTimeout); 164 | if (!is_null($cookie)) { 165 | curl_setopt($ch, CURLOPT_COOKIE, $cookie); 166 | } 167 | if ($this->debug) { 168 | curl_setopt($ch, CURLOPT_PROXY, "127.0.0.1"); //代理服务器地址 169 | curl_setopt($ch, CURLOPT_PROXYPORT, "8888"); //代理服务器端口 170 | } 171 | $content = curl_exec($ch); 172 | $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 173 | 174 | if ($code === 0) { 175 | throw new Exception(curl_error($ch)); 176 | } 177 | 178 | curl_close($ch); 179 | return array( 180 | 'code' => $code, 181 | 'content' => $content, 182 | ); 183 | } 184 | 185 | /** 186 | * 构造 header 187 | * @param array $headers 188 | * @return array 189 | */ 190 | private function buildHeaders($headers) 191 | { 192 | $result = array(); 193 | foreach ($headers as $k => $v) { 194 | $result[] = sprintf('%s:%s', $k, $v); 195 | } 196 | return $result; 197 | } 198 | 199 | /** 200 | * 201 | * @param string $url 202 | * @param array $params 参数 203 | * @return string 204 | */ 205 | private function buildUrl($url, $params) 206 | { 207 | if (!empty($params)) { 208 | $str = http_build_query($params); 209 | return $url . (strpos($url, '?') === false ? '?' : '&') . $str; 210 | } else { 211 | return $url; 212 | } 213 | } 214 | } --------------------------------------------------------------------------------