關於小米手機網站搶購的一點技術分析


先PS一下:最近小米手機火了,看起來好像地球人已經不能阻止它的發展趨勢了

其實本文論述的技術也並非小米手機專用,只是用小米手機來做借鑒,但課題起源於朋友請求幫忙購買小米手機,於是借助專業知識寫了一個搶購的工具,拿出來和大家分享一下。說叫搶購工具,其實就是自動下訂單而已,因為小米手機網站的訂單只要在72小時內完成支付就可以,所以在第一時間完成下單以后找閑暇時間進行支付就可以了。

再PS一下:【聲明】本工具原理為模擬瀏覽器訪問和執行頁面,不包含任何入侵和破壞行為。本工具的優點只是自動化處理以及節省不必要的流量耗費。

1、首先分析任務實現需要操作的步驟:

  a、登錄系統,此步驟中小米商城網站使用了通行證認證模式,即需要將表單提交到passport.xiaomi.com,認證成功后通過token傳遞回商城網站的callback頁面。

  b、清空購物車,本步驟可選。因為是工具自動購買,防止錯誤下單,可在每次執行操作前自動清空購物車,購物車清空后會302到購物車頁面,http://order.xiaomi.com/cart,頁面提示“清空購物車成功”。

  c、將商品放入購物車。此步驟小米網站沒有使用post,而是get,通過url將商品id以及數量傳遞給服務器,並且還使用了友好url,例如 http://order.xiaomi.com/cart/add/1016-0-1 是將 小米手機M1放入購物車,其中1016為小米手機的商品id,0表示此商品為單品(非組合套裝),1表示購買一個。以此類推。放入購物車以后會自動302到購物車頁面,不過此時url為 http://order.xiaomi.com/cart/index,顯示購物車中的商品。

  d、提交訂單。本步驟同樣為get方式,直接請求地址 http://order.xiaomi.com/buy/checkout 即開始將購物車中的商品作為一個訂單進行提交,此時進入訂單詳細信息填寫頁面,填寫收貨人、收貨地址、電話、發票等信息,確認訂單需要post,請求的地址仍然是 http://order.xiaomi.com/buy/checkout。

2、關鍵技術點分析及實現

  清楚了任務要求,接下來分析技術點,鑒於本人對C#比較熟悉,故選用C#語言實現,采用了Winform項目框架。

  a、核心需求即是通過http協議下載頁面,以下為本人使用的核心代碼:

View Code
  1                     Uri uri = rawUri;
2 HttpWebSession session = new HttpWebSession()
3 {
4 RequestUri = uri,
5 };
6 var _request = session.Request =
7 (HttpWebRequest)WebRequest.Create(uri);
8 _httpWebSessions.Add(session);
9 if (Proxy != null)
10 {
11 _request.Proxy = Proxy;
12 }
13 _request.AllowAutoRedirect = false;
14 if (Uri.UriSchemeHttps == uri.Scheme)
15 {
16 ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(RemoteCertificateValidation);//
17 }
18 if (Credentials != null)
19 {
20 _request.Credentials = Credentials;
21 }
22 if (!string.IsNullOrEmpty(Accept))
23 {
24 _request.Accept = Accept;
25 }
26
27 if (!string.IsNullOrEmpty(AcceptLanguage))
28 {
29 _request.Headers.Add("Accept-Language", AcceptLanguage);
30 }
31 if (!string.IsNullOrEmpty(AcceptCharset))
32 {
33 _request.Headers.Add("Accept-Charset", AcceptCharset);
34 }
35 _request.KeepAlive = KeepAlive;
36 if (!string.IsNullOrEmpty(RefererUrl))
37 {
38 _request.Referer = RefererUrl;
39 }
40 switch (_acceptEncoding)
41 {
42 case AcceptEncoding.GZIP:
43 {
44 _request.Headers.Add("Accept-Encoding", "gzip");
45 break;
46 }
47 case AcceptEncoding.DEFLATE:
48 {
49 _request.Headers.Add("Accept-Encoding", "deflate");
50 break;
51 }
52 case AcceptEncoding.BOTH:
53 {
54 _request.Headers.Add("Accept-Encoding", "gzip,deflate");
55 break;
56 }
57 }
58 if (_request.CachePolicy == null || _request.CachePolicy.Level != _cacheLevel)
59 {
60 _request.CachePolicy = new RequestCachePolicy(_cacheLevel);
61 }
62 if (_headers != null && _headers.Count > 0)
63 {
64 foreach (KeyValuePair<string, string> item in _headers)
65 {
66 try
67 {
68 _request.Headers.Add(item.Key, item.Value);
69 }
70 catch
71 { }
72 }
73 }
74 _request.UserAgent = _userAgent;
75 _request.Referer = RefererUrl;
76 if (TimeOut != 0)
77 {
78 _request.Timeout = TimeOut;
79 }
80 if (postData == null)
81 {
82 if (postDictionary != null)
83 {
84 foreach (var item in postDictionary)
85 {
86 postText += item.Key + "=" + item.Value + "&";
87 }
88 postText = postText.Trim('&');
89 }
90 if (!string.IsNullOrEmpty(postText))
91 {
92 postData = _encoding.GetBytes(postText);
93 }
94 }
95 if (_cookieContainer != null)
96 {
97 string strCookieHeader = _cookieContainer.GetCookieHeader(uri);
98 if (!string.IsNullOrEmpty(strCookieHeader))
99 {
100 _request.Headers["Cookie"] = strCookieHeader;
101 }
102 }
103 if (postData != null)
104 {
105 session.PostedData = postData;
106 _request.Method = "POST";
107 _request.ContentType = "application/x-www-form-urlencoded";
108 _request.ContentLength = postData.Length;
109 Stream requestStream = _request.GetRequestStream();
110 requestStream.Write(postData, 0, postData.Length);
111 requestStream.Close();
112 }
113 var _response = session.Response = (HttpWebResponse)_request.GetResponse();
114 if (_cookieContainer != null)
115 {
116 string strSetCookieHeader = _response.Headers["Set-Cookie"];
117 if (!string.IsNullOrEmpty(strSetCookieHeader))
118 {
119 _cookieContainer.SetCookies(_response.ResponseUri, strSetCookieHeader);
120 }
121 }
122 StatusCode = _response.StatusCode;
123 _requestCookieCollection = _cookieContainer.GetCookieCollection(rawUri);
124 _response.Cookies = _cookieContainer.GetCookieCollection(_response.ResponseUri);
125 if (IsEncodingAccepted(_response, AcceptEncoding.GZIP))
126 {
127 responseStream = new GZipStream(_response.GetResponseStream(), CompressionMode.Decompress);
128 }
129 else if (IsEncodingAccepted(_response, AcceptEncoding.DEFLATE))
130 {
131 responseStream = new DeflateStream(_response.GetResponseStream(), CompressionMode.Decompress);
132 }
133 else
134 {
135 responseStream = _response.GetResponseStream();
136 }
137
138 if (StatusCode == HttpStatusCode.Redirect || StatusCode == HttpStatusCode.Moved)
139 {
140 if (_allowAutoRedirect)
141 {
142 responseStream.Close();
143 string strRedirectUrl = _response.Headers["Location"];
144 var stream = GetStream(new Uri(rawUri, strRedirectUrl), true, null, null, null, null, null);
145 return stream;
146 }
147 // return GetStream(
148 }
149 else if (StatusCode == HttpStatusCode.Unauthorized)
150 {
151 string strAuthorizition = OnAuthorizition();
152 if (!string.IsNullOrEmpty(strAuthorizition))
153 {
154
155 }
156 }

  上述代碼是本人多年積累的一個類庫中的一點核心代碼,解決了諸多常見問題,比如cookieContainer對domain識別異常,https訪問等等,如有需要本人可提供下載。

  b、邏輯處理的自動機

   鑒於web訪問過程中容易出現的各種不確定性因素,經常會出現請求同一個地址卻返回不一樣的內容,多數為302活301。本人使用了注冊處理函數的方式,每次請求完以后,根據返回的頁面url找到相應的處理函數進行操作,比如登錄時,發送的是指向http://passport.xiaomi.com/index.php?...的一個請求,但返回結果往往變成 http://www.xiaomi.com/,在相應的處理函數中就需要進行清空購物車或者將商品放入購物車。

 

View Code
 1         protected virtual void ProcessHttpSessions(ProcessState state)
2 {
3 state.Processed = true;
4 if (RunState == RunState.Pauseing)
5 {
6 OnLog("==========被用戶暫停==========");
7 ChangeState(RunState.Paused);
8 state.Break = true;
9 return;
10 }
11 if (RunState == RunState.PauseingByNetService)
12 {
13 OnLog("==========被換IP服務暫停==========");
14 ChangeState(RunState.PausedByNetService);
15 state.Break = true;
16 return;
17 }
18 if (RunState == RunState.Stopping)
19 {
20 OnLog("==========被用戶停止==========");
21 ChangeState(RunState.Stoped);
22 state.Break = true;
23 return;
24 }
25
26 if (_httpClient == null || _httpClient.HttpWebSessions == null)
27 {
28 state.Continue = true;
29 return;
30 }
31 int oldSessionCount = _httpClient.HttpWebSessions.Count;
32 Action<ProcessState> processerAction = null;
33 if (string.IsNullOrEmpty(_httpClient.RequestUrl))
34 {
35 OnLogError("沒有要處理的請求");
36 state.Break = true;
37 return;
38 }
39 if (httpSessionProcesser.ContainsKey(_httpClient.RequestUrl))
40 {
41 processerAction = httpSessionProcesser[_httpClient.RequestUrl];
42 }
43 if (processerAction == null)
44 {
45 foreach (var processer in httpSessionProcesser)
46 {
47 if (_httpClient.RequestUrl.StartsWith(processer.Key) ||
48 Regex.IsMatch(_httpClient.RequestUrl, processer.Key))
49 {
50 processerAction = processer.Value;
51 break;
52 }
53 }
54 }
55 if (processerAction != null)
56 {
57 processerAction(state);
58 if (state.Break || state.Continue)
59 {
60 return;
61 }
62 if (!state.Processed)
63 {
64 ProcessHttpSessions(state);
65 }
66 return;
67 }
68 OnLogError("出錯了,RequestUrl:" + _httpClient.RequestUrl);
69 OnLogError("ResponseUrl:" + _httpClient.ResponseUrl);
70 OnLogError("ResponseText:" + _httpClient.ResponseText);
71 state.Break = true;
72 }

  
  經過實踐和改進,本人已將上述部分分離成獨立類庫,如有需要可提供,以進行參考和交流。

 3、軟件界面布局,界面截圖已隱藏掉部分敏感信息

  

最后一條PS:本人於2012年1月4日下午使用該工具成功下單若干個,並且現在還有一個尚未付款的購機名額,如有需要可留QQ詳談。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM