c#爬取Silverlight網頁
前言:
爬取普通的文字網頁非常容易,但爬取Silverlight的網頁程式碼時,有時候可能會加密。這樣就會很麻煩了。下面就爬取網站http://zx.bjmemc.com.cn/ (北京空氣質量網)進行說明。
任務:
網站http://zx.bjmemc.com.cn/顯示的內容如下圖所示。我們的任務就是將空氣質量資料抓取下來。
工具:
1、fiddler,http://www.telerik.com/fiddler,一款優秀的網頁請求分析工具
2、reflector,http://download.csdn.net/detail/qing_lgq/6764265,.net原始碼破解工具
步驟:
1、安裝fiddler和reflector,並破解reflector。注意
2、開啟fiddler,使之處於監聽狀態。
3、用瀏覽器開啟網頁:http://zx.bjmemc.com.cn/。(此網站做得非常好,為了防止爬取,一旦使用者開啟瀏覽器自帶的developer tool,就不會載入任何東西,為他們點個贊,這就是為什麼我們必須用fiddler等分析工具的原因)
4、待網頁載入完畢,fiddler便已經抓取到了所有的網頁請求,如下圖所示。在此簡要說明一下fiddler的頁面佈局。左側是所有的網頁請求,右上是傳送請求資訊,右下是對應的接收資訊。最下邊狀態列的左邊有兩個按鈕,左側的是監聽開關(下圖是關閉狀態,未顯示該按鈕),右側的ie圖示是監聽物件,可以選擇監聽網頁請求、非網頁請求還是所有程序的請求。注意請求資訊和接收資訊視窗都有很多tag,根據自己的需求切換。
5、在fiddler左側找到網頁的直接請求,即上圖的左側的第二項,返回資訊視窗的tag切換到textview。在視窗的搜尋框輸入xap,快速找到<param name="source" value="ClientBin/BEPB.xap"/>,其中的value屬性值就是後臺Silverlight的程式碼。
6、將ClientBin/BEPB.xap與當前網頁網址拼接為http://zx.bjmemc.com.cn/ClientBin/BEPB.xap,用瀏覽器開啟,瀏覽器自動下載BEPB.xap檔案。
7、修改BEPB.xap檔名字尾為BEPB.zip,用壓縮檔案解壓縮,得到的資料夾內容如下圖所示。這些就是Silverlight後臺檔案以及引用檔案。其中BEPB.dll是該Silverlight專案的核心檔案。
8、用reflector開啟BEPB.dll,如下圖所示。左側是方法名/名稱空間名,右側是對應的程式碼。接下來便是一個痛苦的過程了,得一個一個分析,找到金鑰。但是還是有章可循。
9、fiddler繼續上場。如下圖所示。經過分析,獲得空氣質量資料的是第7個請求,選中它,右下部分的返回資訊視窗的tag選擇HexView,表示以十六進位制顯示。右上部分發送資訊視窗選擇Raw,可是傳送時post的資料呼叫了DataService下的GetWebData方法,在reflector的搜尋框中輸入此兩個關鍵字,如此順藤摸瓜,便能很快找到加密的密碼。
10、上圖右下返回資訊視窗的hexview中,兩端有部分資訊並不是加密資訊,分析需要將返回資訊的兩端無用位元組刪除。
以下附上Silverlight加解密的程式碼。具體抓取程式碼便不公佈了,具體情況具體分析,這裡只是提供一個大體思路。
/// <summary>
/// hmacSha1演算法加密(生成長度40)
/// </summary>
/// <param name="signatureString">加密明文</param>
/// <param name="secretKey">加密金鑰</param>
/// <param name="raw_output">是否輸出原始編碼</param>
/// <returns></returns>
public static object hmacSha1(string signatureString, string secretKey, bool raw_output = false)
{
var enc = Encoding.UTF8;
HMACSHA1 hmac = new HMACSHA1(enc.GetBytes(secretKey));
hmac.Initialize();
byte[] buffer = enc.GetBytes(signatureString);
if (raw_output)
{
return hmac.ComputeHash(buffer);
}
else
{
return BitConverter.ToString(hmac.ComputeHash(buffer)).Replace("-", "").ToLower();
}
}
/// <summary>
/// 使用AES加密字串
/// </summary>
/// <param name="encryptString">待加密字串</param>
/// <param name="encryptKey">加密密匙</param>
/// <param name="salt">鹽</param>
/// <returns>加密結果,加密失敗則返回源串</returns>
public static string EncryptAES(string encryptString, string encryptKey, string salt)
{
AesManaged aes = null;
MemoryStream ms = null;
CryptoStream cs = null;
string str = null;
try
{
Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(encryptKey, Encoding.UTF8.GetBytes(salt));
aes = new AesManaged();
aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
aes.IV = rfc2898.GetBytes(aes.BlockSize / 8);
ms = new MemoryStream();
cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write);
byte[] data = Encoding.UTF8.GetBytes(encryptString);
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
str = Convert.ToBase64String(ms.ToArray());
}
catch
{
str = encryptString;
}
finally
{
if (cs != null)
cs.Close();
if (ms != null)
ms.Close();
if (aes != null)
aes.Clear();
}
return str;
}
/// <summary>
/// 使用AES解密字串
/// </summary>
/// <param name="decryptString">待解密字串</param>
/// <param name="decryptKey">解密密匙</param>
/// <param name="salt">鹽</param>
/// <returns>解密結果,解謎失敗則返回源串</returns>
public static string DecryptAES(string decryptString, string decryptKey, string salt)
{
AesManaged aes = null;
MemoryStream ms = null;
CryptoStream cs = null;
string str = null;
try
{
Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(decryptKey, Encoding.UTF8.GetBytes(salt));
aes = new AesManaged();
aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
aes.IV = rfc2898.GetBytes(aes.BlockSize / 8);
ms = new MemoryStream();
cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write);
byte[] data = Convert.FromBase64String(decryptString);
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
str = Encoding.UTF8.GetString(ms.ToArray(), 0, ms.ToArray().Length);
}
catch
{
str = decryptString;
}
finally
{
if (cs != null)
cs.Close();
if (ms != null)
ms.Close();
if (aes != null)
aes.Clear();
}
return str;
}