目前有很多的SNS社群或類SNS的網站,例如開心、51、校内等,但是發現大多數社群在邀請好友的時候都沒有提供對QQ郵箱或者QQ空間好友清單擷取的功能,不過似乎海内支援,但是網上相關QQ的文章還不是很多,希望這篇文章能給你帶來一些幫助。
QQ空間及郵箱登陸的校驗方式:
QQ空間及郵箱登陸的時候,使用者輸入的密碼首先會被頁面中的一段Js加密,然後加密過後的密碼會加上驗證碼形成一個新的字串,接着這個新的字串被MD5(32位)加密,加密過後形成最終的密碼,這就是我們為什麼經常會發現我們在送出的時候我們的密碼框中的密碼個數會增長的原因,接着在你送出的時候,目前的這個請求會将前一次擷取驗證碼傳回的set-cookie值加入到目前請求頭的cookie中以保持驗證碼請求與目前請求的一緻性,然後再一并将新的密碼和使用者名Post到QQ的伺服器上去。
好了,看了上面的QQ校驗方式以後,我們再來看看怎樣使用代碼來登入QQ空間及郵箱來擷取我們需要的好友和聯系人。
QQ空間及郵箱自動登入擷取聯系人的解決方案:
QQ空間及郵箱登陸首先我們需要将使用者輸入的密碼進行Js加密,但是我們在使用代碼登入的時候我們并沒有使用到浏覽器,那我們怎樣驅動Js呢?不用怕,在Java和.NET中都有相應的方式在伺服器代碼端驅動JS,這裡着重講一下.NET的方法:
首先到微軟的網站上下載下傳Windows Script Control,它是一個ActiveX(R) 控件。下載下傳安裝完成後,建立一個C#應用程式項目,在解決方案資料總管中選中引用節點, 右鍵點選選擇添加引用菜單,彈出添加引用對話框,單擊浏覽找到安裝Windows Script Control的目錄,選取msscript.ocx檔案确定。那麼在引用節點下會增加一個MSScriptControl元件,下面是他Interop 後的所有對象。
<!--[if !vml]-->
<!--[endif]-->
ScriptControl 對支援 ActiveX(TM) Script 的宿主 Script 引擎提供簡單接口。接下來我們對被轉化成ScriptControlClass類的ScriptControl的屬性和方法進行一些說明。
屬性
AllowUI 屬性:應用于 ScriptControl 本身或 Scirpt 引擎顯示的使用者界面元素,可讀寫。
CodeObject 屬性:傳回對象,該對象用于調用指定子產品的公用成員。隻讀。
Error 屬性:傳回 Error 對象,其中包含所發生的最後一個錯誤的相關詳細資訊。隻讀。
Language 屬性:設定或傳回正在使用的 Script 語言名稱。可讀寫。
Modules 屬性:為 ScriptControl 對象傳回子產品集合。隻讀。
Procedures 屬性:傳回在指定子產品中定義的過程集合。隻讀。
SitehWnd 屬性:設定或傳回視窗的 hWnd,通過執行 Script 代碼,此視窗用于顯示對話框和其他使用者界面元素。可讀寫。
State 屬性:設定或傳回 ScriptControl 對象的模式。可讀寫。
Timeout 屬性:設定或傳回時間(毫秒),此時間後使用者可選擇中止 Script 代碼的執行或允許代碼繼續執行。可讀寫。
UseSafeSubset 屬性:設定或傳回 Boolean 值,指明宿主應用程式是否有保密性要求。如果宿主應用程式需要安全控制,則 UseSafeSubset 為 True,否則為 False。可讀寫。
方法
AddCode 方法:向子產品添加指定代碼。可多次調用 AddCode 方法。
AddObject 方法:使主機對象模型對 Script 引擎可用。
Eval 方法:計算表達式并傳回結果。
ExecuteStatement 方法:執行指定的語句。
Reset 方法:放棄所有已經添加到 ScriptControl 中的 Script 代碼和對象。
Run 方法:運作指定過程。
事件
Error 事件:出現運作時錯誤時,發生此事件。
Timeout 事件:當超出了 Timeout 屬性指定的時間且使用者在結果對話框中標明了 End 時,發生此事件。
說明:
AllowUI 屬性如果設定為false,則顯示對話框之類的語句不起作用,如在 VBScript 中MsgBox 語句,JavaScript中的alert等,并且如果執行的腳本超出TimeOut設定的毫秒數,也不會跳出超出時間提醒的對話框,反之則相反;重新設 置 Language 屬性會清空AddCode加載的代碼;對于TimeOut屬性,發生逾時時,ScriptControl 檢查對象的 AllowUI 屬性,确定是否允許顯示使用者界面元素。
為了使控件更容易使用,用ScriptEngine類封裝一下,下面是完整代碼:
using System;
using MSScriptControl;
using System.Text;
namespace ScriptNameSpace
{
/// <summary>
/// 腳本類型
/// </summary>
public enum ScriptLanguage
{
/// <summary>
/// JScript腳本語言
/// </summary>
JScript,
/// <summary>
/// VBscript腳本語言
/// </summary>
VBscript,
/// <summary>
/// JavaScript腳本語言
/// </summary>
JavaScript
}
/// <summary>
/// 腳本運作錯誤代理
/// </summary>
public delegate void RunErrorHandler();
/// <summary>
/// 腳本運作逾時代理
/// </summary>
public delegate void RunTimeoutHandler();
/// <summary>
/// ScriptEngine類
/// </summary>
public class ScriptEngine
{
private ScriptControl msc;
//定義腳本運作錯誤事件
public event RunErrorHandler RunError;
//定義腳本運作逾時事件
public event RunTimeoutHandler RunTimeout;
/// <summary>
///構造函數
/// </summary>
public ScriptEngine()
: this(ScriptLanguage.VBscript)
{ }
/// <summary>
/// 構造函數
/// </summary>
/// <param name="language">腳本類型</param>
public ScriptEngine(ScriptLanguage language)
{
this.msc = new ScriptControlClass();
this.msc.UseSafeSubset = true;
this.msc.Language = language.ToString();
((DScriptControlSource_Event)this.msc).Error += new DScriptControlSource_ErrorEventHandler(ScriptEngine_Error);
((DScriptControlSource_Event)this.msc).Timeout += new DScriptControlSource_TimeoutEventHandler(ScriptEngine_Timeout);
}
/// <summary>
/// 運作Eval方法
/// </summary>
/// <param name="expression">表達式</param>
/// <param name="codeBody">函數體</param>
/// <returns>傳回值object</returns>
public object Eval(string expression, string codeBody)
{
msc.AddCode(codeBody);
return msc.Eval(expression);
}
/// <summary>
/// 運作Eval方法
/// </summary>
/// <param name="language">腳本語言</param>
/// <param name="expression">表達式</param>
/// <param name="codeBody">函數體</param>
/// <returns>傳回值object</returns>
public object Eval(ScriptLanguage language, string expression, string codeBody)
{
if (this.Language != language)
this.Language = language;
return Eval(expression, codeBody);
}
/// <summary>
/// 運作Run方法
/// </summary>
/// <param name="mainFunctionName">入口函數名稱</param>
/// <param name="parameters">參數</param>
/// <param name="codeBody">函數體</param>
/// <returns>傳回值object</returns>
public object Run(string mainFunctionName, object[] parameters, string codeBody)
{
this.msc.AddCode(codeBody);
return msc.Run(mainFunctionName, ref parameters);
}
/// <summary>
/// 運作Run方法
/// </summary>
/// <param name="language">腳本語言</param>
/// <param name="mainFunctionName">入口函數名稱</param>
/// <param name="parameters">參數</param>
/// <param name="codeBody">函數體</param>
/// <returns>傳回值object</returns>
public object Run(ScriptLanguage language, string mainFunctionName, object[] parameters, string codeBody)
{
if (this.Language != language)
this.Language = language;
return Run(mainFunctionName, parameters, codeBody);
}
/// <summary>
/// 放棄所有已經添加到 ScriptControl 中的 Script 代碼和對象
/// </summary>
public void Reset()
{
this.msc.Reset();
}
/// <summary>
/// 擷取或設定腳本語言
/// </summary>
public ScriptLanguage Language
{
get { return (ScriptLanguage)Enum.Parse(typeof(ScriptLanguage), this.msc.Language, false); }
set { this.msc.Language = value.ToString(); }
}
/// <summary>
/// 擷取或設定腳本執行時間,機關為毫秒
/// </summary>
public int Timeout
{
get { return 0; }
}
/// <summary>
/// 設定是否顯示使用者界面元素
/// </summary>
public bool AllowUI
{
get { return this.msc.AllowUI; }
set { this.msc.AllowUI = value; }
}
/// <summary>
/// 宿主應用程式是否有保密性要求
/// </summary>
public bool UseSafeSubset
{
get { return this.msc.UseSafeSubset; }
set { this.msc.UseSafeSubset = true; }
}
/// <summary>
/// RunError事件激發
/// </summary>
private void OnError()
{
if (RunError != null)
RunError();
}
/// <summary>
/// OnTimeout事件激發
/// </summary>
private void OnTimeout()
{
if (RunTimeout != null)
RunTimeout();
}
private void ScriptEngine_Error()
{
OnError();
}
private void ScriptEngine_Timeout()
{
OnTimeout();
}
}
}
在找到.NET驅動JS的方法以後,那麼我們還需要找到QQ用來加密的那個JS檔案,具體的JS檔案大家可以到QQ的登入頁面去DownLoad,如果不知道是哪一個JS大家可以通過抓包的方式去找到,這裡就不再贅述了。下面我們就可以用MSScriptControl來驅動JS擷取第一次加密的密碼了,方法如下:
/// <summary>
/// 得到第一次加密後的密碼
/// </summary>
/// <param name="jsFilePath">js檔案</param>
/// <param name="funcName">加密的方法名</param>
/// <param name="paramers">加密方法需要傳進的參數(一個是密碼,另一個是頁面上可擷取的一個标志碼)</param>
/// <returns>加密過後的密碼</returns>
private object GetPassword(string jsFilePath, string funcName, params object[] paramers)
{
StreamReader reader = new StreamReader(jsFilePath);
string sScript = reader.ReadToEnd();
ScriptEngine se = new ScriptEngine(ScriptLanguage.JavaScript);
object obj = se.Run(funcName, paramers, sScript);
return obj;
}
通過上面的這個函數我們就可以對密碼進行第一次加密了,下一步我們需要擷取驗證碼。
我們可以使用HttpWebRequest類來Get請求這個位址:http://ptlogin2.qq.com/getimage來擷取驗證碼,不過一定要記住把這次請求傳回的cookie儲存下來,我們可以将它儲存到CookieContainer對象中,以便将這次傳回的cookie加入到登入POST請求的頭當中保持前後請求的一緻性。
現在我們将獲得的驗證碼轉化成流或者其他的方式供不同的平台輸出,我們發現QQ空間及郵箱的驗證碼生成的圖像不是太複雜,是以可以考慮使用圖像識别的方式自動的擷取圖像對應的字元(現在有很多第三方驗證碼識别軟體),當然為了成功率考慮,最好還是讓使用者手動的輸入。
好了有了驗證碼了,我們現在就将驗證碼字元加上剛剛我們加過密的密碼形成新字串,再通過MD5加密
string pwd=FormsAuthentication.HashPasswordForStoringInConfigFile(this.Password, "MD5").ToLower()
形成最終我們需要的密碼。
最後我們就可以将使用者輸入的使用者名、密碼一并POST到QQ相應的位址上去,這樣我們就成功登陸QQ空間或郵箱了。登入成功過後你當然就可以擷取聯系人好友等等的操作了。
好了,文章就寫到這了,希望對大家有用。^^ Brave chen