前言
通過前兩篇的代碼編寫已經能正常模擬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空間對日志等操作的時 候是必不可少的參數。
時間倉促,代碼及文章比較雜亂,有什麼出錯的地方歡迎指出。若資料有用, 幫忙頂一下。