前兩篇已實現模擬 QQ 登入並取得 Cookie。本文進一步:獲取群列表、群成員 (含 QQ 郵箱),核心難點在於 g_tk 參數計算 (網上鮮見完整 C# 實作)。
URL:https://qun.qzone.qq.com/cgi-bin/get_group_list?uin={uin}&random={rand}&g_tk={gtk}
回傳 JSON:{ "code":0, "data":{ "group":[{ "groupid":"12345", "groupname":"測試群", ... }] } }
URL:https://qun.qzone.qq.com/cgi-bin/get_group_member?uin={uin}&groupid={gid}&random={rand}&g_tk={gtk}
回傳 JSON:{ "code":0, "data":{ "item":[{ "uin":"123456", "nick":"用戶", "iscreator":"0", ... }] } }
uin 即 QQ 號,郵箱為 {uin}@qq.com。
源自騰訊網頁 JS:hash = 5381; for(c in skey) hash += (hash << 5) + c.charCodeAt(); g_tk = hash & 0x7fffffff;
public static int GetGTK(CookieContainer cookies)
{
foreach (Cookie cookie in GetAllCookies(cookies))
{
if (cookie.Name == "skey")
{
int hash = 5381;
foreach (char c in cookie.Value)
hash += (hash << 5) + c;
return hash & 0x7fffffff;
}
}
return 0;
}
public static List GetAllCookies(CookieContainer cc)
{
var list = new List();
var table = (Hashtable)cc.GetType().GetField("m_domainTable",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField).GetValue(cc);
foreach (object pathList in table.Values)
{
var lst = (SortedList)pathList.GetType().GetField("m_list",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField).GetValue(pathList);
foreach (CookieCollection coll in lst.Values)
list.AddRange(coll.Cast());
}
return list;
}
public class QQGroupJson { public int code; public QQGroupList data; }
public class QQGroupList { public List group; }
public class QQGroupInfo { public string groupid; public string groupname; public int count; }
public class QQMemberJson { public int code; public QQMemberData data; }
public class QQMemberData { public List item; }
public class QQMember { public string uin; public string nick; public string iscreator; public string ismanager; }
var cookies = _cookieContainer; // 登入後取得的 CookieContainer
int gtk = GetGTK(cookies);
string uin = "你的QQ號";
string rand = new Random().NextDouble().ToString();
// 1. 獲取群列表
string groupUrl = $"https://qun.qzone.qq.com/cgi-bin/get_group_list?uin={uin}&random={rand}&g_tk={gtk}";
string json = HttpGet(groupUrl, cookies);
json = json.Substring(10, json.Length - 12); // 去除 _Callback() 包裹
var groups = JsonConvert.DeserializeObject(json).data.group;
// 2. 獲取指定群成員
string gid = groups[0].groupid; // 選擇第一個群
string memberUrl = $"https://qun.qzone.qq.com/cgi-bin/get_group_member?uin={uin}&groupid={gid}&random={rand}&g_tk={gtk}";
json = HttpGet(memberUrl, cookies);
json = json.Substring(10, json.Length - 12);
var members = JsonConvert.DeserializeObject(json).data.item;
foreach (var m in members)
Console.WriteLine($"{m.nick} ({m.uin}) -> {m.uin}@qq.com");
public static string HttpGet(string url, CookieContainer cookies)
{
var req = (HttpWebRequest)WebRequest.Create(url);
req.CookieContainer = cookies;
req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0";
using (var resp = (HttpWebResponse)req.GetResponse())
using (var stream = resp.GetResponseStream())
using (var reader = new StreamReader(stream, Encoding.UTF8))
return reader.ReadToEnd();
}
從模擬登入到解析 g_tk 算法、調用內部介面獲取群成員,完整展示了逆向工程 Web 介面的典型流程。理解 Cookie 容器遍歷、JS 算法移植、JSONP 回調剝離等技巧,對爬蟲開發、API 測試、自動化測試均有參考價值。