前言:
爬取普通的文本网页非常容易,但爬取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。注意,下载、安装和破解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; }