Socket模拟HTTP协议之火车票购票软件

前段时间我发布过一篇文章描述Socket进行HTTP/HTTPS操作,但是还是很多朋友觉得多次一举,放着简单的HttpWebRequest不用!

实际是有些人根本没看文章就乱说了,我们的目地是提高访问速率,了解HTTP协议与一般网络开发,并非奔着简单去!

 

正好这年末,大家抢火车票抢的火热,但是我们很多程序员朋友却只没有应用好自己的专业知识为自己购得回家的车票。

网上已经不乏一些抢票软件、以及对12306流程分析的文章,

 

google code中也已经有了java版的全自动购票程序,自动AI+OCR识别的。有兴趣的可以去搜索下看看。

 

回到文章中来,我这次也是通过编写一下购票软件来实践下上次文章中的内容:

http://www.cnblogs.com/cxwx/archive/2011/10/25/2224105.html

下面的这款小软件还没有完全完成,软件是WPF缩写,基本的MVVM模式(有需要学习MVVM的也可以参考学习下)

下面就按照截图走流程+编码说明:

一:登录

 

我们知道,首次请求一个页面,服务端会为客户端创建一个Session来保存客户端状态,我们通常在登录页面提交信息成功后,服务端会在其他页面跳转之后判断

Seesion是否已经成功登录。

对于存在验证码的网站,其实请求图片与请求页面没有什么区别,如果是首次请求那么HTTP返回协议一般都带附带Cookies信息,即Set-Cookies 信息!

 

对于12306网站来说,我们的登录页面只要请求验证码图片即可,因为这个请求的HTTP返回协议会包含服务端为本次请求创建的Cookies,

如果您利用上一篇文章讲解的方法来请求,你会发现请求返回协议类似如下:

 1 Response Headers    Value
2 (Status-Line) HTTP/1.1 200 OK
3 Date Sun, 08 Jan 2012 23:57:29 GMT
4 Server asfep/2.3.0 svn:3075
5 Content-Type text/html;charset=UTF-8
6 Transfer-Encoding chunked
7 X-Powered-By Servlet 2.5; JBoss-5.0/JBossWeb-2.1
8 Pragma no-cache
9 Cache-Control no-cache
10 Expires Wed, 31 Dec 1969 23:59:59 GMT
11 Content-Encoding gzip
12 X-Cache MISS from cache.51cdn.com
13 X-Via 1.1 hbts205:8090 (Cdn Cache Server V2.0)
14 Connection keep-alive

在这段返回头中,你会发现不存在Content-Length协议头,那我们如何知道返回的数据包长度呢;

 

如果你善于google你就会知道,这种反回是因为Keep-Live的存在 服务器使用chunked方式分多次返回数据的结果;既然服务端还是会返回数据,只是分了多次!(我们知道浏览器能够正常解析哦,所以别小看浏览器内核哦,很强大滴~~~~)

 

我们首要先来解决对于反回数据的解析:接着上篇文章中的HttpHelper,大家找到如下代码段:看看修改后的代码;

 static byte[] ReadResponse(Stream sm)
        {
            DateTime now = DateTime.Now;
            ArrayList array = new ArrayList();
            int length = 0;
            while (true)
            {
                byte[] buff = new byte[1024];
                try
                {
                    int len = sm.Read(buff, 0, buff.Length);
                    if (len > 0)
                    {
                        length += len;
                        byte[] reads = new byte[len];
                        Array.Copy(buff, 0, reads, 0, len);
                        array.Add(reads);
                        if (len < buff.Length)
                        {
                            break; //fix bug
                        }
                    }
                    else
                    {
                        break;  //fix bug
                    }
                }
                catch (Exception)
                {
                    break;
                }
                finally
                {
                    Thread.Sleep(200);
                }
            }
            byte[] bytes = new byte[length];
            int index = 0;
            for (int i = 0; i < array.Count; i++)
            {
                byte[] temp = (byte[])array[i];
                Array.Copy(temp, 0, bytes,
                    index, temp.Length);
                index += temp.Length;
            }
            return bytes;
        }

  很简单,其实就是多次请求,直到请求异常或者实际请求数据小于缓冲区大小就OK啦(注意两处 fix bug)

 

这样,我们首先解决了解析服务端反回数据的问题,紧接着我们娶到了登录页面的验证码图片, 如果你现在抓包看下,你又会发现,抓包的数据就是图片数据,

而我们使用Socket请求来的数据,多出了一段数据头,呵呵,明眼的你注意,就会发现多出的这段数据头还包含真实数据的长度!

如图中红色区域;

 

我这里呢没有通过解析这个长度去提取真实数据,而是通过遍历出JPEG图片数据头来截取数据的,代码如下:

 1 /// <summary>
2 /// JFIF数据头
3 /// </summary>
4 static readonly byte[] JFIF = new byte[] { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46 };
5
6 public static Stream GetVerifyImage()
7 {
8 IPEndPoint endpoint = GetHost();
9 RequestArgs request=new RequestArgs()
10 {
11 Url = "/otsweb/passCodeAction.do?rand=lrand",
12 Host="dynamic.12306.cn",
13 Accept="image/*",
14 Referer="https://dynamic.12306.cn/otsweb/loginAction.do?method=init",
15 };
16 m_Response=HttpHelper.Get(endpoint,request,m_X509);
17 if(m_Response!=null)
18 {
19 if(m_Response.Header.StartsWith("HTTP/1.1 200"))
20 {
21 //先校正保存cookies
22 AdjustCookies();
23
24 //由于HTTP返回协议使用了 chunked,所以要手动去分析实际需要的内容
25 if (m_Response.Body != null && m_Response.Body.Length > 10)
26 {
27 int index = 0;
28 byte[] head = new byte[10];
29 while (index < m_Response.Body.Length - 10)
30 {
31 Array.Copy(m_Response.Body,index,head,0,head.Length);
32 if (head.SequenceEqual(JFIF))
33 {
34 byte[] buff = new byte[m_Response.Body.Length-index];
35 Array.Copy(m_Response.Body,
36 index,
37 buff,0,buff.Length);
38 return new MemoryStream(buff);
39 }
40 index++;
41 }
42 }
43 }
44 }
45 return null;
46 }

上面这段代码也是我们购票程序登录页面的第一个服务代码; 注意看对Repose的解析部分,根据JPEG图片头进行数据解析,

读到这里,各位看官应该了解了如果使用上次文章中的HttpHelper,做一个简单的HTTPS请求,当然这个请求是通过Socket完成的,这里我就不再解释了!

 

OK,说了一些关于第一步请求的东西,接着我们先看下MVVM模式的简单应用,

我们的Login页面是一个UserCOntrol,整个购票程序是一个Window 当中是一个Frame,通过页面跳转来指导运行的,

Login页面绑定LoginViewModel,注意看,很多朋友不知道MVVM模式中ViewModel怎么和View交互,这里你可看清楚了哦!

 

1  <UserControl.DataContext>
2 <vm:LoginViewModel Loaded="LoginViewModel_Loaded"
3 Loading="LoginViewModel_Loading"
4 Logined="LoginViewModel_Logined"
5 VerifyImageCompleted="LoginViewModel_VerifyImageCompleted" />
6 </UserControl.DataContext>

没错,事件!!! 就是痛过事件,我们简单的让ViewModel与View进行交互。 当然这并不是唯一的方法,方法很多,这里我就不多讲了,有兴趣的自己看下Prsim以及Service共享和,ICO相关知识!

 

至于您看到的这里,为什么会有三个事件,我需要解释下,Logined事件无疑是登录成功事件,其余两个看名字判断应该是一个登录过程提示的事件,

我这样做的目地是希望后台全部采用异步请求,避免程序体验效果不好,在MVVM模式中,该VM干的事就给VM干,该View干的那就让View去干吧!

 

OK,请求到了验证码图片,紧接着填写帐号信息,提交登录请求,判断返回结果,一步带过:通过ViewModel来看吧!

  1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using SimpleMvvmToolkit;
6 using System.Windows.Input;
7 using System.Windows.Media.Imaging;
8 using BuyTicket.Service;
9 using System.IO;
10 using System.Diagnostics;
11 using System.ComponentModel;
12 using System.Windows;
13
14 namespace BuyTicket.ViewModels
15 {
16 class LoginViewModel : ViewModelBase<LoginViewModel>
17 {
18 public LoginViewModel()
19 {
20
21 }
22
23 event EventHandler<NotificationEventArgs> m_Loading;
24 public event EventHandler<NotificationEventArgs> Loading
25 {
26 add
27 {
28 m_Loading += value;
29 }
30 remove
31 {
32 m_Loading -= value;
33 }
34 }
35
36 event EventHandler<NotificationEventArgs> m_Loaded;
37 public event EventHandler<NotificationEventArgs> Loaded
38 {
39 add
40 {
41 m_Loaded += value;
42 }
43 remove
44 {
45 m_Loaded -= value;
46 }
47 }
48
49
50 event EventHandler<NotificationEventArgs> m_Logined;
51 public event EventHandler<NotificationEventArgs> Logined
52 {
53 add
54 {
55 m_Logined += value;
56 }
57 remove
58 {
59 m_Logined -= value;
60 }
61 }
62
63 event EventHandler<NotificationEventArgs<BitmapImage>> m_VerifyImageCompleted;
64 public event EventHandler<NotificationEventArgs<BitmapImage>> VerifyImageCompleted
65 {
66 add
67 {
68 m_VerifyImageCompleted += value;
69 }
70 remove
71 {
72 m_VerifyImageCompleted -= value;
73 }
74 }
75
76 ICommand m_LoginCommand = null;
77 public ICommand LoginCommand
78 {
79 get
80 {
81 return m_LoginCommand ?? (m_LoginCommand = new DelegateCommand(Login));
82 }
83 }
84
85 bool m_NotLogined = false;
86
87 void Login()
88 {
89 if(string.IsNullOrEmpty(m_UserName)
90 || string.IsNullOrEmpty(m_PassWord)
91 || string.IsNullOrEmpty(m_VerifyCode))
92 {
93 MessageBox.Show("请填写完整的登录数据!", "错误提示",
94 MessageBoxButton.OK, MessageBoxImage.Error);
95 }
96 else
97 {
98 this.Async(new Func<object>(() =>
99 {
100 string error;
101 bool logined = BuyTicketService.Login(m_UserName, m_PassWord, m_VerifyCode, out error);
102 if (!logined)
103 {
104 m_NotLogined = true;
105 if (!string.IsNullOrEmpty(error))
106 {
107 MessageBox.Show(error, "错误提示",
108 MessageBoxButton.OK, MessageBoxImage.Error);
109 }
110 }
111 return logined;
112 }), new Action<object>(args =>
113 {
114 bool logined = (bool)args;
115 if (logined)
116 {
117 if (m_Logined != null)
118 {
119 m_Logined(this, new NotificationEventArgs());
120 }
121 }
122 else
123 {
124 RefreshVerifyCode(); //注意:此过程中可能造成View中Loading状态不一致
125 }
126 }));
127 }
128 }
129
130 ICommand m_RefreshVerifyCodeCommand = null;
131 public ICommand RefreshVerifyCodeCommand
132 {
133 get
134 {
135 return m_RefreshVerifyCodeCommand ?? (m_RefreshVerifyCodeCommand = new DelegateCommand(RefreshVerifyCode));
136 }
137 }
138
139 void RefreshVerifyCode()
140 {
141 this.Async(new Func<object>(() =>
142 {
143 return BuyTicketService.GetVerifyImage();
144
145 /* 过时
146 var sm = BuyTicketService.GetVerifyImage();
147 if (sm != null)
148 {
149 try
150 {
151 using (FileStream fs = new FileStream(@"d:\\temp.png", FileMode.Create))
152 {
153 sm.CopyTo(fs);
154 fs.Close();
155 }
156
157 System.Drawing.Bitmap.FromStream(sm, true)
158 .Save(@"d:\\temp.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
159
160 BitmapImage image = new BitmapImage();
161 image.BeginInit();
162 image.StreamSource = sm; //sm引用来自BackgroundWorker线程
163 image.EndInit();
164 return image;
165 }
166 catch (Exception ex)
167 {
168 Trace.WriteLine(ex);
169 }
170 }
171 return null;
172 */
173 }), new Action<object>((args) =>
174 {
175 BitmapImage image = null;
176 Stream sm = args as Stream;
177 if (sm != null)
178 {
179 try
180 {
181 image = new BitmapImage();
182 image.BeginInit();
183 image.StreamSource = sm; //sm引用来自BackgroundWorker线程
184 image.EndInit();
185 }
186 catch (Exception ex)
187 {
188 Trace.WriteLine(ex);
189 }
190 }
191 //通知到View
192 if (m_VerifyImageCompleted != null)
193 {
194 m_VerifyImageCompleted(this,
195 new NotificationEventArgs<BitmapImage>("", image));
196 }
197 this.VerifyCode = "";
198 /*
199 BitmapImage image = args as BitmapImage;
200 if (image != null)
201 {
202 if (m_VerifyImageCompleted != null)
203 {
204 m_VerifyImageCompleted(this,
205 new NotificationEventArgs<BitmapImage>("", image));
206 }
207 }*/
208 }));
209
210 }
211
212 void Async(Func<object> func, Action<object> completed)
213 {
214 using (BackgroundWorker worker = new BackgroundWorker())
215 {
216 if (m_Loading != null)
217 {
218 m_Loading(this, new NotificationEventArgs());
219 }
220 worker.DoWork += (sender, e) =>
221 {
222 e.Result = func();
223 };
224 worker.RunWorkerCompleted += (sender, e) =>
225 {
226 completed(e.Result);
227 if (m_NotLogined==false) //登录失败时不通知Loaded事件
228 {
229 if (m_Loaded != null)
230 {
231 m_Loaded(this, new NotificationEventArgs());
232 }
233 }else
234 m_NotLogined = false; //重新标记
235 };
236 worker.RunWorkerAsync();
237 }
238 }
239
240 string m_UserName;
241 public string UserName
242 {
243 get
244 {
245 return m_UserName;
246 }
247 set
248 {
249 m_UserName = value;
250 this.NotifyPropertyChanged(m => m.UserName);
251 }
252 }
253
254 string m_PassWord;
255 public string PassWord
256 {
257 get
258 {
259 return m_PassWord;
260 }
261 set
262 {
263 m_PassWord = value;
264 this.NotifyPropertyChanged(m => m.PassWord);
265 }
266 }
267
268 string m_VerifyCode;
269 public string VerifyCode
270 {
271 get { return m_VerifyCode; }
272 set
273 {
274 m_VerifyCode = value;
275 this.NotifyPropertyChanged(m => m.VerifyCode);
276 }
277 }
278 }
279 }

 

相应的View就不展示出来了,需要的自己在文章末下载代码回去看吧!对于登录的过程,这里我不想细说,这里是演示上篇文章的通过Socket进行HTTP操作,

并不是延时如何抓包,如何模拟HTTP请求,有兴趣的可自己去查阅相关资料!

 

登录成功后,我们需要先填写购票信息,界面设计为:

 

请原谅我的美工水平哦,俺可不是专业的, 在这个页面中,起始站点信息是我从12306的js上搞下来的,说实话,最近这站被人诟病很多,

我也不乏诟病一下,但也不是非常差,我想没有点墨水可不是谁都能搞的了的。

 

这里还提供了一项功能就是跳转至浏览器, 其实登录成功后,Cookies信息我们也就娶到了,这时候通过浏览器打开12306再编辑其cookies也可以完成直接登录,

至于我后来添加“跳转至浏览器”是因为这款软件并没有完全完成,给需要使用的普通用户提供一个购票入口而已!

 

从当前页面进行下一步,就会根据参数提交到服务器查询余票信息,具体的提交服务展示一下,看看如果您抓取到了数据包您会是如何请求呢,

 1  public static bool Login(string name, string pass, string verifyCode, out string error)
2 {
3 error = string.Empty;
4 IPEndPoint endpoint = GetHost();
5 RequestArgs request=new RequestArgs()
6 {
7 Url = "/otsweb/loginAction.do?method=login",
8 Host="dynamic.12306.cn",
9 Accept="text/*",
10 Referer="https://dynamic.12306.cn/otsweb/loginAction.do?method=init",
11 Cookie = m_Cookies,
12 Body = string.Format("loginUser.user_name={0}&nameErrorFocus=&user.password={1}&passwordErrorFocus=&randCode={2}&randErrorFocus=",
13 name,pass,verifyCode)
14 };
15 m_Response=HttpHelper.Post(endpoint,request,m_X509);
16 if (m_Response != null)
17 {
18 if (m_Response.Header.StartsWith("HTTP/1.1 200"))
19 {
20 if (m_Response.Body != null)
21 {
22 string strHtml = Encoding.UTF8.GetString(m_Response.Body);
23 if (strHtml.Contains("我的信息") && strHtml.Contains("用户注销"))
24 {
25 return true;
26 }
27 else if (strHtml.Contains("当前访问用户过多,请稍后重试!"))
28 {
29 error = "当前访问用户过多,请稍后重试!";
30 }
31 else if (strHtml.Contains("请输入正确的验证码!</span>"))
32 {
33 error = "请输入正确的验证码!";
34 }
35 else
36 {
37 error = "各种登录失败,兄弟,理解万岁吧!";
38 }
39 }
40 }
41 else
42 {
43 error = "HTTP响应错误:"+Utility.SplitString(m_Response.Header,
44 "HTTP/1.1 ",
45 " ");
46 }
47 }
48 else
49 {
50 error = "超时,主机没有反映!";
51 }
52 return false;
53 }
54
55 static void AdjustCookies()
56 {
57 m_Cookies = HttpHelper.ParseCookies(m_Response.Header);
58 }
59
60 public static bool GetTickets(out string strHtml)
61 {
62 strHtml = string.Empty;
63 IPEndPoint endpoint = GetHost();
64 RequestArgs request=new RequestArgs()
65 {
66 Url = string.Format("/otsweb/order/querySingleAction.do?{0}",
67 Global.MyTicketArgs.ToString()),
68 Host="dynamic.12306.cn",
69 Accept="text/*",
70 Referer = "https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=init",
71 Cookie = m_Cookies,
72 };
73 m_Response=HttpHelper.Get(endpoint,request,m_X509);
74 if (m_Response != null)
75 {
76 if (m_Response.Header.StartsWith("HTTP/1.1 200"))
77 {
78 if (m_Response.Body != null)
79 {
80 strHtml = Encoding.UTF8.GetString(m_Response.Body);
81 return true;
82 }
83 }
84 }
85 return false;
86 }

 

忘记补充一点,购票信息我是存放在全局中了,服务是静态服务;从上面2个服务中您也能够看出对上篇文章中HttpHelper的使用,当时文章中的这个辅助类并不完善,有些BUG存在,目前我也只是简单修复了下BUG,有需要的还是需要自己去试试调试修改哦!

 


OK ,订票参数提交后,就是订票信息板的展示,这里只展示有余票信息的列车,抓包发现查询参数提交后服务器返回如下类型数据:

1 0,<span id='id_240000T1090C' class='base_txtdiv' onmouseover=javascript:onStopHover('240000T1090C#BJP#SHH') onmouseout='onStopOut()'>T109</span>,<img src='/otsweb/images/tips/first.gif'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;北京&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;19:28,<img src='/otsweb/images/tips/last.gif'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;上海&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;10:25,14:57,--,--,--,--,9,4,5,--,<font color='darkgray'>无</font>,<font color='darkgray'>无</font>,--,<input type='button' class='yuding_u' onmousemove=this.className='yuding_u_over' onmousedown=this.className='yuding_u_down' onmouseout=this.className='yuding_u' onclick=javascript:getSelected('T109#14:57#19:28#240000T1090C#BJP#SHH#10:25#北京#上海#10179000003031700005404990000460921000091017903000') value='预订'></input>\n
2 1,<span id='id_240000D31100' class='base_txtdiv' onmouseover=javascript:onStopHover('240000D31100#BJP#SHH') onmouseout='onStopOut()'>D311</span>,<img src='/otsweb/images/tips/first.gif'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;北京&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;20:52,<img src='/otsweb/images/tips/last.gif'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;上海&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;08:40,11:48,--,--,--,<font color='darkgray'>无</font>,--,<font color='#008800'>有</font>,--,--,--,--,--,<input type='button' class='yuding_u' onmousemove=this.className='yuding_u_over' onmousedown=this.className='yuding_u_down' onmouseout=this.className='yuding_u' onclick=javascript:getSelected('D311#11:48#20:52#240000D31100#BJP#SHH#08:40#北京#上海#4069800047O031100000') value='预订'></input>\n
3 2,<span id='id_240000D32110' class='base_txtdiv' onmouseover=javascript:onStopHover('240000D32110#BJP#SHH') onmouseout='onStopOut()'>D321</span>,<img src='/otsweb/images/tips/first.gif'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;北京&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;20:58,<img src='/otsweb/images/tips/last.gif'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;上海&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;08:46,11:48,--,--,--,<font color='#008800'>有</font>,--,<font color='#008800'>有</font>,--,--,--,--,--,<input type='button' class='yuding_u' onmousemove=this.className='yuding_u_over' onmousedown=this.className='yuding_u_down' onmouseout=this.className='yuding_u' onclick=javascript:getSelected('D321#11:48#20:58#240000D32110#BJP#SHH#08:46#北京#上海#4069800096O031100046') value='预订'></input>\n
4 3,<span id='id_240000D31310' class='base_txtdiv' onmouseover=javascript:onStopHover('240000D31310#VNP#SHH') onmouseout='onStopOut()'>D313</span>,<img src='/otsweb/images/tips/first.gif'>&nbsp;&nbsp;&nbsp;&nbsp;北京南&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;21:11,<img src='/otsweb/images/tips/last.gif'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;上海&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;08:52,11:41,--,--,--,<font color='darkgray'>无</font>,16,<font color='#008800'>有</font>,--,--,--,--,--,<input type='button' class='yuding_u' onmousemove=this.className='yuding_u_over' onmousedown=this.className='yuding_u_down' onmouseout=this.className='yuding_u' onclick=javascript:getSelected('D313#11:41#21:11#240000D31310#VNP#SHH#08:52#北京南#上海#40698002416139200016O031100000') value='预订'></input>\n
5 4,<span id='id_330000T28407' class='base_txtdiv' onmouseover=javascript:onStopHover('330000T28407#BXP#SNH') onmouseout='onStopOut()'>T281</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;北京西&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;22:30,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;上海南&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;13:24,14:54,--,--,--,--,--,<font color='darkgray'>无</font>,<font color='darkgray'>无</font>,--,<font color='darkgray'>无</font>,<font color='darkgray'>无</font>,--,<input type='button' class='yuding_x' value='预订'></input>

 

对于这种Html格式数据的分析,有过这方面的经验的同学可能会想到HtmlAgilityPack ,没错,这是一款非常不错并且强大的分析组件,如果您还没用过,不妨去codeplex上看看哦!

有了HtmlAgilityPack,那上面的数据分析就很简单啦:我们看下信息板View的ViewModel中的相关代码就很清楚了:

 1 void Refresh()
2 {
3 this.Async(new Func<object>(() =>
4 {
5 List<Ticket> result = new List<Ticket>();
6 string strHtml;
7 bool geted = BuyTicketService.GetTickets(out strHtml);
8 if (geted)
9 {
10 //通过strHtml解析出Ticket
11 string[] sArry = Regex.Split(strHtml, @"\\n");
12 foreach (var strLine in sArry)
13 {
14 HtmlDocument document = new HtmlDocument();
15 document.LoadHtml(strLine);
16 var node = document.DocumentNode.SelectSingleNode("//input[@type='button']");
17 if (node != null)
18 {
19 var attribute = node.Attributes["onclick"];
20 if (attribute != null)
21 {
22 string temp = Utility.SplitString(attribute.Value,
23 "'",
24 "'");
25 sArry = Regex.Split(temp, "#");
26
27 result.Add(new Ticket(sArry[0],
28 string.Format("{0}/{1}",sArry[7],sArry[2]),
29 string.Format("{0}/{1}",sArry[8],sArry[6]),
30 temp));
31 }
32 }
33 }
34 }
35 return result;
36 }), new Action<object>(args =>
37 {
38 List<Ticket> tickets = args as List<Ticket>;
39 if (tickets != null)
40 {
41 m_Tickets = tickets;
42 this.NotifyPropertyChanged(m => m.Tickets);
43 }
44 }));
45 }

 

当然,这个页面的数据请求显示也必须要异步哦, 而且最好向我做的这样,有个定时器间隔时间刷新,咱就算买不到票,咱看看这票源信息也是个满足么,呵呵!

 

开玩笑了,主要哥哥我今年不回家,要去丈母娘加过年咯,所以俺也不担心买票问题,  各位回家的朋友还是要上点心哦,  

 

我写这文章 放出程序不是为了给您回家买票,俺想 您回家不需要了,回来总还要买票吧,看看文章,学点东西,自己再去完善下,你也就没白当程序员哦!

 

最后一点,点击预定后的界面、:

 



 

做到这里我就没做下去了,一来我没什么时间,都是早上早点去公司挤点,晚上下班搞搞,上个周末2天还把U盘丢公司浪费了2天。

做到这里也主要是破12306太卡, 抓不到最后的数据,我也就不着急去完善了, 在前面2页添加了个“跳转至浏览器” 通过浏览器打开登录成功的页面解燃眉之需!

 

最后附上完整项目:有时间有兴趣的可继续完善,我有时间也会写完,也算是个做个这东西的一个交代; 最好希望咱天朝火车有朝一日不用抢票!

代码:

 /Files/cxwx/BuyTicket.zip    可能引用的组件有点大,我只包含了项目,用到的组件都是nuget来的,缺少第三方组件的自己nuget下

 

最后补充下:SimpleMVVM这框架Command上有个BUG,它NND 

 



 

 

posted @ 2012-01-10 20:30  lianghugg  阅读(8772)  评论(22编辑  收藏  举报