摩诘

我思故我在 常辨而常新

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  20 随笔 :: 2 文章 :: 370 评论 :: 14 引用
  示例下载

朋友问到这样一个问题,需要实现如下功能

1、   打开一家航空运输公司的查询网页,如http://www.skyteamcargo.com/en/tracking/,该页面有两个文本框,供用户输入业务代码,如180-36898035


2、   然后单击“Go”按钮后,下一个页面显示查询出来的结果


 

现在要求以上步骤都用程序自动实现,并把查询结果提取出来,以备后面进一步处理。
要完成这样的功能,首先要解决以下几个问题:

l          能够用程序在后台将数据Post到目标网页

l          能接收到对方返回的HTML结果页面

l          能够分析该页面,并将需要的结果提取出来

 经过一番研究和实验,我解决了以上几个问题,下面分别描述。

1.          用程序将指定数据Post到目标网页,并接收结果网页

我先用一个网络嗅探器来捕获用IE手工查询时浏览器和对方网页的HTTP协议交互过程,这里推荐用Ultra Network Sniffer,它有个Connection Monitor功能,可以监视某个程序的所有连接,这样就避免了其他无关数据包的干扰。如图


当在查询页面里输入数据后,点击“Go”按钮,嗅探器捕捉到的数据包如下图


请注意第三行,有一个Post数据包,这个就是我们需要的,

它所发送的网页地址为:http://www.skyteamcargo.com/en/tracking/tra_result.php

它所Post的数据为:awbpre=180&awbnum=36898035&x=17&y=9

很显然,18036898035是可以替换的,而且要注意的是发送的目标网页不再是我们在浏览器里直接输入的初始页面http://www.skyteamcargo.com/en/tracking/

下面我们用程序来发送这段数据,代码如下

     public static string GetPage(string url, string postData,string encodeType,out string err)

     {

         Stream outstream = null;

         Stream instream = null;

         StreamReader sr = null;

         HttpWebResponse response = null;

         HttpWebRequest request = null;

         Encoding encoding = Encoding.GetEncoding(encodeType);

         byte[] data = encoding.GetBytes(postData);

         // 准备请求...

         try

         {   

              // 设置参数

              request = WebRequest.Create(url) as HttpWebRequest;

              CookieContainer cookieContainer = new CookieContainer();

              request.CookieContainer = cookieContainer;

              request.AllowAutoRedirect = true;

              request.Method = "POST";

              request.ContentType = "application/x-www-form-urlencoded";

              request.ContentLength = data.Length;

              outstream = request.GetRequestStream();

              outstream.Write(data,0,data.Length);

              outstream.Close();

              //发送请求并获取相应回应数据

              response = request.GetResponse() as HttpWebResponse;

              //直到request.GetResponse()程序才开始向目标网页发送Post请求

              instream = response.GetResponseStream();

              sr = new StreamReader( instream, encoding );

              //返回结果网页(html)代码

string content = sr.ReadToEnd();

              err = string.Empty;

              return content;

         }

         catch(Exception ex)

         {

              err = ex.Message;

              return string.Empty;

         }

}

 

上面的代码很好懂,就不多费口水了,只谈几点要注意的问题:

如果你的程序需要保留SessionID,如ASP.NET中的那个SessionID。比如说自动登录后,可能还要后继的处理,则你需要在向下一个页面发送请求之前将前一个request返回的cookieContainer赋值给新的request,因为在cookieContainer中保留了一个叫做ASPNETSessionIDCookie

如果你Post的目标页面是ASP.NET页面,则一般在要提交的Form里都有一个__ViewState隐藏Input字段,一个服务器返回的网页源码的ViewState的例子如下,

<form name="Form1" method="post" action="login.aspx" id="Form1">

<input type="hidden" name="__VIEWSTATE"

         value="dDwtMzg4MDA0NzA7Oz7+jHQ0vF37/ga2CitRkQ3sfg+ePg==" />

<input name="UserName" type="text" id="UserName" />

<input name="Password" type="text" id="Password" />

<input type="submit" name="Submit" value="Button" id="Submit"  />

</form>

 

然而在嗅探器中捕捉到的Post数据是:

__VIEWSTATE=dDwtMzg4MDA0NzA7Oz7%2BjHQ0vF37%2Fga2CitRkQ3sfg%2BePg%3D%3D&UserName=admin&Password=123&Submit=Button0

请大家注意,这里ViewState的数据和前面的有点不一样,差别是其中的+/=等符号都被转成了%2B,%2F,%3D等,这其实将ViewState的值进行了URL编码,大家可以用System.Web.HttpUtility.UrlEncode()方法来做这种转换

当然,如果你只需要对页面做一次性访问,则大可不必理会这些,只用根据嗅探器捕获的结果来发送数据就可以了,但是如果你需要根据页面返回的ViewState值动态处理,则需要注意这一点。

2.          HTML网页代码中提取信息

要从HTML网页中提取信息,首先得将网页代码格式化成XML形式的文件,然后可以用XPath很方便的提取出自己想要的信息,要将HTML格式化为XML形式,目前有几个选择

1、   Chris LovettSgmlReader:

http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=b90fddce-e60d-43f8-a5c4-c3bd760564bc

2、   Simon Mourier.NET Html Agility Pack

http://blogs.msdn.com/smourier/archive/2003/06/04/8265.aspx

我选择了SgmlReader, 通过实际使用,发现SgmlReader也不是尽善尽美,格式化出来的XML有时候嵌套关系会搞错,但即使这样,根据他格式化出来的文本,用来提取数据也足够了。

     public static DataSet ParsePage(string pageContent, string xpath, out string err)

     {

         err = string.Empty;

         string pageContent = this.tbHTML.Text;

     StreamReader streamReader  = null;

     StringWriter strWriter = null;

     SgmlReader sgmlReader = null;

     XmlTextWriter xmlWriter = null;

     try

     {

              sgmlReader = new SgmlReader();

                   sgmlReader.DocType = "HTML";

                   sgmlReader.InputStream = new StringReader(pageContent);

                   strWriter = new StringWriter();

                   xmlWriter = new XmlTextWriter(strWriter);

                   xmlWriter.Formatting = Formatting.Indented;

                   while (sgmlReader.Read())

                   {

                       if (sgmlReader.NodeType != XmlNodeType.Whitespace)

                       {

                            xmlWriter.WriteNode(sgmlReader, true);

                       }

                   }

                   string wellFormedHTML = strWriter.ToString();

                   this.tbHTML.Text = wellFormedHTML;

                   string xpath = this.tbXPath.Text;

                   XPathDocument doc = new XPathDocument(new StringReader(wellFormedHTML));

                   XPathNavigator nav = doc.CreateNavigator();

                   XPathNodeIterator nodes = nav.Select(xpath);

                   while (nodes.MoveNext())

                   {

                       this.tbResult.Text += nodes.Current.Value+"\n";

                   }

               }

              catch (Exception exp)

              {

                   string err = exp.Message;

                   MessageBox.Show("错误:"+err);

         }

}

}

 

根据上面的代码来看,目前主要的问题就是要设置正确的XPath了,Code Project上有篇文章提供了一个简单的XPath查询工具,我对其作了一些修改,主要增加了在节点的正文中搜索某个字符串的功能,方便大家根据获得的HTML页面查询XPath


Debugging XPath Queries http://www.codeproject.com/dotnet/xpath_visualizer.asp

例如下面是一个根据查询出来的结果格式化后的XML文本片断,该信息的XPath

//div[@class=content]/DIV[@id=tblwithbck]/TABLE/TR/TD/TABLE/TR/TD/DIV[@class=data]

需要注意的是XPath中的每个路径是大小写敏感的,我就是一开始没注意这个问题,犯了些错误。

<div class="content">

<DIV id="tblwithbck">

<TABLE border="0" cellspacing="0" cellpadding="0" width="452">

<TR>

<TD bgcolor="#d5d5d5">

<TABLE border="0" cellspacing="1" cellpadding="0" width="452">

<TR>

<TD bgcolor="#ffffff">

<DIV class="data">

2 piece(s) 42 K departed on flight KE6316/04JUN from PVG to ICN

<BR />

Actual Time of Flight-Departure : 19:20

<BR />

Scheduled Time of Flight-Arrival : 21:55

</DIV>

</TD>

</TR>

<TR>

<TD bgcolor="#ffffff">

<DIV class="data">

2 piece(s) 42 K departed on flight 9S0910/06JUN from ICN to DFW

<BR />

Actual Time of Flight-Departure : 22:15

<BR />

Scheduled Time of Flight-Arrival : 05:35+1

</DIV>

</TD>

</TR>

<TR>

<TD bgcolor="#ffffff">

<DIV class="data">

2 piece(s) 42 K departed on flight 3A3617/07JUN from DFW to ELP

<BR />

Actual Time of Flight-Departure : 22:00

</DIV>

</TD>

</TR>

<TR>

<TD bgcolor="#ffffff">

<DIV class="data">

2 piece(s) 42 K arrived in ELP from flight 3A3617

<BR />

Scheduled arrival : 08 JUN

<BR />

Goods checked in at : 11:00

</DIV>

</TD>

</TR>

<TR>

<TD bgcolor="#ffffff">

<DIV class="data">

Arrival docs delivered for 2 piece(s) 42 K on 09 JUN at 09:48 in ELP

</DIV>

</TD>

</TR>

</TABLE>

</TD>

</TR>

</TABLE>

</DIV>

</div>

posted on 2005-06-15 16:27 sema 阅读(12879) 评论(39)  编辑 收藏 网摘 所属分类: 技术研究

评论

#1楼  2005-06-15 17:21 ocean      
真麻烦,这种功能用WebClient类就可以实现。捕捉post数据用ihttpheader插件就可以轻易实现。
参加我的一个blog

http://www.cnblogs.com/ocean/archive/2005/02/01/100445.html

  回复  引用  查看    

#2楼  2005-06-15 19:03 birdshome      
要是能给一个Ultra Network Sniffer破解版的下载连接,这篇文章就有意义了:}
  回复  引用  查看    

#3楼 [楼主] 2005-06-15 21:07 sema      
呵呵,如果本文没有意义,并不会因为提供了一个破解下载就变得更有意义了。
  回复  引用  查看    

#4楼 [楼主] 2005-06-15 21:12 sema      
其实WebClient和HttpWebRequest实现的思路和原理都是一样的,在代码上应该是HttpWebRequest要简单些,因为它自动帮你封装了头信息以及Cookie,你要做的就是设置正确的网址和Post数据.不过ieHTTPHeaders的确是个好东西,还是免费的。省得大家还要到处找破解版下载。
  回复  引用  查看    

#5楼  2005-06-16 08:41 恩电      
Ultra Network Sniffer v1.30 Build 0055 破解版下载:
http://www.ttdown.com/ViewDownURL.asp?softID=18576

  回复  引用  查看    

#6楼 [楼主] 2005-06-16 10:16 sema      
正如ocean 所推荐的,IEHttpHeaders是个不错的选择,对本应用来说,用它已经足够了,而且还是免费的。下载地址是:
http://www.blunck.info/iehttpheaders.html

  回复  引用  查看    

#7楼  2005-06-16 10:37 Vokobo      
我看作用不大,现在都要求验证码的呢。
  回复  引用  查看    

#8楼 [楼主] 2005-06-16 16:25 sema      
要验证码的是无法交给程序自动处理的,我这篇文章是针对无需验证码的情况谈的
  回复  引用  查看    

#9楼  2005-07-28 23:27 魔力小西瓜      
要求验证码的话 我觉得 可以 把验证图片 以 from窗口的形式 弹出来 手工填写 能实现“半自动” 也不错啊 也省了很多力啊

--------------
最后 要感谢 楼主 ,正是因为您的文章才我 茅塞顿开啊

我个人认为 程序级访问http 的功能其实非常有用的 关键在于你怎么去挖掘。

  回复  引用  查看    

#10楼 [楼主] 2005-07-29 07:28 sema      
To 魔力小西瓜:
对于验证码,目前只能这样做,或者前几天在博客园有篇文章讲数字识别,可以识别一些比较规矩的数字。
谢谢你对我的鼓励,如果这篇文章能对大家有用,我会很开心的。

  回复  引用  查看    

#11楼  2005-08-17 14:58 ttyp      
看了下你的blog,都比较不错,重质量不重数量:),就是字体和排版有问题:(
  回复  引用  查看    

#12楼  2005-08-24 17:26 bo18 [未注册用户]
Ultra Network Sniffer怎么不管用,抓不到post参数。
  回复  引用    

#13楼  2005-08-25 15:12 seenoo [未注册用户]
当初我用VBS也搞了一个类似的,去特定网站查信息,但有一个是要POST,很麻烦,
  回复  引用    

#14楼 [楼主] 2005-08-27 20:11 sema      
@bo18
Post参数肯定是有的,你应该用我上面演示的,只捕捉ie的数据包,然后看http规则的,outgoing的,post的包,应该就能看到了

  回复  引用  查看    

#15楼  2005-09-27 08:55 raozr [未注册用户]
能把那个你改造后的Debugging XPath Queries 贡献出来吗?

rzr@sina.com

  回复  引用    

#16楼 [楼主] 2005-09-27 09:44 sema      
@raozr,
在我的下载包里有的

  回复  引用  查看    

#17楼  2005-11-30 18:48 noimpulse [未注册用户]
已经很少有人这么分析事情了,难得,敬佩!
  回复  引用    

#18楼  2005-12-14 00:37 Aaron.D [未注册用户]
WebClient和HttpWebRequest在有些地方还是有区别的,我在使用的时候对有些服务器用WebClient的时候就是返回“error”,怎么搞都不行,但是用HttpWebRequest就一点问题都没有……搞不清为什么,不知道哪位可以指点一下其中的区别。
  回复  引用    

#19楼  2006-01-20 15:14 MyXQ      
好文!谢谢了!
  回复  引用  查看    

#20楼  2006-03-01 20:42 hyp [未注册用户]
我是菜鸟,请教一下,在这基础上如何把验证图片 以 from窗口的形式 弹出来.
  回复  引用    

#21楼  2006-03-23 15:29 逍遥天下      
能否把源码发给我呀,谢谢了,非常需要,superghy@126.com
  回复  引用  查看    

#22楼 [楼主] 2006-03-23 20:33 sema      
本文最前面的示例代码下载里有
  回复  引用  查看    

#23楼  2006-03-24 14:08 bundgrid [未注册用户]
不错,正是我们需要用到的技术
  回复  引用    

#24楼 [楼主] 2006-03-27 13:17 sema      
superghy提到如下问题:
你好,我下了你的自动向网页Post信息并提取返回的信息 示例程序,并能成功运行,但是感觉很多网址不能解析,这是为什么,比如我用WebQuery.exe 访问http://www.hao123.com,提示错误,少根元素,这是为什么,能否给与指点指点,谢谢了!

Answer:
这是因为大多数情况下返回的网页都不是合法的XML文档,嵌套关系不正确,因此无法转换为合法的XML。你可以先修改原网页,修正其嵌套结构,然后再传递给SgmlReader格式化为有效的XML.就可以分析了。

  回复  引用  查看    

#25楼  2006-05-13 14:29 顾爱华 [未注册用户]
如何通过后台程序把YAHOO邮箱的帐号密码通过验证呢 然后取回邮件目录 我的邮箱是guaihua888@yahoo.com.cn 希望指导
  回复  引用    

请教一下POST数据里面的 &x=17&y=9 如何获取,因为每次都不一样,而返回的HTML源文件中并没有。
  回复  引用    

#27楼 [楼主] 2006-09-01 20:07 sema      
根据我的经验,你只要取其中一次的值就可以了,x和y的值应该不会影响到返回的结果。
  回复  引用  查看    

非常感谢楼上朋友的回复!
但是我想了解下X,Y是怎么产生的?
为什么网页FORM中没有X,Y,而提交的时候POST数据中却有X,Y

  回复  引用    

#29楼  2006-12-06 22:12 雪茄 [未注册用户]
需要验证码识别的找我。cigar8013@hotmail.com
  回复  引用    

做得很不错很细致,只是看起来有点烦

使用cookiercontainer不就可以了

  回复  引用    

#31楼  2007-01-22 14:58 Tiger!      
不知道百度的验证码识别有没有人成功的?
  回复  引用  查看    

#32楼  2007-07-01 06:56 枪 [未注册用户]
x和y是因为用了<input type="image">
x和y的坐标是你点到那个图上的坐标。

  回复  引用    

#33楼  2007-07-19 12:46 请教 [未注册用户]
楼主你好!
我在Post目标页面是ASP.NET的页面时,也按照你的提示提交了那些隐含的参数了,但是总是报服务器500错误,根据抓包分析好像是说无效的ViewState之类的提示。
请问这种情况该如何处理啊?

  回复  引用    

#34楼  2007-07-19 19:15 sema [未注册用户]
建议你自己搭建一个IIS服务器,然后把自己编写的ASP.NET网页放上去,再自己访问分析一下就知道了。而且Viewstate是有状态的,和前一页的状态有关。所以你不能够没有访问过前一页,就直接访问第二页。这样的话Viewstate还没有在服务器上建立,就会出问题了。
  回复  引用    

出售蓝奇高级验证码识别引擎,可准确识别新浪动网淘宝CSDN等多种复杂验证码。

输出为一个标准DLL,可供VB,VC,Delphi,C#.NET,VB.NET,模拟精灵,按键精灵等多平台调用,调用方法简单,几行代码即可完成。独具特色的边缘检测字符分离、旋转倾斜纠正和通用字符匹配算法(无论字体和大小), 使得该引擎对于像新浪、动网、淘宝、CSDN等多种验证码均有不错的识别率,是一款效果较为理想的验证码识别引擎。附详细的调用实例和代码注释等相关技术文档。

官方网站 - http://***/yzm_advocr
识别效果怎么样一试就知道 - DEMO下载 http://***/yzm_advocr/advocr.rar

  回复  引用    

非常感谢楼主,我找这个方法找好久了!!
  回复  引用    

#37楼  2008-07-21 03:55 wings [未注册用户]
x,y很多都用到。
X,Y真的是计算坐标吗?
JAVACRIPT里面也没有计算这样的公式。
他是怎么样判断。而且现在防群发机制,好多用了X,Y。

  回复  引用    

#38楼  2008-11-08 09:53 codemo      
好文!
  回复  引用  查看    

#39楼  2009-02-21 11:09 Done      
怎么我每次调得到的结果都是和没有发送请求得到的结果一样的,楼主你的代码我用来调也是那样
  回复  引用  查看    


发表评论



姓名 [登录] [注册] 
主页
Email (仅博主可见) 
验证码 *  验证码看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论   新用户注册   返回页首      

导航: 网站首页 社区 新闻 博问 闪存 网摘 招聘 .NET频道 知识库 找找看 Google站内搜索



China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
China-Pub 计算机绝版图书按需印刷服务

相关文章:

相关链接: