Après d'innombrables achats échoués, j'ai découvert que les commerçants libéraient une petite quantité de stock de temps en temps, et j'ai estimé visuellement le nombre d'unités qu'il y aurait à chaque fois. Si nous écrivons un script pour surveiller l'inventaire des produits 24 heures sur 24 et que, une fois la source des marchandises interrogée, nous essaierons de passer des commandes automatiquement, ce qui peut augmenter considérablement nos chances de succès.
L'achat précipité de marchandises par JD est principalement divisé en deux types :
Achat sur rendez-vous : ouvert à l'achat sur place, ce qui est le même que le processus de commande de produits de vente flash ordinaires : interface d'achat séparée ; et le processus de commande.
Bien sûr, cette période est destinée aux achats urgents de réservation ou aux commandes en rupture de stock, c'est-à-dire que le processus de commande global est le même que lors de l'achat de produits ordinaires :
Connectez-vous à votre compte→ panier→ Sélectionnez le produit d'achat urgent→ Cliquez pour commander → Cliquez sur Soumettre la commande→
登录账号 → 进入购物车 → 选择抢购商品 → 点击去结算 → 点击提交订单 → 选择付款方式并付款
。
由于笔者本人没有一个可以抓包的客户端,决定采用京东 WEB 端接口实现我们的脚本程序。
于是经过对京东网页下单流程的分析,将我们的脚本程序分为四个模块:账号登录模块、库存监听模块、购物车管理模块、订单管理模块。
由于使用账号密码时有验证码限制,此处采用扫码登录方式绕过。
如对扫码登录不熟悉或感兴趣的同学可以查看周周之前的博文 扫码登录原理和实现。
本次只要针对京东登录页进行抓包分析,找到几个有用接口:
def getQRcode(self): url = 'https://qr.m.jd.com/show' payload = { 'appid': 133, 'size': 147, 't': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.userAgent, 'Referer': 'https://passport.jd.com/new/login.aspx', } resp = self.sess.get(url=url, headers=headers, params=payload) if not self.respStatus(resp): return None return resp.content
def getQRcodeTicket(self): url = 'https://qr.m.jd.com/check' payload = { 'appid': '133', 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)), 'token': self.sess.cookies.get('wlfstk_smdl'), '_': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.userAgent, 'Referer': 'https://passport.jd.com/new/login.aspx', } resp = self.sess.get(url=url, headers=headers, params=payload) if not self.respStatus(resp): return False respJson = self.parseJson(resp.text) if respJson['code'] != 200: return None else: return respJson['ticket']
def getQRcodeTicket(self): url = 'https://qr.m.jd.com/check' payload = { 'appid': '133', 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)), 'token': self.sess.cookies.get('wlfstk_smdl'), '_': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.userAgent, 'Referer': 'https://passport.jd.com/new/login.aspx', } resp = self.sess.get(url=url, headers=headers, params=payload) if not self.respStatus(resp): return False respJson = self.parseJson(resp.text) if respJson['code'] != 200: return None else: return respJson['ticket']
此时验证 Ticket 有效后使用pickle
def getItemDetail(self, skuId): url = 'https://item.jd.com/{}.html'.format(skuId) page = requests.get(url=url, headers=self.headers) html = etree.HTML(page.text) vender = html.xpath( '//div[@class="follow J-follow-shop"]/@data-vid')[0] cat = html.xpath('//a[@clstag="shangpin|keycount|product|mbNav-3"]/@href')[ 0].replace('//list.jd.com/list.html?cat=', '') if not vender or not cat: raise Exception('获取商品信息失败,请检查SKU是否正确') detail = dict(catId=cat, venderId=vender) return detail
def getItemStock(self, skuId, num, areaId): item = self.itemDetails.get(skuId) if not item: return False url = 'https://c0.3.cn/stock' payload = { 'skuId': skuId, 'buyNum': num, 'area': areaId, 'ch': 1, '_': str(int(time.time() * 1000)), 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)), # get error stock state without this param 'extraParam': '{"originid":"1"}', # get 403 Forbidden without this param (obtained from the detail page) 'cat': item.get('catId'), # return seller information with this param (can't be ignored) 'venderId': item.get('venderId') } headers = { 'User-Agent': self.userAgent, 'Referer': 'https://item.jd.com/{}.html'.format(skuId), } respText = '' try: respText = requests.get( url=url, params=payload, headers=headers, timeout=self.timeout).text respJson = self.parseJson(respText) stockInfo = respJson.get('stock') skuState = stockInfo.get('skuState') # 商品是否上架 # 商品库存状态:33 -- 现货 0,34 -- 无货 36 -- 采购中 40 -- 可配货 stockState = stockInfo.get('StockState') return skuState == 1 and stockState in (33, 40)
Verify Ticket
def uncheckCartAll(self): """ 取消所有选中商品 return 购物车信息 """ url = 'https://api.m.jd.com/api' headers = { 'User-Agent': self.userAgent, 'Content-Type': 'application/x-www-form-urlencoded', 'origin': 'https://cart.jd.com', 'referer': 'https://cart.jd.com' } data = { 'functionId': 'pcCart_jc_cartUnCheckAll', 'appid': 'JDC_mall_cart', 'body': '{"serInfo":{"area":"","user-key":""}}', 'loginType': 3 } resp = self.sess.post(url=url, headers=headers, data=data) # return self.respStatus(resp) and resp.json()['success'] return resp
pickle
pour enregistrer localement les cookies dans la session du programme pour une prochaine utilisation.
def addCartSku(self, skuId, skuNum): """ 加入购入车 skuId 商品sku skuNum 购买数量 retrun 是否成功 """ url = 'https://api.m.jd.com/api' headers = { 'User-Agent': self.userAgent, 'Content-Type': 'application/x-www-form-urlencoded', 'origin': 'https://cart.jd.com', 'referer': 'https://cart.jd.com' } data = { 'functionId': 'pcCart_jc_cartAdd', 'appid': 'JDC_mall_cart', 'body': '{\"operations\":[{\"carttype\":1,\"TheSkus\":[{\"Id\":\"' + skuId + '\",\"num\":' + str(skuNum) + '}]}]}', 'loginType': 3 } resp = self.sess.post(url=url, headers=headers, data=data) return self.respStatus(resp) and resp.json()['success']
def changeCartSkuCount(self, skuId, skuUid, skuNum, areaId): """ 修改购物车商品数量 skuId 商品sku skuUid 商品用户关系 skuNum 购买数量 retrun 是否成功 """ url = 'https://api.m.jd.com/api' headers = { 'User-Agent': self.userAgent, 'Content-Type': 'application/x-www-form-urlencoded', 'origin': 'https://cart.jd.com', 'referer': 'https://cart.jd.com' } body = '{\"operations\":[{\"TheSkus\":[{\"Id\":\"'+skuId+'\",\"num\":'+str( skuNum)+',\"skuUuid\":\"'+skuUid+'\",\"useUuid\":false}]}],\"serInfo\":{\"area\":\"'+areaId+'\"}}' data = { 'functionId': 'pcCart_jc_changeSkuNum', 'appid': 'JDC_mall_cart', 'body': body, 'loginType': 3 } resp = self.sess.post(url=url, headers=headers, data=data) return self.respStatus(resp) and resp.json()['success']
def getCheckoutPage(self): """获取订单结算页面信息 :return: 结算信息 dict """ url = 'http://trade.jd.com/shopping/order/getOrderInfo.action' # url = 'https://cart.jd.com/gotoOrder.action' payload = { 'rid': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.userAgent, 'Referer': 'https://cart.jd.com/cart', }
def submitOrder(self): """提交订单 :return: True/False 订单提交结果 """ url = 'https://trade.jd.com/shopping/order/submitOrder.action' # js function of submit order is included in https://trade.jd.com/shopping/misc/js/order.js?r=2018070403091 data = { 'overseaPurchaseCookies': '', 'vendorRemarks': '[]', 'submitOrderParam.sopNotPutInvoice': 'false', 'submitOrderParam.trackID': 'TestTrackId', 'submitOrderParam.ignorePriceChange': '0', 'submitOrderParam.btSupport': '0', 'riskControl': self.risk_control, 'submitOrderParam.isBestCoupon': 1, 'submitOrderParam.jxj': 1, 'submitOrderParam.trackId': self.track_id, 'submitOrderParam.eid': self.eid, 'submitOrderParam.fp': self.fp, 'submitOrderParam.needCheck': 1, }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!