百度指数对于从事SEO的关键词优化人员来说绝不陌生,这一指数可以在百度指数官网上(http://index.baidu.com)通过输入关键词来进行查询。其指数的呈现方式是flash图表的形式,如图所示:
对于做seo相关研究或开发seo相关工具的开发人员来说,可能有必要采集这一重要指数以供参考。但是由于这里的数据是以flash的方式呈现的,并且各种数据和交互过程都经过了加密处理(后面会详细分析),所以普通的采集方式自然是不可用了。在这种情况下,我们还能否通过程序获得原始的未经处理过的百度指数的数据呢?
不急,我们先来对百度指数的数据呈现过程和方式逐一进行分析:
当我们在百度指数首页输入关键词后,打开的页面是一个动态flash图表,这个flash文件是如何加载的呢?通过查看页面源文件,可以发现它是通过javascript脚本加载的flash,代码是var flashHot = baidu.swf.getMovie("trendAnalyser");其中的对象名称为trendAnalyser,但是我们无法直接找到这个对象,原因在于这个对象的定义过程是以加密脚本的方式生成的,密文就在这行代码的上方,诸如:eval(Dec('2035412***','7505'));的形式。其中前半部分为加密的密文,后半部分为解密的密码,eval函数的作用是执行解密出的字符串。那么这个Dec函数是什么样的呢?其实该函数的定义就存在于页面代码中,其引用的脚本代码为<script type="text/javascript" src="../js/abc.js"></script>,打开abc.js,就可以看到该解密函数了,通过在本地运行该函数,可以得到解密后的字符串,诸如:
baidu.swf.create( { 'id': "trendAnalyser", 'width': "492", 'height': "396", 'ver': "9.0.0", 'errorMessage': "Please download the newest flash player.", 'url': "../fla/TrendAnalyser8f7a43e0.swf", 'bgColor': "#FFFFFF", 'wmode': "transparent", 'vars': { keys: "百度", areas: "0", periods: ["2013-05-15|2013-06-13"], periodNames: ["null"], dv: 1, nc: 0, gateway: "http://index.baidu.com/gateway.php", snapshot: "http://index.baidu.com/main/show.php", key:"2490849166481b3801" } },"flashContent");
这样就可以看到本页加载的flash文件的地址了,对于本例而言,flash文件的地址为http://index.baidu.com/fla/TrendAnalyser8f7a43e0.swf。但是需要注意的是,即使查询的是同一个关键词,每一次所请求的页面加密代码的内容及密钥都是唯一的和变化的,并且每次加载的flash文件也是动态变化的。也就是说,即便上面解密和得到了加载flash文件的js脚本代码,但是该代码的时效性只有一次并且是唯一的,加载的flash文件也是动态的唯一的,时效性在这里暂时还无法判断。除此以外,上面解密出来的代码中还有一些参数,如keys,periods,areas,gateway,snapshot,key等,其作用我们后面再作探讨。
作一个假设,如果百度指数所加载的flash文件时效性是永久的,只是地址动态变化,那么到这一步,我们可以自行设计一个js脚本解析引擎,通过程序向百度指数请求数据,然后本地解析得到名文代码及flash地址,再通过分析flash对数据的处理方式,配合前面得到的各项参数,想必是可以解密得到原始数据的。
然而事实正如我们预计的一样,这个flash文件的时效性也只有一次,并且该文件也是动态生成的(非静态的flash资源文件),这通过分析flash文件的action script脚本代码可以看出。Analyser的init方法接收了来自于网页的等各项参数,然后调用loadIndexes(this._paramKeys, this._paramAreas, GlobalSetting.isDateCompare ? (this._paramPeriods) : ([]), parameters["key"]);方法请求数据,这里所传递的参数包括:keys(关键词),periods(数据区间),key(唯一时效密钥),这几个数据从上面的js脚本中都可以得到。
再来分析loadIndexes方法,它创建了基于gateway的一个连接,使用代码con.call("DataAccessor.getIndexes", responder, keysStr, areasStr, periodsStr, timeStr, MD5.encrypt(areasStr + new MovieClip().k4f1b3 + keysStr + periodsStr));提交数据到服务器。通过var responder:* = new Responder(function (param1:Object) : void{...}接收反馈。这里面的各参数与上面的调用参数一一对应,可以看到key即为timeStr。这里最后提交的一个参数是对前面几项参数进行MD5加密得到的结果。值得注意的是,这里多出了一个new MovieClip().k4f1b3的参数,这个参数的值怎么得到呢?我们在该flash的初始化代码中找到了它的定义,诸如:
var _loc_1:String = "628136f59e0527"; var _loc_2:* = _loc_1; _loc_2 = _loc_2; var _loc_3:* = _loc_2.split(""); _loc_3.reverse(); _loc_2 = _loc_3.join(""); var _loc_4:* = _loc_2.split(""); _loc_2.split("").splice(Math.floor(0.805243 * 14), Math.floor(0.805243 * 14)); var _loc_5:* = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""); while (_loc_4.length < 14) { _loc_4.splice(Math.floor(0.805243 * _loc_4.length), 0, _loc_5[Math.floor(0.805243 * _loc_5.length)]); } _loc_2 = _loc_4.join(""); var _loc_6:* = _loc_2.split(""); _loc_6 = _loc_2.split("").splice(7, 14).concat(_loc_6); _loc_2 = _loc_6.join(""); var _loc_7:* = _loc_2.split(""); var _loc_8:int = 0; while (_loc_8 < 14) { _loc_7[_loc_8] = Boolean(0.805243) ? (_loc_7[_loc_8].toUpperCase()) : (_loc_7[_loc_8].toLowerCase()); _loc_8++; } _loc_2 = _loc_7.join(""); MovieClip.prototype.k4f1b3 = _loc_2;
虽然这里也是一段解密过程的代码,但是通过执行,我们仍然可以得到MovieClip.prototype.k4f1b3这个全局变量的值,那么这个变量名称是固定的么?答案是否定的。那这个变量的解密过程是固定的么?答案也是否定的。那这个变量的值是固定的么?这个就更不用问了。通过分析多个加载的flash文件代码,我们可以确定,该全局变量每次请求独立生成,每次解密过程其中的解密参数也是动态变化的,因此导致了每次所提交数据的唯一性。 这里还没有完,在向服务器提交数据并得到反馈后,反馈回来的数据是怎么处理的呢?通过分析flash的代码,我们也不难看出,服务器回传的数据也是经过加密的,其解密过程的函数存在于flash的actionscript脚本中。同样我们要问了,这个解密的过程是固定的么?答案仍然是冰冷的否定。
归纳一下,百度指数的flash全局变量名称动态生成、全局变量取值方式动态生成、提交数据MD5加密、获取的数据加密传输、数据解密密钥动态生成、flash文件名称动态生成、flash对象js脚本定义代码加密、js脚本解密密钥动态生成……看来,百度在保护指数数据方面花了不少的功夫,那么在这样一种情况下,我们还能不能获得百度指数的原始数据呢?欣慰的是,答案自然是肯定的!不过这个过程却并不是那么容易。请看我们的风影百度指数采集工具:
通过风影百度指数采集工具,我们可以动态采集到从2006年6月1日至今的每一天的详细数据。由于数据是直接从百度官方获取,因此时效性及权威性与百度指数页面的数据完全一致。
(注:本文为 [风影网络工作室] 原创文章,未经书面许可,严禁转载和复制本站的任何信息,违者必究)