不知道是飢餓營銷仍是真的供不該求,小米的火熱真的是沒法阻擋。衆多產品一一亮相,着實吸引眼球,可是一機難求的局面沒有改善,讓衆多米粉敗興而歸。咱們來實現一個簡單的小米搶購軟件,讓搶購之路多上那麼一點點但願。html
首先要說明的是小米搶購過程當中的不少頁面和請求地址都是在開放搶購當天時間點到了以後纔開放,搶購結束會關閉,因此你在按照博客的內容本身實現的過程當中有請求地址不能訪問的,請在搶購開始以後測試,樓主解決不了這個問題。我是在第一次搶購的時候記錄請求了哪些地址,作好簡單的邏輯以後第二次搶購的時候驗證。軟件是兩個月前完成,當時是可用的。小米搶購的邏輯常常改變,最近這幾回沒搶,不肯定中間請求的地址是否失效。web
下面開始分析實現的過程吧。json
第一步,模擬登錄。在小米首頁點擊登錄以後能夠看到登錄頁面,要求輸入郵箱/ID、密碼,小米的登錄沒有驗證碼,相對簡單了不少。點擊登錄按鈕能夠看到請求的地址是 https://account.xiaomi.com/pass/serviceLoginAuth2,請設置好對應的Request Header,請求參數見下圖:cookie
user 和 pwd 是你的用戶名和密碼,其餘的保持不變應該就能夠,返回的結果裏 desc 是「成功」說明登錄成功,這個時候能拿到一個返回的cookie,這個cookie不是咱們最後須要的cookie,還須要一步請求以後拿到的cookie才能用,從登錄頁面請求的抓包結果來看確實還有一步。從上一步的返回的結果裏得到notificationUrl的值,形如:/auth/service?userId=xxxxxxxx&_sign=xxxxxxxx&nonce=xxxxxxxx,完整地址爲 https://account.xiaomi.com/auth/service?userId=xxxxxxxx&_sign=xxxxxxxx&nonce=xxxxxxxx,帶着上一步拿到的cookie訪問這個頁面,拿到返回的cookie保存起來,這個cookie就是可用的,模擬登錄到這裏就完成了。ide
第二步,得到手機版本選擇頁面的地址。請求地址形如:http://tp.hd.mi.com/getpath/cn?m=1&jsonpcallback=getpath&_=1416887144988,最後一個參數爲當前時間的時間戳,這個地址只有到開放購買的時候纔有效,平時訪問是不通的。從返回的結果中拿到path對應的值,形如:http://s1.mi.com/open/3612A6B4D011142E4D533DE85CCBA5D1/choosePhone.html?_20141125,這個頁面是咱們在搶購的時候看到的手機版本選擇的頁面,獲取頁面內容,裏面有不少有用的信息。測試
先看一下這個頁面裏的部分js內容。jsonp
1 init: function() { 2 this.inTheQueue = !1, 3 this.phoneSku = "", 4 this.phoneType = "", 5 this.hdinfoData = null, 6 this.startTime = new Date("2014/12/9 12:00:00").getTime() / 1e3, 7 this.nextDate = "12月16日", 8 this.showMod = !0, 9 this.modType = null, 10 this.fkNum = 0, 11 this.isReg = "true", 12 this.hdget_date_tmp = "{{M}}md{{Y}}y47d15s", 13 this.cookies = { 14 isStart: "XM_Hd_Start", 15 buySucc: "XM_Buy_Succ", 16 userId: "userId", 17 login: "xm_order_btauth" 18 }, 19 this.home = "http://s1.mi.com/open/index.html", 20 this.hdgetUrl = "http://tp.hd.mi.com/hdget/cn?product={{SKU}}&addcart=1&m=1&fk={{FK}}&uagent={{TODAY}}", 21 this.hdinfoUrl = "http://tp.hd.mi.com/hdinfo/cn", 22 this.timestampUrl = "http://tp.hd.mi.com/gettimestamp", 23 this.getmodeUrl = "http://tp.hd.mi.com/getmode/cn/?product=", 24 this.nextBookUrl = "http://a.hd.mi.com/productv2/book/a/18#MIPHONE", 25 this.ordeSite = "http://order.mi.com", 26 this.shopCartUrl = this.ordeSite + "/cart/add/{{SKU}}?source=bigtap&token={{TOKEN}}", 27 this.addCartNext = this.ordeSite + "/event/success?goodsid={{SKU}}", 28 this.loginUrl = "http://s1.mi.com/zt/xm_account/limitfacade.html?third=http%253A%252F%252Forder.mi.com%252Flogin%252Fcallback%253Ffollowup%253Dhttp%25253A%25252F%25252Fs1.mi.com%25252Fopen%25252Findex.html%2526sign%253DNjEzYmU3ZTJkOWRlY2FiZDQ5NDEwNzEyZjNiMjg0NDA0MGYxYWY3Mg%252C%252C%26sid%3Dmi_eshop&sid=mi_eshop&callback=http%253A%252F%252Forder.mi.com%252Flogin%252Fcallback%253Ffollowup%253Dhttp%25253A%25252F%25252Fs1.mi.com%25252Fopen%25252Findex.html%2526sign%253DNjEzYmU3ZTJkOWRlY2FiZDQ5NDEwNzEyZjNiMjg0NDA0MGYxYWY3Mg%252C%252C&sign=dK3nqW%252FKhFM3Tl7Jyt9%252FGt3jOI8%253D", 29 this.noPresaleGoods = [], 30 this.noBookGoods = [], 31 this.isHm = ["2143300001", "2143400005", "2143200006", "2141600007", "2140700031"]; 32 var a = this; 33 return xmCookie(a.cookies.userId) && xmCookie(a.cookies.login) || (location.href = a.loginUrl), 34 xmCookie(a.cookies.isStart) ? a.getHdInfo() : a.checkTime(), 35 $("[data-close-target]").on("click", 36 function() { 37 var b = $(this).attr("data-close-target"); 38 return a.hideBox(b), 39 !1 40 }), 41 $("#submitBtn").on("click", 42 function() { 43 return ! a.phoneSku || $(this).hasClass("btn-disabled") ? (alert("請選擇您要購買的手機"), !1) : $.inArray(a.phoneSku, a.isHm) >= 0 ? void(location.href = "http://order.mi.com/event/selectPacket/goodsid/" + a.phoneSku) : "true" !== a.isReg ? (a.showBox("Tip", 44 function() { 45 a.getTipMsg("reg") 46 }), !1) : void(a.showMod ? (a.getmode(), a.showBox("Fk")) : (a.startQueue(), a.getDmSys())) 47 }), 48 $("#boxCacheBtn").on("click", 49 function() { 50 a.hideBox("all"), 51 $("#submitBtn").trigger("click") 52 }), 53 "undefined" != typeof HDOVER && HDOVER === !0 ? (location.href = a.home, !1) : ($(".J_nextDate").html(a.nextDate), $(".J_bookBtn").attr("href", a.nextBookUrl), $(".J_fkLoading").on("click", ".J_reloadFk", 54 function() { 55 $(this).parent().html('<img src="http://img03.mifile.cn/webfile/images/2014/cn/loading.gif">'), 56 a.getmode() 57 }), void $("#fkNum").on("keyup", 58 function() { 59 $(this).val().length; 60 $("#boxFkBtn").removeClass("btn-disabled").off().on("click", 61 function() { 62 a.checkFk() 63 }) 64 })) 65 }
仔細分析一下這段js,發現搶購後續的請求地址和參數格式在這裏都能找到,hdgetUrl,hdinfoUrl,getmodeUrl,shopCartUrl 這幾個後面都會用到,抓包分析後面幾步的過程能夠和這裏的地址驗證,先保存下來。下面要作的工做是計算js中{{TODAY}}對應的值,將hdgetUrl中的值替換,計算邏輯可用下面的,this
Pattern dateTmpPattern=Pattern.compile("hdget_date_tmp=\"(.*?)\""); Matcher dateTmpMatcher=dateTmpPattern.matcher(result); if(dateTmpMatcher.find()) { dateTmp=dateTmpMatcher.group(1); dateTmp=dateTmp.replace("{{Y}}",YEAR+"").replace("{{M}}",MONTH).replace("{{D}}",DAY); hdGetUrl=hdGetUrl.replace("{{TODAY}}",dateTmp); }
YEAR、MONTH、DAY 爲當前日期。url
第三步,得到產品狀態信息。這裏用到了 hdinfoUrl,請求地址爲http://tp.hd.mi.com/hdinfo/cn?jsonpcallback=hdinfo&_=1416888070538(下面不說明的時候比較長的數字應該就是指當前的時間戳)。請求返回的數據格式以下:spa
{"stime":1416979023,"pmstart":false,"status":{"2141700014":{"hdstart":false,"hdstop":true,"reg":true},"2143000004":{"hdstart":false,"hdstop":true,"reg":true},"2143400001":{"hdstart":false,"hdstop":true,"reg":true},"2143400004":{"hdstart":false,"hdstop":true,"reg":true},"2143600001":{"hdstart":false,"hdstop":true,"reg":true},"2144000012":{"hdstart":false,"hdstop":true,"reg":true},"2144100013":{"hdstart":false,"hdstop":true,"reg":true},"2144100014":{"hdstart":false,"hdstop":true,"reg":true}},"dbe5a2":false}
包含了每種產品的可售狀態,能夠根據這個信息判斷是否能購買,在這裏能夠過濾掉不能買的信息。
產品代號和名稱的對應關係我作了一個對應,這個信息比較老了,請根據每次購買的狀況更新:
2143000004----小米4聯通3G版 亮白16GB 1999
2143400001----小米4聯通3G版 亮白64GB 2499
2143100007----小米4聯通4G版 亮白16GB 1999
2143400004----小米4電信3G版 亮白16GB 1999
2144000012----小米4移動4G版 雅黑16GB 1999
2144100014----小米4移動4G版 雅黑64GB 2499
2144800007----小米4移動4G版 黑色(金屬原色框)16GB 1999
2143600001----小米4移動4G版 亮白16GB 1999
2144100013----小米4移動4G版 亮白64GB 2499
2143200006----紅米Note 移動4G加強版 899
2143400005----紅米Note 聯通4G加強版 899
2141600007----紅米1S聯通3G版 金屬灰 799
2140700031----紅米1S電信3G版 金屬灰 799
2143300001----紅米1S移動4G版 金屬灰 599
2141700014----小米路由器 mini 129
2143000001----小米手環 79
要購買哪一種產品只須要產品代號信息(應該是sku)就夠了。
第四步,獲取驗證碼或驗證問題。用到了getmodeUrl地址,請求路徑爲 http://tp.hd.mi.com/getmode/cn/?product=xxxxxxxxxx&jsonpcallback=getmode&_=1416888070539,將product值換成你要去買的產品的sku值。根據返回response頭的Content-Type值判斷是驗證碼圖案仍是驗證問題,image/jpeg 類型的返回信息應該是驗證碼,text/html 應該是驗證問題。根據不一樣類型,輸入答案後進入第五步。
第五步,驗證答案。地址和第四步中是相似的,多了一個答案參數,http://tp.hd.mi.com/getmode/cn/?product=2144000012&vecode=89&jsonpcallback=getmode&_=1416888070540,返回結果中code值是「1」說明驗證成功,能夠進行下一步,不然從新輸入驗證答案。
第六步,最後得到產品信息,得到下一步TOKEN對應的hdurl。這一步用到了hdgetUrl,http://tp.hd.mi.com/hdget/cn?product={{SKU}}&addcart=1&m=1&fk={{FK}}&uagent={{TODAY}},SKU爲要購買產品的sku,FK爲第四步出現驗證碼或問題的答案,TODAY已在前面替換成一個和日期有關動態的參數,拿到請求這個地址的返回結果,
hdcontrol({"d22a51":10,"login":true,"pmstart":false,"status":{"2144800007":{"hdstart":false,"hdstop":true,"hdurl":""}}})
其中hdurl有值的時候說明這一步是成功的,拿到這個hdurl進入第七步,若是你獲得的結果和我同樣沒有hdurl的值,那麼很差意思,你無法加入購物車,後面會提示出錯的,這一步能夠多試幾回,看看能不能運氣好就能買了。
第七步,加入到購物車。用到了shopCartUrl ,地址 http://order.mi.com/cart/add/2144000012?source=bigtap&token={{TOKEN}}&jsonpcallback=getdata,將這裏的 TOKEN 替換成第六步得到的 hdurl , 根據這個請求的返回結果判斷是否添加成功,
下面是返回數據的格式,
{"code":-1,"message":"\u6dfb\u52a0\u8d2d\u7269\u8f66\u9700\u8981\u767b\u5f55\uff0c\u8bf7\u5148\u767b\u5f55\uff01","msg":"\u6dfb\u52a0\u8d2d\u7269\u8f66\u9700\u8981\u767b\u5f55\uff0c\u8bf7\u5148\u767b\u5f55\uff01"}
能夠根據code的值判斷是否成功,後面的爲提示信息。
搶購過程當中的關鍵點都分析完了,再次強調一下,搶購的邏輯常常改變,不保證這個過程還適應如今的邏輯,須要本身在開放購買的時候實測。
上一張截圖