前言
通過前兩篇的代碼編寫已經能正常模擬QQ登陸,拿到cookie也就是我們進行以後相關操作的金 鑰匙。這篇文章將通過代碼的方式去獲取登陸QQ賬號的群列表,某群裡面的群成員列表。
本文重點:
1、抓包獲取QQ群列表訪問地址
2、抓包獲取QQ群成員列表
3、參數值計算,gtk的計算 方法(網上幾乎找不到的計算方法)
4、處理返回值
本文完成這一系列也基本算是完成了,到此 篇為止,可正常獲取群成員,當然也就是拿到了QQ郵箱,如果想進行其他操作的話,同樣也可以用次方式來實 現。
抓包
1、獲取QQ群列表
首先我們用登陸成功的QQ去訪問我們的群空間,群空間的頭 部有我的群,在此我們可以看到登陸QQ上面所有的群列表,也就是說此頁面必有返回該列表的請求地址,做 web開發的大體都差不多,這種東西多用Js處理,相信我們也可以抓到此地址,如下圖所示。
相比而言這個地方的抓包不需要挨個去看了,這次給的JS請求地址很直觀通過名字我們一眼就可以看出是 群列表,在抓包過程中我們會發現有一條get_group_list的地址,不用說這個就是了,查看這個js返回的數據 剛好便是群列表,如下圖
右側group便是登陸QQ上所有的qq群,包含總數目等信息。
相關請求地址:
http://qun.qzone.qq.com/cgi-bin/get_group_list?uin={0}&ua=Mozilla%2F5.0%20(Windows% 20NT%206.1%3B
%20WOW64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome% 2F28.0.1500.95%20Safari
%2F537.36&random={1}&g_tk={2}
參數0:賬號,參數1:隨即數 ,參數2:此次訪問的gtk值
2、獲取訪問群空間的成員列表
訪問具體群空間,在群空間右側有 群成員選項,選擇以後變回返回所有的群成員,如圖
在點擊群成員列表的時候我們通過抓包工具可抓起返回數據的地址,這個也很明顯,很直觀 get_group_member,一看便知是返回群成員,查看返回的數據,下面給出fiddler和谷歌浏覽器返回的數據格 式
請求地址:
http://qun.qzone.qq.com/cgi-bin/get_group_member?
uin={0}&groupid= {2}&random={3}&g_tk={4}
參數0:賬號,參數1:群號,參數2:隨即數,參數3:此次訪問的 gtk值
代碼部分
1、gtk參數的計算(此參數的計算方法幾乎沒有)
通過上述抓包我們看到 ,數據包主體部分最後一個參數就是g_tk值,一般是一串數字。那這個值到底怎麼算出來的呢?因為我們在網 頁登錄QQ的時候,騰訊都會通過cookies裡的skey值來計算,用js來算。既然在運算的時候執行了js腳本,那 麼我們就可以在抓包中獲得。那g_tk是通過什麼算法算出來的?其實很簡單,當我們得到skey後,循環取單字 符的二進制並取左值.累加之後就得到後面的g_tk值了,這聽上去很復雜,不過算法不用我們自己寫,我們只 需要執行在騰訊網頁登錄的時候所執行的那個js腳本就可以了。在這裡我已經將此算法轉化成c#代碼:
/// <summary> /// 計算gtk /// </summary> /// <returns></returns> public static Int32 GetGTK(List<Cookie> cookies) { int gtk = 0; foreach (var item in cookies) { if (item.Name == "skey") { int hash = 5381; string str = item.Value; for (int i = 0, len = str.Length; i < len; ++i) { hash += (hash << 5) + str.ElementAt(i); } gtk = hash & 0x7fffffff; } } return gtk; }
同時也給出一個遍歷CookieContainer的方法:
/// <summary> /// 遍歷CookieContainer /// </summary> /// <param name="cc"></param> /// <returns></returns> public static List<Cookie> GetAllCookies(CookieContainer cc) { List<Cookie> lstCookies = new List<Cookie>(); Hashtable table = (Hashtable)cc.GetType().InvokeMember("m_domainTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, cc, new object[] { }); foreach (object pathList in table.Values) { SortedList lstCookieCol = (SortedList)pathList.GetType().InvokeMember("m_list", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, pathList, new object[] { }); foreach (CookieCollection colCookies in lstCookieCol.Values) foreach (Cookie c in colCookies) lstCookies.Add(c); } return lstCookies; }
唯一一個重要的參數解決以後便是請求地址,返回數據了,收獲果實的時候了。
2 、獲取群列表
下面先寫兩個處理返回Json字符串的方法,本文沒有用第三方的處理工具,二是用.net 3.5以後出現的System.Web.Extensions.dll中提供的序列化方法實現。
//json解析群列表 public class QQGropJson { public int code { get; set; } public int defaults { get; set; } public QQGrouplist data { get; set; } public string message { get; set; } public int subcode { get; set; } } public class QQGroupInfo { public int count { get; set; } public int flag { get; set; } public string groupid { get; set; } public string groupname { get; set; } } public class QQGrouplist { public List<QQGroupInfo> group { get; set; } public int guid { get; set; } public int total { get; set; } } //以下三個類解析群成員 public class QQMember { public string iscreator { get; set; } public string ismanager { get; set; } public string nick { get; set; } public string uin { get; set; } } public class QQGroup { public int code { get; set; } public int subcode { get; set; } public string message { get; set; } public int defaults { get; set; } public QQInfo data { get; set; } public int level { get; set; } public string nick { get; set; } public int option { get; set; } public int total { get; set; } } public class QQInfo { public int alpha { set; get; } public int bbscount { get; set; } public int classs { get; set; } public string createtime { get; set; } public int filecount { get; set; } public string fingermemo { get; set; } public string groupmemo { get; set; } public string groupname { get; set; } public List<QQMember> item { get; set; } }
過濾返回的_callback標簽:
/// <summary> /// 過濾返回的標簽_callback(); /// </summary> /// <param name="retstr"></param> /// <returns></returns> public static string FilterReturnstring(string retstr) { string result = retstr.Substring(10, retstr.Length - 12); return result; }
解析返回的json數據
//解析json字符串返回群信息 public static List<QQGroupInfo> GetGropList(string jsonstring) { var js = new JavaScriptSerializer(); QQGropJson grouplist = new QQGropJson(); grouplist = js.Deserialize<QQGropJson>(jsonstring); return grouplist.data.group; }
獲取群列表
var list = HttpHelper.GetAllCookies(_cookie); string gtk = HttpHelper.GetGTK(list).ToString(); ////群列表 string grouplisturl = string.Format(@"http://qun.qzone.qq.com/cgi- bin/get_group_list?uin={0}&random={1}&g_tk={2}",usernum,rand.NextDouble(),gtk); string tmp = HttpHelper.GetHtml(grouplisturl, _cookie); List<QQGroupInfo> grouplist = HttpHelper.GetGropList (HttpHelper.FilterReturnstring(tmp));
解析群成員列表json的方法
/// <summary> /// 群成員列表 /// </summary> /// <param name="jsonstring"></param> /// <returns></returns> public static List<QQMember> GetMemberList(string jsonstring) { var js = new JavaScriptSerializer(); QQGroup group = new QQGroup(); group = js.Deserialize<QQGroup>(jsonstring); return group.data.item; }
以上方法grouplist便是群列表賬號
獲取某一群的成員列表
////群成員 var list = HttpHelper.GetAllCookies(_cookie); string gtk = HttpHelper.GetGTK(list).ToString(); string groupnumber = this.cmbQQgroup.SelectedValue.ToString(); string groupmemberlist = string.Format(@"http://qun.qzone.qq.com/cgi- bin/get_group_member?uin={0}&groupid={1}&random={2}&g_tk={3}", usernum, groupnumber,rand.NextDouble(),gtk); string tmp = HttpHelper.GetHtml(groupmemberlist, _cookie); grouplist = HttpHelper.GetMemberList(HttpHelper.FilterReturnstring(tmp));//成員列表
文章到此,群成員拿到了,同時也拿到了QQ郵箱。
結語
本文的重點在於拿著cooke這 把鑰匙去開門,相對比較簡單,唯一一個比較難的就是gtk參數的計算方法,這個在QQ空間對日志等操作的時 候是必不可少的參數。
時間倉促,代碼及文章比較雜亂,有什麼出錯的地方歡迎指出。若資料有用, 幫忙頂一下。