卧佛寺畔寻常路,萼新绿,香如故。
东君闲情有几许?犹寒燕赵,早春浪屿,南北各风物。
红墙紫禁春寒处,最是艰难返乡路。
羡煞东风无束缚,江南江北,无凭鳞羽,一夜即飞度。

送上一首以前写的〈青玉案〉,年年这个时节,总要为火车票之事焦头烂额,尤其像我这样路途遥远的,正常途径买票鲜有成功之例,往年有两个方法,一是找黄牛,然可靠的黄牛甚少,加价甚狠,十分不给力;二是网上搜索转让的车票。

其实网上转让的火车票不少,但是因为时效性的关系,需要你频繁刷新,常常是刚发布的转让信息,一分钟不到即被人抢走。尝夜间刷票5小时,才抢得一张硬卧。夜里刷票刷到眼冒金星时,曾想过自己写一个程序,定时刷新几个主要的火车票转让网站(比如赶集、58),当有新的转让信息出现时给出提醒。除了定时刷新之外,还希望能将电话直观地显示出来(因为网站的搜索结果只包含火车票的信息,需要一条条点击进入具体页面才能看到电话)。

1.赶集网

1.1

首先找到的是赶集网的火车票转让。而在程序中,第一想法是使用WebBrowser组件,使用

this.wb.Navigate(“http://bj.ganji.com/piao/”);//wb是WebBrowser对象

载入赶集网火车票转让网页。在此页面上,主要有两个文本框与一个按钮,两个文本框用入分别输入起始站及终点站,按钮用于提交。分析页面的源文件后知道这三个HTML元素的ID分别是:

piao_from_station
piao_to_station
piao_zz_submit

于是使用以下代码进行网页的提交:

HtmlElement tbFrom = wb.Document.All["piao_from_station"];
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是

id_train_time_select

我尝试通过下面的代码选择指定的日期:

HtmlElement slDate = tmp.Document.All["id_train_time_select"];
string dt = {指定的日期字符串};
//
foreach (HtmlElement option in slDate.Children)
{
    if (option.GetAttribute("value") == dt)
    {
        option.SetAttribute("selected", "selected");
        break;
    }
}

或是以下的代码:

HtmlElement slDate = tmp.Document.All["id_train_time_select"];
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

因为始终无法在选择下拉框时让页面刷新,所以只好使用其它方案,因为最终搜索页面的地址形如:

http://bj.ganji.com/piao/zz_北京-福州/20110120/

显然,我可以根据自己的需要拼出具体的Url,土是土了点,但是代码简单多了,于是顺利进行搜索页面的分析(我只是逐行读取WebBrowser中的DocumentStream,使用简单的正则进行匹配),找到每条搜索结果的具体Url,并通过WebBrowser打开这些具体Url,从中得到发贴人的电话号码。

在这里,又遇到新的问题,那就是多次使用WebBrowser之后,内存令人吃惊地上涨。因为内存的问题,最后不得不换成使用WebRequest、WebResponse,好在事实上我并不需要在程序里显示真正的页面内容。使用类似下面的几行代码可以获得页面的Stream,随后便可对它进行分析。

protected StreamReader GetStreamReaderFromResponse(string pUrl)
{
    WebRequest wrq = WebRequest.Create(pUrl);
    WebResponse wrp = wrq.GetResponse();
    return new StreamReader(wrp.GetResponseStream());
}

至此,内存问题基本解决了。

1.4

顺利地获得了电话号码,但是,为什么跟页面上显示的完全对应不起来?这里又遇到一个难处,从页面源代码中可以看到,从源码中抓到的电话并不是真正的电话号码,它经过一段繁琐的Javascript转换之后才能得到真实的电话号码(即页面上最终显示的号码)。

本想尝试写一个等价的C#方法,未遂。几经辗转,决定直接从C#代码中调用页面中的Javascript方法。参见如下代码片段:

wb = new WebBrowser();
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上的电话有一部分是以图片的方式显示的,就像一般的验证码那样,目前没有什么简单的处理方案,热心的同志们可以试试继续努力一把。祝同志们顺利买到火车票/机票。春节快乐!

转载于:https://www.cnblogs.com/morvenhuang/archive/2011/01/19/1939504.html

.NET基础示例系列之二十三:WebRequest、WebResponse及刷票程序相关推荐

  1. .NET基础示例系列之二十:对图片的几种简单处理

    又有一段时间没有更新了,缺少学习的热情了.今天贴几个图片处理的小技巧,希望对大家有用: (1)如何获取.gif图片中的各个帧? (2)如何获取图片的缩略图? (3)如何"截取"图片 ...

  2. .NET基础示例系列之二十四:家谱软件(1)

    最近忙于摆弄Oracle方面的东西,C#的功课落下了不少,趁着还没手生,把前段时间断断续续写的WPF家谱小软件拿出来整理一下,好记性不如烂笔头,先截两张图放着,后续再写写中间一些问题.已解决的是: 1 ...

  3. spring配置文件_SpringBoot入门建站全系列(二十三)配置文件优先级及自定义配置文件...

    SpringBoot入门建站全系列(二十三)配置文件优先级及自定义配置文件 一.概述 Spring Boot允许多种配置来源,官网是这样说的: Spring Boot使用一种非常特殊的Property ...

  4. Docker系列(二十三)——Docker实例五Docker安装MongoDB实例

    < Docker实例三Docker安装MongoDB实例 > 前言 在前面一篇文章种,完成了 < Docker安装MySQL实例 >,本篇将继续镜像安装教程,并完成Docker ...

  5. 强化学习系列文章(二十三):AirSim Python API图像与图像处理

    强化学习系列文章(二十三):AirSim Python API图像与图像处理 参考网址:https://microsoft.github.io/AirSim/image_apis/#segmentat ...

  6. IT职场人生系列之二十三 知识体系(专家与杂家)

    这是IT职场人生系列的第二十三篇.(序言,专栏目录) 专家与杂家 专家与杂家之争由来已久. 挺专家者说:只有专一,才能学透学精:那些泛泛之辈,只能学到些皮毛,终究不能有所成就. 挺杂家者说:只有广泛, ...

  7. IT职场人生系列之二十三:知识体系(专家与杂家)

    这是IT职场人生系列的第二十三篇.(序言,专栏目录) 专家与杂家 专家与杂家之争由来已久. 挺专家者说:只有专一,才能学透学精:那些泛泛之辈,只能学到些皮毛,终究不能有所成就. 挺杂家者说:只有广泛, ...

  8. 零基础学Python【二十三、图形化界面设计 】(基础一篇全,欢迎认领)

    1.图形化界面设计的基本理解 当前流行的计算机桌面应用程序大多数为图形化用户界面(Graphic User Interface,GUI). 即通过鼠标对菜单.按钮等图形化元素触发指令,并从标签.对话框 ...

  9. 单片机小白学步系列(二十三) IO口原理知识补充:双向IO口、互补推挽、高阻态

    由于之前考虑不周,本篇在IO口原理知识的基础上,进一步补充一些知识. ================================================= 双向IO口的输出:互补推挽 在 ...

最新文章

  1. 详解Oracle安装与配置.
  2. 安卓音频输出采样率_只有AirPods配有姓名吗?安卓的这些无线耳机也不错
  3. python数据清理的实践总结_Python数据清洗实践
  4. 关于jstl在tomcat5和tomcat6的部署
  5. 深入浅出InfoPath——动态获取InfoPath中的命名空间
  6. 2014 java面试题_2014 java面试题 (答案)
  7. (转载)Hadoop map reduce 过程获取环境变量
  8. 浅析vue的双向数据绑定
  9. Struts2基础总结
  10. 继续解决YUI3 Panel的yui3-panel-hidden样式带来的问题
  11. Jquery 提交表单
  12. android u盘检测工具,android U盘检测及获取内存储器信息
  13. You Only Watch Once(YOWO)
  14. android 修改充电图标,更换图标、修改充电音...这个软件把iPhone玩成了安卓
  15. 三.螺丝与核弹。【成长篇】
  16. matlab eqs,EQS(奔驰eqs什么时候上市)
  17. UG二次开发GRIP创建注释
  18. DDR中bank,die,rank,channel的概念
  19. Android-Handle详解
  20. 基于深度学习的遥感影像语义分割数据预处理

热门文章

  1. Django中的CBV视图
  2. 郭德纲经典爆笑语录!!!(肚子疼别找我!!!)
  3. 2021考研英语完型易熙人
  4. linux桌面环境_Linux桌面环境
  5. 关于spring 事物传播性的研究
  6. Java SE day18_IO流4
  7. 程序员的全新的兼职工作方式
  8. 前端必读 0基础学习 一文看懂 Vue3 对比 Vue2 发生哪些变化
  9. SAP FI 系列 (026) - 增值税的配置
  10. Linux操作系统-信号量