QQ 達人 (city.qq.com) 曾提供公開的用戶搜尋介面,透過偽造 Referer 繞過防盜鏈、解析 JSONP 回調獲取用戶資料。**注意:此介面已停止維護、極大概率已失效,且屬於未授權資料抓取,僅作技術教學參考。**
<?php
/**
* 獲取 QQ 達人用戶列表 (教學用,介面已失效)
* 原理:
* 1. 目標 URL 返回 JSONP:showSrchResult({...})
* 2. 需偽造 Referer: https://city.qq.com 繞過防盜鏈
* 3. 參數 cd=城市代碼, nl=性別, xb=年齡, pg=頁碼
*/
function getQqUsers(string $cityCode = '610900', int $page = 1, int $perPage = 10): array {
$url = 'https://city.qq.com/json.php';
$params = [
'mod' => 'search',
'act' => 'page',
'callback' => 'showSrchResult',
'jsontype' => 'str',
'perpage' => $perPage,
'cd' => $cityCode,
'nl' => 3, // 性別:1男 2女 3不限
'xb' => 0, // 年齡段
'type' => 'city',
'pg' => $page,
];
$referer = 'https://city.qq.com';
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "Referer: {$referer}\r\n" .
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\n",
'timeout' => 10,
],
]);
$query = http_build_query($params);
$fullUrl = $url . '?' . $query;
$jsonp = @file_get_contents($fullUrl, false, $context);
if ($jsonp === false) {
return ['error' => '請求失敗'];
}
// 解析 JSONP:去除回調函數包裹
// 格式:showSrchResult({...});
if (preg_match('/^\s*showSrchResult\s*\(\s*(.+)\s*\)\s*;?\s*$/s', $jsonp, $m)) {
$json = $m[1];
} else {
return ['error' => 'JSONP 格式錯誤', 'raw' => $jsonp];
}
$data = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return ['error' => 'JSON 解析失敗: ' . json_last_error_msg()];
}
return $data['data']['list'] ?? [];
}
// 使用
$users = getQqUsers('610900', 1, 20);
print_r($users);
?>
| 技術 | 說明 |
|---|---|
| Referer 偽造 | stream_context_create(['http'=>['header'=>'Referer: ...']]) 設定 HTTP Referer 標頭 |
| JSONP 解析 | 正則提取回調函數內的 JSON 字串,再 json_decode |
| 參數含義 | cd 城市代碼, nl 性別, xb 年齡, pg 頁碼, perpage 每頁筆數 |
| 防盜鏈繞過 | 伺服器檢查 Referer 是否為 city.qq.com,偽造即可通過 |
QQ 用戶資料獲取應使用 騰訊開放平台 OAuth2.0:
AppID、AppKeyhttps://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=APP_ID&redirect_uri=REDIRECT_URI&scope=get_user_infohttps://graph.qq.com/oauth2.0/token?grant_type=authorization_code&...https://graph.qq.com/user/get_user_info?access_token=TOKEN&oauth_consumer_key=APP_ID&openid=OPENID<?php
namespace App\Service;
class QqOAuth {
private string $appId;
private string $appKey;
private string $redirectUri;
public function __construct(string $appId, string $appKey, string $redirectUri) {
$this->appId = $appId;
$this->appKey = $appKey;
$this->redirectUri = $redirectUri;
}
/** 獲取授權 URL */
public function getAuthUrl(string $state = ''): string {
$params = [
'response_type' => 'code',
'client_id' => $this->appId,
'redirect_uri' => $this->redirectUri,
'scope' => 'get_user_info',
'state' => $state ?: bin2hex(random_bytes(16)),
];
return 'https://graph.qq.com/oauth2.0/authorize?' . http_build_query($params);
}
/** 回調處理:code 換取 access_token */
public function handleCallback(string $code, string $state): array {
// 1. 驗證 state (防 CSRF)
// 2. 請求 token
$tokenUrl = 'https://graph.qq.com/oauth2.0/token';
$params = [
'grant_type' => 'authorization_code',
'client_id' => $this->appId,
'client_secret' => $this->appKey,
'code' => $code,
'redirect_uri' => $this->redirectUri,
];
$resp = $this->httpGet($tokenUrl, $params);
// 回應格式:access_token=XXX&expires_in=7776000&refresh_token=YYY
parse_str($resp, $tokenData);
if (empty($tokenData['access_token'])) {
throw new RuntimeException("獲取 token 失敗: {$resp}");
}
// 3. 獲取 openid
$openidUrl = 'https://graph.qq.com/oauth2.0/me';
$resp = $this->httpGet($openidUrl, ['access_token' => $tokenData['access_token']]);
// 回應格式:callback( {"client_id":"APPID","openid":"OPENID"} );
if (preg_match('/\{.*\}/', $resp, $m)) {
$openidData = json_decode($m[0], true);
} else {
throw new RuntimeException("解析 openid 失敗: {$resp}");
}
// 4. 獲取用戶資訊
$userInfo = $this->getUserInfo($tokenData['access_token'], $openidData['openid']);
return array_merge($userInfo, $tokenData, $openidData);
}
private function getUserInfo(string $accessToken, string $openid): array {
$url = 'https://graph.qq.com/user/get_user_info';
$params = [
'access_token' => $accessToken,
'oauth_consumer_key' => $this->appId,
'openid' => $openid,
];
$resp = $this->httpGet($url, $params);
$data = json_decode($resp, true);
if (json_last_error() !== JSON_ERROR_NONE || ($data['ret'] ?? -1) !== 0) {
throw new RuntimeException("獲取用戶資訊失敗: {$resp}");
}
return $data;
}
private function httpGet(string $url, array $params): string {
$ch = curl_init($url . '?' . http_build_query($params));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => 10,
]);
$resp = curl_exec($ch);
if (curl_errno($ch)) throw new RuntimeException("cURL 錯誤: " . curl_error($ch));
curl_close($ch);
return $resp;
}
}
?>
原文技術棧(Referer 偽造 + JSONP 解析)展示了早期「非官方 API」常用手法,但**現代開發必須使用官方 OAuth2 開放平台**。《個人信息保護法》、平台服務條款、技術穩定性均要求合規接入。本文代碼僅供學習 HTTP 上下文建立、JSONP 解析、OAuth2 流程等通用技術。