.NET基础示例系列之二十三:WebRequest、WebResponse及刷票程序
东君闲情有几许?犹寒燕赵,早春浪屿,南北各风物。
红墙紫禁春寒处,最是艰难返乡路。
羡煞东风无束缚,江南江北,无凭鳞羽,一夜即飞度。
送上一首以前写的〈青玉案〉,年年这个时节,总要为火车票之事焦头烂额,尤其像我这样路途遥远的,正常途径买票鲜有成功之例,往年有两个方法,一是找黄牛,然可靠的黄牛甚少,加价甚狠,十分不给力;二是网上搜索转让的车票。
其实网上转让的火车票不少,但是因为时效性的关系,需要你频繁刷新,常常是刚发布的转让信息,一分钟不到即被人抢走。尝夜间刷票5小时,才抢得一张硬卧。夜里刷票刷到眼冒金星时,曾想过自己写一个程序,定时刷新几个主要的火车票转让网站(比如赶集、58),当有新的转让信息出现时给出提醒。除了定时刷新之外,还希望能将电话直观地显示出来(因为网站的搜索结果只包含火车票的信息,需要一条条点击进入具体页面才能看到电话)。
1.赶集网
1.1
首先找到的是赶集网的火车票转让。而在程序中,第一想法是使用WebBrowser组件,使用
载入赶集网火车票转让网页。在此页面上,主要有两个文本框与一个按钮,两个文本框用入分别输入起始站及终点站,按钮用于提交。分析页面的源文件后知道这三个HTML元素的ID分别是:
piao_to_station
piao_zz_submit
于是使用以下代码进行网页的提交:
HtmlElement tbTo = wb.Document.All["piao_to_station"];
HtmlElement btSubmit = wb.Document.All["piao_zz_submit"];
//
tbFrom.SetAttribute("value", {你的起始站名,比如北京});
tbTo.SetAttribute("value", {你的目标站名,比如福州});
btSubmit.InvokeMember("click");
至此,页面被顺利定位到搜索结果页面。
1.2
接着我就遇到第一个问题,在结果搜索页面,还用一个下拉框(HTML的Select元素),用于选择火车票的发车时间,很容易得到此Select元素的ID是
我尝试通过下面的代码选择指定的日期:
string dt = {指定的日期字符串};
//
foreach (HtmlElement option in slDate.Children)
{
if (option.GetAttribute("value") == dt)
{
option.SetAttribute("selected", "selected");
break;
}
}
或是以下的代码:
string dt = {指定的日期字符串};
int idx = 0;
//
foreach (HtmlElement option in slDate.Children)
{
if (option.GetAttribute("value") == dt)
{
slDate.SetAttribute("selectedIndex", idx.ToString());
break;
}
idx++;
}
查看WebBrowser中的页面,下拉框的值已被正确设置,但页面未刷新(手工在页面上选择日期后搜索结果是会刷新的,并只显示此日期的车票),我尝试通过再次触发上一节中的piao_zz_submit按钮的click事件进行页面刷新,但这么刷新后导致刚才选中的下拉框中的日期丢失。还尝试触发此下拉框的onchange事件,无果(实际上从页面源代码看,此该下拉框也没有关于onchange事件的处理脚本)。
1.3
因为始终无法在选择下拉框时让页面刷新,所以只好使用其它方案,因为最终搜索页面的地址形如:
显然,我可以根据自己的需要拼出具体的Url,土是土了点,但是代码简单多了,于是顺利进行搜索页面的分析(我只是逐行读取WebBrowser中的DocumentStream,使用简单的正则进行匹配),找到每条搜索结果的具体Url,并通过WebBrowser打开这些具体Url,从中得到发贴人的电话号码。
在这里,又遇到新的问题,那就是多次使用WebBrowser之后,内存令人吃惊地上涨。因为内存的问题,最后不得不换成使用WebRequest、WebResponse,好在事实上我并不需要在程序里显示真正的页面内容。使用类似下面的几行代码可以获得页面的Stream,随后便可对它进行分析。
{
WebRequest wrq = WebRequest.Create(pUrl);
WebResponse wrp = wrq.GetResponse();
return new StreamReader(wrp.GetResponseStream());
}
至此,内存问题基本解决了。
1.4
顺利地获得了电话号码,但是,为什么跟页面上显示的完全对应不起来?这里又遇到一个难处,从页面源代码中可以看到,从源码中抓到的电话并不是真正的电话号码,它经过一段繁琐的Javascript转换之后才能得到真实的电话号码(即页面上最终显示的号码)。
本想尝试写一个等价的C#方法,未遂。几经辗转,决定直接从C#代码中调用页面中的Javascript方法。参见如下代码片段:
wb.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.wb_DocumentCompleted);
wb.Navigate("");
while (wb.ReadyState != WebBrowserReadyState.Complete)
{
System.Windows.Forms.Application.DoEvents();
}
phone = wb.Document.InvokeScript(“F13”, new object[] { “需要处理的电话号码” }).ToString();
private void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wb = (WebBrowser)sender;
HtmlElement head = wb.Document.GetElementsByTagName("head")[0];
HtmlElement scriptEl = wb.Document.CreateElement("script");
IHTMLScriptElement element = (IHTMLScriptElement)scriptEl.DomElement;
element.text = strJavascript;
head.AppendChild(scriptEl);
}
“phone =..”一行是示例写法,其中的“F13”是用于处理电话号码的Javascript主方法名称,但因为这段Javascript经过混淆,每一次打开的页面背后的这段Javascript是不同的,所以并非都叫“F13”,但通过查看这段js,也很容易从中抽出主方法名称。
代码中strJavascript变量即是从页面源代码中抽取的js脚本。
另外,要使用IHTMLScriptElement,需要引用COM中的Microsoft HTML object library。
至此,缝缝补补,如果加上定时器等一些处理,对赶集网火车票转让就基本可以实现预定目标了。
2. 58同城
58上的电话有一部分是以图片的方式显示的,就像一般的验证码那样,目前没有什么简单的处理方案,热心的同志们可以试试继续努力一把。祝同志们顺利买到火车票/机票。春节快乐!
关注作者:欢迎扫码关注公众号「后厂村思维导图馆」,获取本人自建的免费ChatGPT跳板地址,长期有效。 原文链接:https://www.cnblogs.com/morvenhuang/archive/2011/01/19/1939504.html 版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。 |