用Fiddler和JScript捕获网页

用Fiddler和JScript捕获网页

 

因为要写篇分析报告,需要反复从网页里提取数据,因此作了些http和网页捕获方面的研究。下面把过程回顾一下,做个总结、以利于下次工作的提高。

1、开始的时候准备用VB编一个网页自动循环下载软件,所以去下载了VS2008和MSDN。选VB是考虑到能方便地过渡到excel的宏VBA,写数据分析论文不可能不用到excel,没道理画个图、作个统计都要自己编程吧。而且excel本身也具备从网页导入数据的功能,如果可能的话,让办公室里的小妹帮个忙,能省不少功夫!

2、然后又搜索http捕获、分析教程和工具,准备照版煮碗,仿照开发一个。
其中Fidder比较有吸引力,许多人提到它可扩展性很强。

打开Fiddler的网站一看,几个大大的"Building Extensions with C#, VB.NET, Managed C++",用C、VB扩展?!

顿时眼睛一亮,窃喜、相见恨晚。

3、不过还是犯了先入为主的毛病,走了弯路,一根筋的考虑VB,不停的在VS的IDE界面和Fiddler之间来回编译、切换。

后来发现Fiddler是直接支持脚本的,可以用JScript直接写脚本。而且在它的《FiddlerScript CookBook》(大概可以翻译成《随身手册》吧)里有各种情况下的功能扩展举例,很容易上手。

这下方便多了,完全抛开VS、VB,效率大大提高,写完脚本马上运行,哪里用得着编译那么麻烦。

倒是MSDN起了很大作用,我之前完全没学过JScript,好在现在的开发工具之间差异很小,MSDN里每个例子都有不同开发语言的版本,对照看,触类旁通,很容易理解。

Fiddler的功能和使用方法我不介绍了,网上很容易找到,我提供一个链接,一个CSDN网友贡献的《Fiddler工具使用说明》

我用的是Fiddler2.2.2,下面说说我做的功能扩展。

另外我只说说开发时的思路和落脚点,编码就不放上来了。

前者是因为Fiddler的网站和论坛提供的资料太详尽了,大家多半只是不知道该怎么下手,把思路放上来给个启发、避免弯路我想足够了。

后者是因为每个人的需求千差万别,根本不可能简单复制。当然必要的编码我还是会放的。

 

一、准备工作

MSDN之类的开发指引不用说了。

Fiddler如果要调用外部动态链接库,例如.NET,应先通过菜单Tools——Fiddler Options——Extensions——References指示路径。

 

Fiddler功能扩展脚本文件是CustomRules.js,默认路径“我的文档”——Fiddler2——Scripts,可以用文本工具直接编辑。如果通过菜单Rules——Customize Rules(或者Ctrl-R)直接调用Fidder的脚本编辑工具Fiddler ScriptEditor更好,编辑完了保存时会自动重载,立马生效,很方便。

脚本分两大块。

第一块是import,不用多说,申明准备调用的动态链接库。

第二块是class Handlers,处理方法。自定义变量、常数、类型、函数、方法都放在这里,是下一步的主要工作空间。

其中有两个函数:

static function OnBeforeRequest(oSession: Session);

static function OnBeforeResponse(oSession: Session);

刚开始的时候我理解有问题,以为是在用户请求之前、响应给用户之前。我就奇怪了,请求之前?我还没下指示,你知道干什么?等着不就得了。响应之前?既然准备回应了,就是已经收到请求了,那还不赶快照指示做、还要干别的?

想想不对劲,回过头仔细研究《FiddlerScript CookBook》,转念一想会不会是Fiddler请求之前、Fiddler响应之前,用户提交请求、Fiddler截获、Fiddler把请求提交给服务器之前,或者Fiddler收到服务器响应、提交给用户之前。

去Fiddler论坛找版主讨教,果然如此。估计刚接触Fiddler无从下手的朋友多半也会卡在这里。

这下所有关节全部打通,所有要求软件自动完成的功能都可以插在这两个函数里。

 

于是整体设想这样:

1、巡航。

人工操作浏览器在目标网页范围内巡航。

2、标识。

客户/服务器对答增加自定义标识,Fiddler实时监视客户端和服务器之间的对话,把疑似符合既定模式的对话标识出来。

3、触发。

人工精确判断、刷选对话,设置参数、循环变量、转存路径,触发Fiddler提取对话中的请求体,改造、重新编码,再重发服务器。这个功能由鼠标右键触发。

4、循环。

改造Fiddler接收模块,放开浏览器,Fiddler直接分析服务器应答,符合要求的,下载存盘、调用压缩工具,进入下一个循环;或者,超时则重新请求;

上面1、3是用户操作,由用户触发。2、4是软件操作,自动触发。

 

二、自定义对话标识

Fiddler已经给对话定义了很多标识,用户还可以增加自己的标识,详细说明见Fiddler对话标识。

这里又有个难点,Fiddler截获的原始请求和应答都是HTTP编码字符串,用户无法直接识别,必须反向解码、还原成用户可识别字符,方便于其它处理。反过来,如果要把用户请求发给服务器,就要按HTTP规范编回码去。

关于各种编码的理论渊源我也很糊涂,但大体意思应该如下,不对之处请高手指教。

用户编码,用户在浏览器里看到的字符,有不同的编码,例如GB、UTF-8等。

应用协议,软件之间有自己的沟通方式,它们也不能直接处理用户编码,要把用户数据编码成软件之间的数据,才能被它们利用。例如HTTP、FTP协议,有各自编码规则。

然后是网络协议,例如Winsock、TCP/IP。再往下还有网卡驱动程序、机器码等。这些协议我们不用去管它,Fiddler已经把它们解码回HTTP编码了。

编码不光数据转换,还包括压缩、打包,添加必要的标识和指令等。解码则相反。

Fiddler夹在浏览器和网络之间,已经自带了一些编码、解码函数,再结合微软.NET提供的大量编码转换函数,完全可以实现不同协议、编码之间自由、任意转换。

例如我的一段代码,oSession表示Fiddler捕获的一个对话实例:

oSession.utilDecodeRequest();    //请求体解除chunked编码,我推测是转成了二进制流。

var oRBB = Encoding.UTF8.GetString( oSession.requestBodyBytes );  //把解码后的字符串按utf8编码返回。

var sRequestBody = HttpUtility.UrlDecode( oRBB, Encoding.UTF8 );    //按url标准返回字符串。

HTTP传输消息时都要chunked-encoding,什么意思大家自己查吧,我解释不了,我只会盯着变量,跟踪、监控,然后对着样版来。

Encoding.UTF8.GetString是.NET的System.Text里的函数,HttpUtility.UrlDecode是.NET的System.Web里的函数,所以调用前先要在脚本文件CustomRules.js的最前面加上:

import System.Text;

import System.Web;

 

请求体(sRequestBody)包含目标资源路径和参数,据此能判断对话“主题类型”。我做了些正则表达式模版,每个请求和这个模版比较,相符合的,加标识;否则,略过。(JScript的正则表达式有它自己的特点,我准备另外撰文写点体会。)

判断标识是否存在和增加一个标识的函数分别是:

oSession.oFlags.ContainsKey( "用户标识");

oSession.oFlags.Add( "用户标识");

增加了用户标识后,用oSession.RefreshUI()刷新一下,标识就显示出来了。

注意:

1、可以只给符合要求的对话加标识。不符合要求的对话,不光没有标识值,甚至连这个标识都可以没有。

2、增加自定义标识前应判断该标识是否已经存在,否则会引发脚本异常终止。如果存在只需重新赋值。

 

我把“主题类型”识别放在了onBeforeRequest函数里,请求一提交就标识主题。又在onBeforeResponse函数里放置了长度、日期等应答特征的的识别,收到应答就标识出来。

 

三、用户自定义界面

定义了用户标识,形成对话的内在属性,还要把它绑定到屏幕介面、显示出来给用户看到,方法见Fiddler配置列。我比较喜欢使用其中的这种方法:

public static BindUIColumn("HTTPMethod")

function CalcMethodCol(oS: Session){

   if (null != oS.oRequest){

      return oS.oRequest.headers.HTTPMethod; 

   }else{

      return String.Empty; 

   }

}

简单明了,Fiddler刷新的时候就有显示了。

上面例子里if块的{}括号是我加的,Fiddler自己的例子没有用{}括号。我试过不加,但Fiddler不是总能正确识别,不知道为什么。于是干脆都加了。读代码时也省点脑精。

另外鬼子的习惯跟咱们中国人可能真的很不同,Fiddler作判断时都把运算量(常量)放逻辑运算符前面、被运算量(变量)放后面,例如上面if ( null != oS.oRequest )我全部按自己习惯颠倒过来,没有任何问题。

这段代码和下面鼠标右键菜单都可以放在class Handlers块内任何地方。

 

四、自定义定义鼠标右键菜单

在设想3中,计划用鼠标右键菜单实现人工选择对话,触发Fiddler重新请求网页。《FiddlerScript CookBook》里context-menu样版如下:

public static ContextAction("Open in Firefox")

function DoOpenInIE(oSessions: Fiddler.Session[]){

  if (null == oSessions){

    MessageBox.Show("Please choose at least 1 session."); return;

  }

  for (var x = 0; x < oSessions.Length; x++){

    System.Diagnostics.Process.Start("firefox.exe", oSessions[x].url);

  }

}

我下载的时候还是“Open in IE”,现在已经是火狐狸了,Fiddler没闲着啊!题外话。

右键菜单被激发时,会自动提交当前选择对话作为参数。注意这里还作了参数校验,以防还没有选择对话就调用该函数。

我因为只提取网页里的数据,不关心网页的显示,除了刚开始巡航到目的网页,后面都没必要打开浏览器,所以从这里开始由Fiddler直接请求网页。《FiddlerScript CookBook》里提交请求的方法是这样的:

FiddlerObject.utilIssueRequest(sRequest);

采用这个方法用户要自己组合sRequest串,添加HTTP头,我搞不懂,我从Fiddler论坛找到另一个方法:

FiddlerApplication.oPorxy.InjectCustomRequest(oSession.oRquest.headers, oSession.requestBodyBytes, true, false );

我记得Fiddler版主说过,InjectCustomRequest后两个参数现在没有任何意义,但让我照写。

oSession就用当前选择的对话,headers不变,什么cookie、session之类的,照搬原样。其实这也就是我标识、选择对话的原因,费事去搞headers了。

重发请求可能要调整参数。和前面定义用户标识一样,先解码被选择对话的请求体,随便采用什么方法置换sRequestBody里的参数,然后再重新编码、注入oSession,一鼓作气方法是:

    oSession.utilSetRequestBody( HttpUtility.UrlEncode( sRequestBody, Encoding.UTF8)); 

HTTP请求体中用符号&和=分隔、赋值参数,有特殊含义。但HttpUtility.UrlEncode编码的时候并不知道自己是在给参数编码,一视同仁,结果&和=也被编了码。因此还要把请求体中的&和=置换回来。

with ( oSession ){

   utilReplaceInRequest( '%3d','=');

   utilReplaceInRequest( '%26','&');

 }。

当然这样做存在着把本该作为参数值的&和=也置换回来的风险。好在到目前为止我的工作中还没有出现这种情况,等以后有必要时再考虑吧。

 

五、应答下载

应答存盘之前也要解码,方法是:

oSession.utilDecodeResponse();

然后就可以直接存盘了:

oSession.SaveResponseBody( sFileName );

 

如果要再调用外部软件对下载文件进行处理,例如压缩,方法是:

Utilities.RunExecutableAndWait( sExecute, sParams);

注意sExecute可以包含路径,但某些软件可能不支持带空格的路径,建议强制给路径加上引号。这个方法和上面那个InjectCustomRequest都是Fiddler自带,用VS的对象浏览器打开Fiddler,可以找到这两个方法。

 

 

好了,终于写完了,长长舒口气吧!

还有些东西没写清楚吧,不管了,先放上来接受一下检验,等有了更多的思路、理解再补充吧。

你可能感兴趣的:(用Fiddler和JScript捕获网页)