Python实现JD口罩预约+抢购

疫情期间,口罩成了市面上的硬通货,简直就是“一罩难求”,不过在某些电商平台上有预约抢购的医用外科口罩,抢得到抢不到就要看个人运气了,但是我坚信科技改变生活,就用Python写了个抢购的脚本,可以自动预约,到点自动抢购JD。

ps:仅供技术交流

思路:

  1. 模拟登陆(参考github上已有大神写好的登陆模块)
  2. 输入需要抢购的物品,获取商品信息
  3. 到点自动抢购,提交订单

实现步骤:
4. 发现jd的抢购规则
找一个也需要预约抢购的商品,通过Fiddler抓包,找出目标接口
这里推荐去抓包一下N95的抢购,因为价格稍微有点高,所以一般都可以手动抢,这样就可以抓包分析出接口地址了。
5. 编码
根据找到的接口地址的规律,开始写代码
代码实现:
在这里插入图片描述
假设我们现在要抢购这一个
商品信息url:https://item.jd.com/100011412340.html
访问并抓包,分析数据包:

  1. url:https://item.jd.com/100011412340.html
    进入商品主页
    2.url:https://yushou.jd.com/youshouinfo.action?callback=fetchJSON&sku=100011412340&_=1584107644620
    在这里插入图片描述
    如图,这个接口返回了商品的信息,包含抢购预约时间等等,sku是商品id,_是时间戳
    3.点击立即抢购在这里插入图片描述
    进入提交页面 ,提交订单:
    提交订单url:https://marathon.jd.com/seckillnew/orderService/pc/submitOrder.action?skuId=100011412340,post参数过去
    参数包含有收货地址等,这些参数可以在https://marathon.jd.com/seckillnew/orderService/pc/init.action这个接口中获得,post
    data = {
    “sku”:goodsno,
    “num”:1,
    “isModifyAddress”:False
    }
    就可以获得所有的参数了。
    具体实现看代码吧。
    祝大家都能抢到口罩。
import requests,re,pickle
import time,datetime
import json
import sys,os
import logging
import logging.handlers
import random
from dateutil.parser import parse

LOG_FILENAME = '/kz.log'

logger = logging.getLogger()


def set_logger():
    logger.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')

    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)

    file_handler = logging.handlers.RotatingFileHandler(
        LOG_FILENAME, maxBytes=10485760, backupCount=5, encoding="utf-8")
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)


set_logger()

class JDSpider:
    '''
    登陆模块作者 zstu-lly
    参考 https://github.com/zstu-lly/JD_Robot
    '''
    def __init__(self):

        # init url related
        self.home = 'https://passport.jd.com/new/login.aspx'
        self.login = 'https://passport.jd.com/uc/loginService'
        self.imag = 'https://authcode.jd.com/verify/image'
        self.auth = 'https://passport.jd.com/uc/showAuthCode'
        self.goodsurl = ["https://item.jd.com/100011551632.html"]

        self.sess = requests.Session()

        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
            'ContentType': 'text/html; charset=utf-8',
            'Accept-Encoding': 'gzip, deflate, sdch',
            'Accept-Language': 'zh-CN,zh;q=0.8',
            'Connection': 'keep-alive',
        }

        self.cookies = {

        }
        self.address = {}

    def checkLogin(self):
        # 恢复之前保存的cookie
        checkUrl = 'https://passport.jd.com/uc/qrCodeTicketValidation'
        try:
            logger.info('+++++++++++++++++++++++++++++++++++++++++++++++++++++++')
            logger.info(f'{time.ctime()} > 检查登录状态中... ')
            with open('cookie', 'rb') as f:
                cookies = requests.utils.cookiejar_from_dict(pickle.load(f))
                response = self.sess.get(checkUrl, cookies=cookies)
                print(response.text)
                if response.status_code != requests.codes.OK:
                    logger.info('登录过期, 请重新登录!')
                    return False
                else:
                    logger.info('登陆状态正常')
                    self.cookies.update(dict(cookies))
                    return True

        except Exception as e:
            logger.info(e)
            return False

    def login_by_QR(self):
        # jd login by QR code
        try:
            logger.info('+++++++++++++++++++++++++++++++++++++++++++++++++++++++')
            logger.info(f'{time.ctime()} > 请打开京东手机客户端,准备扫码登录:')
            urls = (
                'https://passport.jd.com/new/login.aspx',
                'https://qr.m.jd.com/show',
                'https://qr.m.jd.com/check',
                'https://passport.jd.com/uc/qrCodeTicketValidation'
            )
            # step 1: open login page
            response = self.sess.get(
                urls[0],
                headers=self.headers
            )
            if response.status_code != requests.codes.OK:
                logger.info(f"获取登录页失败:{response.status_code}")
                return False
            # update cookies
            self.cookies.update(response.cookies)

            # step 2: get QR image
            response = self.sess.get(
                urls[1],
                headers=self.headers,
                cookies=self.cookies,
                params={
                    'appid': 133,
                    'size': 147,
                    't': int(time.time() * 1000),
                }
            )
            if response.status_code != requests.codes.OK:
                logger.info(f"获取二维码失败:{response.status_code}")
                return False

            # update cookies
            self.cookies.update(response.cookies)

            # save QR code
            image_file = 'qr.png'
            with open(image_file, 'wb') as f:
                for chunk in response.iter_content(chunk_size=1024):
                    f.write(chunk)

            # scan QR code with phone
            if os.name == "nt":
                # for windows
                os.system('start ' + image_file)
            else:
                if os.uname()[0] == "Linux":
                    # for linux platform
                    os.system("eog " + image_file)
                else:
                    # for Mac platform
                    os.system("open " + image_file)

            # step 3: check scan result    京东上也是不断去发送check请求来判断是否扫码的
            self.headers['Host'] = 'qr.m.jd.com'
            self.headers['Referer'] = 'https://passport.jd.com/new/login.aspx'

            # check if QR code scanned
            qr_ticket = None
            retry_times = 100  # 尝试100次
            while retry_times:
                retry_times -= 1
                response = self.sess.get(
                    urls[2],
                    headers=self.headers,
                    cookies=self.cookies,
                    params={
                        'callback': 'jQuery%d' % random.randint(1000000, 9999999),
                        'appid': 133,
                        'token': self.cookies['wlfstk_smdl'],
                        '_': int(time.time() * 1000)
                    }
                )
                if response.status_code != requests.codes.OK:
                    continue
                rs = json.loads(re.search(r'{.*?}', response.text, re.S).group())
                if rs['code'] == 200:
                    logger.info(f"{rs['code']} : {rs['ticket']}")
                    qr_ticket = rs['ticket']
                    break
                else:
                    logger.info(f"{rs['code']} : {rs['msg']}")
                    time.sleep(3)

            if not qr_ticket:
                logger.info("二维码登录失败")
                return False

            # step 4: validate scan result
            # must have
            self.headers['Host'] = 'passport.jd.com'
            self.headers['Referer'] = 'https://passport.jd.com/new/login.aspx'
            response = requests.get(
                urls[3],
                headers=self.headers,
                cookies=self.cookies,
                params={'t': qr_ticket},
            )
            if response.status_code != requests.codes.OK:
                print(f"二维码登录校验失败:{response.status_code}")
                return False

            # 京东有时候会认为当前登录有危险,需要手动验证
            # url: https://safe.jd.com/dangerousVerify/index.action?username=...
            res = json.loads(response.text)
            if not response.headers.get('p3p'):
                if 'url' in res:
                    logger.info(f"需要手动安全验证: {res['url']}")
                    return False
                else:
                    logger.info(res)
                    logger.info('登录失败!!')
                    return False

            # login succeed
            self.headers['P3P'] = response.headers.get('P3P')
            self.cookies.update(response.cookies)
            self.sess.cookies = response.cookies
             # 保存cookie
            with open('cookie', 'wb') as f:
                pickle.dump(self.cookies, f)
            return True
        except Exception as e:
            logger.info(e)
            raise

    def opengoods(self):
        """
        预约
        """
        
        for url in self.goodsurl:
            goodsno = url.split("/")[-1][:-5]
            yuyue = "https://yushou.jd.com/youshouinfo.action?callback=fetchJSON&sku={}&_={}".format(goodsno,int(time.time()*1000))
            self.headers["Referer"]=url
            res = self.sess.get(yuyue,headers=self.headers)
            resjson = json.loads(res.text[10:-2])
            logger.info(resjson)
            yuyueinfo = resjson["info"]  # 预约状态
            yuyuestarttime = parse(resjson["yueStime"]) # 预约开始时间
            yuyueendtime = resjson["yueEtime"] # 预约结束时间
            buytime = resjson["qiangStime"] # 开始抢购时间
            # url = ""
            infourl = "https:"+resjson["url"] # 预约的url
            # self.yuyue(infourl)  # 预约的地址
            # self.ko(goodsno) # 获取抢购链接
            # self.getaddress(goodsno)  # 获取收货地址
            # self.submit(goodsno)  # 提交订单

            # url = "https://marathon.jd.com/seckill/seckill.action?skuId=100011521400&num=1&rid=1583546499"
            # self.yuyue(url)
            self.buytime = parse(buytime)
            if yuyueinfo == "预约未开始":
                pass  # 等待开始预约了就开始预约
                while True:
                    timenow = parse(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
                    if timenow > yuyuestarttime:
                        infourl = "https:"+resjson["url"] # 预约的url
                        self.headers["Referer"]=yuyue # 换个头
                        if self.yuyue(infourl):
                            logger.info("预约成功,等待开抢,开始抢购时间{}".format(buytime))
                            self.kogoods(goodsno)
                            break
                    time.sleep(60)
                
            elif yuyueinfo == "预约进行中":
                while True:
                    timenow = parse(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
                    if timenow > yuyuestarttime:
                        infourl = "https:"+resjson["url"] # 预约的url
                        self.headers["Referer"]=yuyue # 换个头
                        if self.yuyue(infourl):
                            logger.info("预约成功,等待开抢,开始抢购时间{}".format(buytime))
                            self.kogoods(goodsno)
                            break
                    time.sleep(60)
            elif yuyueinfo == "预约结束抢购未开始":
                logger.info("预约成功,等待开抢,开始抢购时间{}".format(buytime))
                self.kogoods(goodsno)
        # self.kogoods(goodsno)
    def kogoods(self,goodsno):
        """
        等待抢购
        """
        # for i in range(50):
        #             logger.info("第{}次抢购".format(str(i)))
        #             self.getaddress(goodsno)  # 获取收货地址
        #             if self.submit(goodsno):  # 提交订单
        #                 return
        #             time.sleep(0.5)
        #             i+=1
        while True:
            timenow = parse(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
            logger.info((self.buytime - timenow).seconds)
            if (self.buytime - timenow).seconds >120:
                time.sleep(60)
                continue
            elif (self.buytime - timenow).seconds >60:
                time.sleep(30)
                continue
            elif (self.buytime - timenow).seconds >30:
                time.sleep(5)
                continue
            elif (self.buytime - timenow).seconds <=10:
            # if timenow == self.buytime:
                self.getaddress(goodsno)  # 获取收货地址
                for i in range(50):
                    logger.info("第{}次抢购".format(str(i)))
                    if self.submit(goodsno):  # 提交订单
                        return
                    time.sleep(0.5)
                    i+=1
                return

    def yuyue(self,url):
        """
        预约
        """
        res = self.sess.get(url,headers = self.headers,cookies = self.cookies)
        if res.status_code == 200:
            # with open ("a{}.txt".format(str(int(time.time()))),"w") as f:
            #     f.write(res.text)
            # print(url)
            return True # 预约成功

    def ko(self,goodsnoid):
        """
        抢购
        """
        url= "https://itemko.jd.com/itemShowBtn?callback=jQuery1288178&skuId={}&from=pc&_={}".format(goodsnoid,int(time.time()*1000))
        res = self.sess.get(url,headers = self.headers,cookies = self.cookies)
        logger.info(res.text)
        resjson = json.loads(res.text[14:-2])
        logger.info(resjson)
        kourl = "https:"+resjson["url"]  # 抢购地址
        res = self.sess.get(kourl,headers = self.headers,cookies = self.cookies)
        logger.info(res.text)

    def getaddress(self,goodsno):
        """
        获取订单地址
        """
        url = "https://marathon.jd.com/seckillnew/orderService/pc/init.action"
        data = {
            "sku":goodsno,
            "num":1,
            "isModifyAddress":False
        }
        logger.info("开始获取订单信息")
        res = self.sess.post(url,data=data,headers = self.headers,cookies = self.cookies)
        logger.info(res.text)
        self.address = json.loads(res.text)

    def submit(self,goodsno):
        """
        提交订单
        """
        url = "https://marathon.jd.com/seckillnew/orderService/pc/submitOrder.action?skuId={}".format(goodsno) # 提交订单id
        logger.info("尝试提交订单")
        plyaload={
            "skuId":goodsno,
            "num":1,
            "addressId": self.address["addressList"][0]["id"],
            "yuShou":True,
            "isModifyAddress":False,
            "name":self.address["addressList"][0]["name"],
            "provinceId":self.address["addressList"][0]["provinceId"],
            "cityId":self.address["addressList"][0]["cityId"],
            "countyId":self.address["addressList"][0]["countyId"],
            "townId":self.address["addressList"][0]["townId"],
            "addressDetail":self.address["addressList"][0]["addressDetail"],
            "mobile":self.address["addressList"][0]["mobile"],
            "mobileKey":self.address["addressList"][0]["mobileKey"],
            "email":self.address["addressList"][0]["email"],
            "postCode":self.address["addressList"][0]["postCode"],
            "invoiceTitle":self.address["invoiceInfo"]["invoiceTitle"],
            "invoiceCompanyName":self.address["invoiceInfo"]["invoiceCompany"],
            "invoiceContent":self.address["invoiceInfo"]["invoiceContentType"],
            "invoiceTaxpayerNO":self.address["invoiceInfo"]["invoiceCode"],
            "invoiceEmail":self.address["invoiceInfo"]["invoiceEmail"],
            "invoicePhone":self.address["invoiceInfo"]["invoicePhone"],
            "invoicePhoneKey":self.address["invoiceInfo"]["invoicePhoneKey"],
            "invoice":True,
            "password":"",
            "codTimeType":3,
            "paymentType":4,
            "areaCode":'',
            "overseas":0,
            "phone":"",
            "eid":"xxx",
            "fp":"xxx",
            "token":self.address["token"],
            "pru":""
        }
        res = self.sess.post(url,data=plyaload,headers = self.headers,cookies = self.cookies)
        logger.info("抢购结果:"+res.text)
        if "成功" in res.text:
            return True
        else:
            return False
    
            


jd = JDSpider()
cookiesstatus = jd.checkLogin()
if not cookiesstatus:
    cookiesstatus = jd.login_by_QR()
    logger.info("登陆成功")
else: 
    # loginstatus = jd.login_by_QR()
    jd.opengoods()
if cookiesstatus:
    jd.opengoods()
  • 11
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值