摘要:項目地址求個在現(xiàn)在,商家一年不賣貨,雙賣出一年的貨是大家都知道的事實了,總得來說調(diào)一調(diào)蚊子腿的價格,聊勝于無,但是也會有些神價格會出現(xiàn),這時候買到就是賺到本來是想趁著雙組臺電腦,買個的板套裝,沒想到京東的一直是無貨的狀態(tài),這幾天有貨了,價格
項目地址 求個 star
在現(xiàn)在,商家一年不賣貨,雙11賣出一年的貨是大家都知道的事實了,總得來說調(diào)一調(diào)蚊子腿的價格,聊勝于無,但是也會有些神價格會出現(xiàn),這時候買到就是賺到
本來是想趁著雙11組臺電腦,買個 Z370 的板U套裝,沒想到京東的 8700k 一直是無貨的狀態(tài),這幾天有貨了,價格漲到了3999,簡直不能忍,看了下板U套裝比較劃算,但是有些板U套裝是不支持自動下單的,所以 gayhub 搜搜看有沒有爬蟲可以監(jiān)聽到貨自動下單的,正好有了這哥們的 jd-autobuy Python 腳本,還有 Go 的,看了下接口已經(jīng)很齊全了,來個 node 版本的助助興
這次用到的 http 庫是 axios,支持客戶端和服務端,總得來說語法還是很簡潔的,在這之前還有個 superagent 庫,看了下也差不多,只不過 superagent 在 response 上多處理了下
因為在 vue 中使用了 axios,這次想試試服務端的能力咋樣,還是一如既往的好,滋次一波
先寫個 request header ,畢竟是服務端,沒有瀏覽器幫你處理 User-Agent,所以自己去瀏覽器請求下然后把 header 拿到
const defaultInfo = { header: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", "Content-Type": "text/plain;charset=utf-8", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.6,en;q=0.4,en-US;q=0.2", "Connection": "keep-alive", }, }
header 拿到我們就可以偽裝成瀏覽器去請求二維碼圖片了,京東的掃碼圖片地址 https://qr.m.jd.com/show,沒有多余的技巧,直接用 axios 來個get請求即可
async function requestScan() { const result = await request({ method: "get", url: "https://qr.m.jd.com/show", headers: defaultInfo.header, params: { appid: 133, size: 147, t: new Date().getTime() }, responseType: "arraybuffer" }) }
參數(shù) appid size 和 t 可以通過抓包拿到的,這里注意我 responseType 用的 arraybuffer,默認值是 json ,buffer 主要是方便我們來像本地寫入圖片,我們來處理下 res
defaultInfo.cookies = cookieParser(result.headers["set-cookie"]) defaultInfo.cookieData = result.headers["set-cookie"]; const image_file = result.data; await writeFile("qr.png", image_file)
async function writeFile(fileName, file) { return await new Promise((resolve, reject) => { fs.writeFile(fileName, file, "binary", err => { opn("qr.png") resolve() }) }) }
這一步 cookie 已經(jīng)拿到了,這里我做了兩步處理,一步是自己寫的 cookieParser 把參數(shù)進行解析,主要是拿到其中的 wlfstk_smdl,接下來會用到,然后直接 writeFile 寫入圖片就行了,寫好了之后利用 opn 打開圖片,sindresorhus 大神的 opn 庫還是蠻好用的,可以指定程序打開圖片,文件等
在掃碼之前我們要監(jiān)聽掃碼的狀態(tài)
async function listenScan() { let flag = true let ticket while (flag) { const callback = {} let name; callback[name = ("jQuery" + getRandomInt(100000, 999999))] = data => { console.log(` ${data.msg || "掃碼成功,正在登錄"}`) if (data.code === 200) { flag = false; ticket = data.ticket } } const result = await request({ method: "get", url: "https://qr.m.jd.com/check", headers: Object.assign({ Host: "qr.m.jd.com", Referer: "https://passport.jd.com/new/login.aspx", Cookie: defaultInfo.cookieData.join(";") }, defaultInfo.header), params: { callback: name, appid: 133, token: defaultInfo.cookies["wlfstk_smdl"], _: new Date().getTime() }, }) eval("callback." + result.data); await sleep(1000) } return ticket }
一開始的想法是開個定時器來輪詢下:"好沒好呀",沒有我1秒后再來問下,這里使用 async/await
的強大功能實現(xiàn)個 sleep,比 setTimeout 的使用更優(yōu)雅而且對于異步的處理也能夠操控自如
function sleep(ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve() }, ms) }) }
這里我們把 header 組合一下,剛剛拿到的 cookie 帶上,并加上 host 和 referer 來表明我們從哪里來要到哪里去,參數(shù)里面的 token 就是之前解析 cookie 拿到的 wlfstk_smdl ,這個接口應該約定的 jQuery jsonp(京東看了下 jsonp 還是蠻多的),所以我這里使用一個 callback 來模擬一個 jsonp 的執(zhí)行,看返回的 code 和 msg,code 為 200 的時候說明掃碼成功了,這時候 msg 是沒有的,所以自定義下,其他狀態(tài)是有 msg 的,直接輸出就 OK 了,掃碼成功我們要拿到 ticket,這個從字面上理解就知道了,大兄弟你拿到入場券了,并且 ticket 下單的時候也是需要的,存起來
這時候用你的手機打開京東掃一掃打開的二維碼圖片,確認后掃碼成功,用入場券登錄去
async function login(ticket) { const result = await request({ method: "get", url: "https://passport.jd.com/uc/qrCodeTicketValidation", headers: Object.assign({ Host: "passport.jd.com", Referer: "https://passport.jd.com/uc/login?ltype=logout", Cookie: defaultInfo.cookieData.join("") }, defaultInfo.header), params: { t: ticket }, }) defaultInfo.header["p3p"] = result.headers["p3p"] return defaultInfo.cookieData = result.headers["set-cookie"] }
這一步?jīng)]什么說的,入場券有了,理所應當?shù)卿洺晒α?,拿?p3p 參數(shù)并且更新下 cookie 這樣一個合法的身份就誕生了
有了身份后就可以去 get 商品頁面,這一步需要拿三個請求的信息拼一下
拿到商品頁面的 html
function goodInfo(goodId) { const stockLink = `http://item.jd.com/${goodId}.html` return request({ method: "get", url: stockLink, headers: Object.assign(defaultInfo.header, { cookie: defaultInfo.cookieData.join("") }), responseType: "arraybuffer" }) }
拿到商品的價格
async function goodPrice(stockId) { const callback = {} let name; let price; callback[name = ("jQuery" + getRandomInt(100000, 999999))] = data => { price = data } const result = await request({ method: "get", url: "http://p.3.cn/prices/mgets", headers: Object.assign(defaultInfo.header, { cookie: defaultInfo.cookieData.join("") }), params: { type: 1, pduid: new Date().getTime(), skuIds: "J_" + stockId, callback: name, }, }) eval("callback." + result.data) return price }
拿到商品的狀態(tài)
async function goodStatus(goodId, areaId) { const callback = {} let name; let status callback[name = ("jQuery" + getRandomInt(100000, 999999))] = data => { status = data[goodId] } const result = await request({ method: "get", url: "http://c0.3.cn/stocks", headers: Object.assign(defaultInfo.header, { cookie: defaultInfo.cookieData.join("") }), params: { type: "getstocks", area: areaId, skuIds: goodId, callback: name, }, responseType: "arraybuffer" }) const data = iconv.decode(result.data, "gb2312") eval("callback." + data) return status }
最后 Promise.all 一波帶走
async function runGoodSearch() { let flag = true while (flag) { const all = await Promise.all([goodPrice(defaultInfo.goodId), goodStatus(defaultInfo.goodId, defaultInfo.areaId), goodInfo(defaultInfo.goodId)]) const body = $.load(iconv.decode(all[2].data, "gb2312")) outData.name = body("div.sku-name").text().trim() const cartLink = body("a#InitCartUrl").attr("href") outData.cartLink = cartLink ? "http:" + cartLink : "無購買鏈接" outData.price = all[0][0].p outData.stockStatus = all[1]["StockStateName"] outData.time = formatDate(new Date(), "yyyy-MM-dd hh:mm:ss") console.log() console.log(` 商品詳情------------------------------`) console.log(` 時間:${outData.time}`) console.log(` 商品名:${outData.name}`) console.log(` 價格:${outData.price}`) console.log(` 狀態(tài):${outData.stockStatus}`) console.log(` 商品連接:${outData.link}`) console.log(` 購買連接:${outData.cartLink}`) const statusCode = all[1]["StockState"] // 如果有貨就下單 // 33 有貨 34 無貨 if (+statusCode === 33) { flag = false } else { await sleep(defaultInfo.time) } } }
這里要解析 dom,$ 就是有著 Node 版 jQuery 之稱的 cheerio,但是如果直接解析會亂碼,先轉(zhuǎn)碼,轉(zhuǎn)碼神器出場 iconv-lite,剩下的就是 jQuery 操作了,很久沒寫 jQuery 了,寫起來還是這么的順溜
defaultInfo 中的 goodId 是商品的 id,下面會說到,解析命令行的參數(shù)獲得的,在哪里能看到呢,來圖
areaId 是對應著區(qū)域的信息,畢竟每個城市的庫存都是不一樣的
京東購物的流程購物車先走一波,然后開始下單付款,有貨了我們加入購物車
async function addCart() { console.log() console.log(" 開始加入購物車") const result = await request({ method: "get", url: outData.cartLink, headers: Object.assign(defaultInfo.header, { cookie: defaultInfo.cookieData.join("") }), }) const body = $.load(result.data) const addCartResult = body("h3.ftx-02") if (addCartResult) { console.log(` ${addCartResult.text()}`) } else { console.log(" 添加購物車失敗") } }
沒什么可說的,加入后開始下單
async function buy() { const orderInfo = await request({ method: "get", url: "http://trade.jd.com/shopping/order/getOrderInfo.action", headers: Object.assign(defaultInfo.header, { cookie: defaultInfo.cookieData.join("") }), params: { rid: new Date().getTime(), }, responseType: "arraybuffer" }) const body = $.load(orderInfo.data) const payment = body("span#sumPayPriceId").text().trim() const sendAddr = body("span#sendAddr").text().trim() const sendMobile = body("span#sendMobile").text().trim() console.log() console.log(` 訂單詳情------------------------------`) console.log(` 訂單總金額:${payment}`) console.log(` ${sendAddr}`) console.log(` ${sendMobile}`) console.log() console.log(" 開始下單") const result = await request({ method: "post", url: "http://trade.jd.com/shopping/order/submitOrder.action", headers: Object.assign(defaultInfo.header, { cookie: defaultInfo.cookieData.join("") }), params: { "overseaPurchaseCookies": "", "submitOrderParam.btSupport": "1", "submitOrderParam.ignorePriceChange": "0", "submitOrderParam.sopNotPutInvoice": "false", "submitOrderParam.trackID": defaultInfo.ticket, "submitOrderParam.eid": defaultInfo.eid, "submitOrderParam.fp": defaultInfo.fp, }, }) if (result.data.success) { console.log(` 下單成功,訂單號${result.data.orderId}`) console.log("請前往京東商城及時付款,以免訂單超時取消") } else { console.log(` 下單失敗,${result.data.message}`) } }
其實這里 post http://trade.jd.com/shopping/... 這個就可以了,前面的一個請求是下單頁面拿一下訂單的信息展示下,這里會有兩個注意的點
商品的數(shù)量
京東下單是把購物車這個商品全部下單,不管數(shù)量的,比如你購物車已經(jīng)有一件這個商品了,那么前面的流程走完后購物車現(xiàn)在有兩件這個商品,下單后是下單了兩件,當然了這里是可以更改數(shù)量的,但是我沒寫
訂單的參數(shù)
上面下單的請求可以注意到三個陌生的參數(shù) submitOrderParam.trackID submitOrderParam.eid submitOrderParam.fp ,trackID 前面有拿到過,這里直接用就行了,那么 eid 和 fp 是從哪來的呢?答案是登錄頁面,但是這里有個坑是 request 返回的頁面拿到的 dom 元素是不行的,只能通過瀏覽器來,這也很好辦,Node 有 phantomjs,但是這里我用了 Chrome 出品的 puppeteer
puppeteer 使用也很簡單,它是基于 Node 的 headless Chrome 工具
puppeteer.launch().then(async browser => { console.log(" 初始化完成,開始抓取頁面") const page = await browser.newPage(); await page.goto("https://passport.jd.com/new/login.aspx"); await sleep(1000) console.log(" 頁面抓取完成,開始分析頁面") const inputs = await page.evaluate(res => { const result = document.querySelectorAll("input") const data = {} for (let v of result) { switch (v.getAttribute("id")) { case "token": data.token = v.value break case "uuid": data.uuid = v.value break case "eid": data.eid = v.value break case "sessionId": data.fp = v.value break } } return data }) Object.assign(defaultInfo, inputs) await browser.close(); console.log(" 頁面參數(shù)到手,關(guān)閉瀏覽器") console.log() console.log(" ------------------------------------- ") console.log(" 請求掃碼") console.log(" ------------------------------------- ") console.log() })
puppeteer 首先要 launch 后來生成一個 browser 的實例,我們用 browser 來新建一個頁面運行我們的網(wǎng)址,并且我們可以在它提供的 evaluate 方法中操作 DOM,上面的代碼也是很簡單的,一目了然
至此基本上一個自動下單的功能就完成了,再擴展下命令行參數(shù)
const args = require("yargs").alias("h", "help") .option("a", { alias: "area", demand: true, describe: "地區(qū)編號", }) .option("g", { alias: "good", demand: true, describe: "商品編號", }) .option("t", { alias: "time", describe: "查詢間隔ms", default: "10000" }) .option("b", { alias: "buy", describe: "是否下單", default: true }) .usage("Usage: node index.js -a 地區(qū)編號 -g 商品編號") .example("node index.js -a 2_2830_51810_0 -g 5008395") .argv;
這里我給了兩個必需的參數(shù)和兩個可選的參數(shù),-a 必須要的,地區(qū)編號,-g 必要要的,商品編號,-t 商品查詢的間隔時間,默認是10s,-b是否自動購買,默認是購買的,這里是 boolean,yargs 還是蠻好用的,也可以用 TJ 大神的 commander,都是一樣的
完整的代碼可以去下面的項目地址中查看
項目地址 求個 star
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89420.html
摘要:騰訊云雙活動地址點擊進入騰訊云年雙十一活動優(yōu)惠活動參與騰訊云活動,多重優(yōu)惠享不停一爆品秒殺爆款核云服務器首年元,每日場秒殺,全年冰點價。 騰訊云正式開啟2021年雙11智惠云集促銷活動,多重優(yōu)惠享不停,包括首購服務器低至0.4折直擊底價,爆款1核2G云服務器首年48元;新老用戶同享,領(lǐng)今年最大額度代金券;折上再享優(yōu)惠iPad Pr,額外10%返券等你拿,具體活動內(nèi)容如下。 ...
摘要:淺談秒殺系統(tǒng)架構(gòu)設(shè)計后端掘金秒殺是電子商務網(wǎng)站常見的一種營銷手段。這兩個項目白話網(wǎng)站架構(gòu)演進后端掘金這是白話系列的文章。 淺談秒殺系統(tǒng)架構(gòu)設(shè)計 - 后端 - 掘金秒殺是電子商務網(wǎng)站常見的一種營銷手段。 不要整個系統(tǒng)宕機。 即使系統(tǒng)故障,也不要將錯誤數(shù)據(jù)展示出來。 盡量保持公平公正。 實現(xiàn)效果 秒殺開始前,搶購按鈕為活動未開始。 秒殺開始時,搶購按鈕可以點擊下單。 秒殺結(jié)束后,按鈕按鈕變...
摘要:張晨表示,第四次零售革命必然是顛覆性的,因為它改變了零售的基礎(chǔ)設(shè)施。張晨表示,零售基礎(chǔ)設(shè)施是第四次零售革命的核心,其社會化更是推動整個零售行業(yè)變革的動力。 從用戶畫像個性化的描摹,到與供應商結(jié)合需求進行動態(tài)定價,再到無人運輸與云服務在基礎(chǔ)設(shè)施上的升級,圍繞零售的整條產(chǎn)業(yè)鏈,京東已經(jīng)在底層進行了長期的建設(shè)和夯實。 showImg(https://segmentfault.com/img/...
摘要:年騰訊云雙十一活動力針對企業(yè)用戶推出專享的高配置大帶寬云服務器優(yōu)惠核年元,核年元,核年元,適合各種企業(yè)建站使用。輕量云服務器騰訊云輕量應用服務器優(yōu)惠,可選上海廣州北京成都機房,我們可以自定義配置,其中核年元核年元核年元。 騰訊云2021年雙十一活動力度非常不錯,雙11智惠云集不管是個人還是企業(yè)都比較優(yōu)惠,這里小編來推薦下企業(yè)專區(qū)下面的輕量應用服務器,Lighthouse具有配置高、帶寬...
閱讀 3206·2021-09-22 15:05
閱讀 2763·2019-08-30 15:56
閱讀 1071·2019-08-29 17:09
閱讀 803·2019-08-29 15:12
閱讀 2084·2019-08-26 11:55
閱讀 3069·2019-08-26 11:52
閱讀 3381·2019-08-26 10:29
閱讀 1385·2019-08-23 17:19