def _get_qrcode(self): """ 缓存并展示登录二维码 :return: """ url = 'https://qr.m.jd.com/show' payload = { 'appid': 133, 'size': 147, 't': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.spider_session.get_user_agent(), 'Referer': 'https://passport.jd.com/new/login.aspx', } resp = self.session.get(url=url, headers=headers, params=payload) if not response_status(resp): logger.info('获取二维码失败') return False save_image(resp, self.qrcode_img_file) logger.info('二维码获取成功,请打开京东APP扫描') open_image(self.qrcode_img_file) return True
def _get_qrcode_ticket(self): """ 通过 token 获取票据 :return: """ url = 'https://qr.m.jd.com/check' payload = { 'appid': '133', 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)), 'token': self.session.cookies.get('wlfstk_smdl'), '_': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.spider_session.get_user_agent(), 'Referer': 'https://passport.jd.com/new/login.aspx', } resp = self.session.get(url=url, headers=headers, params=payload) if not response_status(resp): logger.error('获取二维码扫描结果异常') return False resp_json = parse_json(resp.text) if resp_json['code'] != 200: logger.info('Code: %s, Message: %s', resp_json['code'], resp_json['msg']) return None else: logger.info('已完成手机客户端确认') return resp_json['ticket']
def get_seckill_url(self): """获取商品的抢购链接 点击"抢购"按钮后,会有两次302跳转,最后到达订单结算页面 这里返回第一次跳转后的页面url,作为商品的抢购链接 :return: 商品的抢购链接 """ url = 'https://itemko.jd.com/itemShowBtn' payload = { 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)), 'skuId': self.sku_id, 'from': 'pc', '_': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.user_agent, 'Host': 'itemko.jd.com', 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id), } while True: resp = self.session.get(url=url, headers=headers, params=payload) resp_json = parse_json(resp.text) if resp_json.get('url'): # https://divide.jd.com/user_routing?skuId=8654289&sn=c3f4ececd8461f0e4d7267e96a91e0e0&from=pc router_url = 'https:' + resp_json.get('url') # https://marathon.jd.com/captcha.html?skuId=8654289&sn=c3f4ececd8461f0e4d7267e96a91e0e0&from=pc seckill_url = router_url.replace('divide', 'marathon').replace( 'user_routing', 'captcha.html') logger.info("抢购链接获取成功: %s", seckill_url) return seckill_url else: logger.info("抢购链接获取失败,稍后自动重试") wait_some_time()
def make_reserve(self): """商品预约""" logger.info('商品名称:{}'.format(self.get_sku_title())) url = 'https://yushou.jd.com/youshouinfo.action?' payload = { 'callback': 'fetchJSON', 'sku': self.sku_id, '_': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.user_agent, 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id), } resp = self.session.get(url=url, params=payload, headers=headers) resp_json = parse_json(resp.text) reserve_url = resp_json.get('url') self.timers.start() while True: try: self.session.get(url='https:' + reserve_url) logger.info('预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约') if global_config.getRaw('messenger', 'enable') == 'true': success_message = "预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约" send_wechat(success_message) break except Exception as e: logger.error('预约失败正在重试...')
def login_by_qrcode(self): """ 二维码登陆 :return: """ self._get_login_page() # download QR code if not self._get_qrcode(): raise SKException('二维码下载失败') # get QR code ticket ticket = None retry_times = 85 for _ in range(retry_times): ticket = self._get_qrcode_ticket() if ticket: break time.sleep(2) else: raise SKException('二维码过期,请重新获取扫描') # validate QR code ticket if not self._validate_qrcode_ticket(ticket): raise SKException('二维码信息校验失败') self.refresh_login_status() logger.info('二维码登录成功')
def submit_seckill_order(self): """提交抢购(秒杀)订单 :return: 抢购结果 True/False """ url = 'https://marathon.jd.com/seckillnew/orderService/pc/submitOrder.action' payload = { 'skuId': self.sku_id, } try: self.seckill_order_data[ self.sku_id] = self._get_seckill_order_data() except Exception as e: logger.info('抢购失败,无法获取生成订单的基本信息,接口返回:【{}】'.format(str(e))) return False logger.info('提交抢购订单...') headers = { 'User-Agent': self.user_agent, 'Host': 'marathon.jd.com', 'Referer': 'https://marathon.jd.com/seckill/seckill.action?skuId={0}&num={1}&rid={2}' .format(self.sku_id, self.seckill_num, int(time.time())), } resp = self.session.post(url=url, params=payload, data=self.seckill_order_data.get(self.sku_id), headers=headers) resp_json = None try: resp_json = parse_json(resp.text) except Exception as e: logger.info('抢购失败,返回信息:{}'.format(resp.text[0:128])) return False # 返回信息 # 抢购失败: # {'errorMessage': '很遗憾没有抢到,再接再厉哦。', 'orderId': 0, 'resultCode': 60074, 'skuId': 0, 'success': False} # {'errorMessage': '抱歉,您提交过快,请稍后再提交订单!', 'orderId': 0, 'resultCode': 60017, 'skuId': 0, 'success': False} # {'errorMessage': '系统正在开小差,请重试~~', 'orderId': 0, 'resultCode': 90013, 'skuId': 0, 'success': False} # 抢购成功: # {"appUrl":"xxxxx","orderId":820227xxxxx,"pcUrl":"xxxxx","resultCode":0,"skuId":0,"success":true,"totalMoney":"xxxxx"} if resp_json.get('success'): order_id = resp_json.get('orderId') total_money = resp_json.get('totalMoney') pay_url = 'https:' + resp_json.get('pcUrl') logger.info('抢购成功,订单号:{}, 总价:{}, 电脑端付款链接:{}'.format( order_id, total_money, pay_url)) if global_config.getRaw('messenger', 'enable') == 'true': success_message = "抢购成功,订单号:{}, 总价:{}, 电脑端付款链接:{}".format( order_id, total_money, pay_url) send_wechat(success_message) return True else: logger.info('抢购失败,返回信息:{}'.format(resp_json)) if global_config.getRaw('messenger', 'enable') == 'true': error_message = '抢购失败,返回信息:{}'.format(resp_json) send_wechat(error_message) return False
def need_relogin(self): """计算是否需要重新登陆""" while True: if self.local_time() >= self.relogin_ms: logger.info("时间到达,验证是否需要重新登陆......") break else: time.sleep(self.sleep_interval)
def _reserve(self): """ 预约 """ try: logger.info('预约结果:' + self.make_reserve()) except Exception as e: logger.error('预约发生异常!', e)
def start(self): logger.info('正在等待到达设定时间:{},检测本地时间与京东服务器时间误差为【{}】毫秒'.format( self.buy_time, self.diff_time)) # 本地时间减去与京东的时间差,能够将时间误差提升到0.1秒附近 # 具体精度依赖获取京东服务器时间的网络时间损耗 while self.local_time() - self.diff_time < self.buy_time_ms: time.sleep(self.sleep_interval) logger.info('时间到达,开始执行……')
def start(self): # print(datetime.now()) logger.info('正在等待到达设定时间:{},检测本地时间与京东服务器时间误差为【{}】毫秒'.format(self.buy_time, self.diff_time)) while True: # 本地时间减去与京东的时间差,能够将时间误差提升到0.1秒附近 # 具体精度依赖获取京东服务器时间的网络时间损耗 if self.local_time() - self.diff_time >= self.buy_time_ms: logger.info('时间到达,开始执行……') break else: time.sleep(self.sleep_interval)
def _reserve(self): """ 预约 """ while True: try: self.make_reserve() break except Exception as e: logger.info('预约发生异常!', e) wait_some_time()
def _get_seckill_order_data(self): """生成提交抢购订单所需的请求体参数 :return: 请求体参数组成的dict """ logger.info('生成提交抢购订单所需参数...') # 获取用户秒杀初始化信息 self.seckill_init_info[self.sku_id] = self._get_seckill_init_info() init_info = self.seckill_init_info.get(self.sku_id) if init_info is None: return False logger.info(f"输出init_info:{init_info}") # addr = {'addressDetail': '谭家岭东路2号 (审批服务中心)', 'addressName': '单位', 'areaCode': '86', 'cityId': 1158, 'cityName': '宁波市', 'countyId': 46345, 'countyName': '余姚市', 'defaultAddress': True, 'email': '', 'id': 843945282, 'mobile': '134****9880', 'mobileKey': '93b9f23b73966c2ac40fc3f9035b95b6', 'name': '陈云', 'overseas': 0, 'phone': '', 'postCode': '', 'provinceId': 15, 'provinceName': '浙江', 'townId': 52176, 'townName': '城区', 'yuyueAddress': False} default_address = init_info['addressList'][0] # 默认地址dict # default_address = addr # 默认地址dict invoice_info = init_info.get('invoiceInfo', {}) # 默认发票信息dict, 有可能不返回 token = init_info['token'] data = { 'skuId': self.sku_id, 'num': self.seckill_num, 'addressId': default_address['id'], # 'addressId': 843945282, 'yuShou': 'true', 'isModifyAddress': 'false', 'name': default_address['name'], 'provinceId': default_address['provinceId'], 'cityId': default_address['cityId'], 'countyId': default_address['countyId'], 'townId': default_address['townId'], 'addressDetail': default_address['addressDetail'], 'mobile': default_address['mobile'], 'mobileKey': default_address['mobileKey'], 'email': default_address.get('email', ''), 'postCode': '', 'invoiceTitle': invoice_info.get('invoiceTitle', -1), 'invoiceCompanyName': '', 'invoiceContent': invoice_info.get('invoiceContentType', 1), 'invoiceTaxpayerNO': '', 'invoiceEmail': '', 'invoicePhone': invoice_info.get('invoicePhone', ''), 'invoicePhoneKey': invoice_info.get('invoicePhoneKey', ''), 'invoice': 'true' if invoice_info else 'false', 'password': global_config.get('account', 'payment_pwd'), 'codTimeType': 3, 'paymentType': 4, 'areaCode': '', 'overseas': 0, 'phone': '', 'eid': global_config.getRaw('config', 'eid'), 'fp': global_config.getRaw('config', 'fp'), 'token': token, 'pru': '' } return data
def request_seckill_url(self): """访问商品的抢购链接(用于设置cookie等""" self.timers.start() self.seckill_url[self.sku_id] = self.get_seckill_url() logger.info('访问商品的抢购连接...') headers = { 'User-Agent': self.user_agent, 'Host': 'marathon.jd.com', 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id), } self.session.get(url=self.seckill_url.get(self.sku_id), headers=headers, allow_redirects=False)
def _seckill(self): """ 抢购 """ while True: try: self.request_seckill_url() while True: self.request_seckill_checkout_page() self.submit_seckill_order() except Exception as e: logger.info('抢购发生异常,稍后继续执行!', e) wait_some_time()
def _seckill(self): """ 抢购 """ try: self.request_seckill_url() while True: self.request_seckill_checkout_page() self.submit_seckill_order() if self.timers.is_end(): logger.info("秒杀已经结束,等待下一次开始.") break except Exception as e: logger.info('抢购发生异常,稍后继续执行!', e)
def request_seckill_checkout_page(self): """访问抢购订单结算页面""" logger.info('访问抢购订单结算页面...') url = 'https://marathon.jd.com/seckill/seckill.action' payload = { 'skuId': self.sku_id, 'num': self.seckill_num, 'rid': int(time.time()) } headers = { 'User-Agent': self.user_agent, 'Host': 'marathon.jd.com', 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id), } self.session.get(url=url, params=payload, headers=headers, allow_redirects=False)
def _get_seckill_order_data(self): """生成提交抢购订单所需的请求体参数 :return: 请求体参数组成的dict """ logger.info('生成提交抢购订单所需参数...') # 获取用户秒杀初始化信息 self.seckill_init_info[self.sku_id] = self._get_seckill_init_info() init_info = self.seckill_init_info.get(self.sku_id) default_address = init_info['addressList'][0] # 默认地址dict invoice_info = init_info.get('invoiceInfo', {}) # 默认发票信息dict, 有可能不返回 token = init_info['token'] data = { 'skuId': self.sku_id, 'num': self.seckill_num, 'addressId': default_address['id'], 'yuShou': 'true', 'isModifyAddress': 'false', 'name': default_address['name'], 'provinceId': default_address['provinceId'], 'cityId': default_address['cityId'], 'countyId': default_address['countyId'], 'townId': default_address['townId'], 'addressDetail': default_address['addressDetail'], 'mobile': default_address['mobile'], 'mobileKey': default_address['mobileKey'], 'email': default_address.get('email', ''), 'postCode': '', 'invoiceTitle': invoice_info.get('invoiceTitle', -1), 'invoiceCompanyName': '', 'invoiceContent': invoice_info.get('invoiceContentType', 1), 'invoiceTaxpayerNO': '', 'invoiceEmail': '', 'invoicePhone': invoice_info.get('invoicePhone', ''), 'invoicePhoneKey': invoice_info.get('invoicePhoneKey', ''), 'invoice': 'true' if invoice_info else 'false', 'password': global_config.get('account', 'payment_pwd'), 'codTimeType': 3, 'paymentType': 4, 'areaCode': '', 'overseas': 0, 'phone': '', 'eid': global_config.getRaw('config', 'eid'), 'fp': global_config.getRaw('config', 'fp'), 'token': token, 'pru': '' } return data
def login_by_qrcode(self): """ 二维码登陆 :return: """ if self.qrlogin.is_login: logger.info('登录成功') return self.qrlogin.login_by_qrcode() if self.qrlogin.is_login: self.nick_name = self.get_username() self.spider_session.save_cookies_to_local(self.nick_name) else: raise SKException("二维码登录失败!")
def __init__(self): self.spider_session = SpiderSession() self.spider_session.load_cookies_from_local() self.qrlogin = QrLogin(self.spider_session) # 初始化信息 self.sku_id = global_config.getRaw('config', 'sku_id') self.seckill_num = global_config.getRaw('config', 'seckill_num') self.seckill_init_info = dict() self.seckill_url = dict() self.seckill_order_data = dict() self.timers = Timer() self.session = self.spider_session.get_session() self.user_agent = self.spider_session.user_agent self.nick_name = None logger.info('商品名称:{}'.format(self.get_sku_title())) logger.info('商品url:{}'.format('https://item.jd.com/{}.html'.format( self.sku_id)))
def request_seckill_url(self): is_request_url_ok = False """访问商品的抢购链接(用于设置cookie等""" logger.info('用户:{}'.format(self.get_username())) logger.info('商品名称:{}'.format(self.get_sku_title())) self.seckill_url[self.sku_id] = self.get_seckill_url() logger.info('访问商品的抢购连接...') headers = { 'User-Agent': self.user_agent, 'Host': 'marathon.jd.com', 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id), } try: retry_time = 3 while(retry_time > 0): retry_time -= 1 resp = self.session.get( url=self.seckill_url.get( self.sku_id), headers=headers, allow_redirects=False) resp.raise_for_status() is_request_url_ok = True break catch Exception as e: logger.error("访问商品的抢购连接重试....") wait_some_time()
def make_reserve(self): """商品预约""" logger.info('商品名称:{}'.format(self.get_sku_title())) url = 'https://yushou.jd.com/youshouinfo.action?' payload = { 'callback': 'fetchJSON', 'sku': self.sku_id, '_': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.user_agent, 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id), } resp = self.session.get(url=url, params=payload, headers=headers) resp_json = parse_json(resp.text) reserve_url = resp_json.get('url') url = 'https:' + reserve_url logger.info('茅台预约url:{}'.format(url)) # self.timers.start() while True: try: yuyue_resp = self.session.get(url) if "您已成功预约过了,无需重复预约" in yuyue_resp.text: logger.info('用户:{},预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约'.format( self.get_username())) break except Exception as e: logger.error('预约失败正在重试...') wait_some_time()
def _validate_qrcode_ticket(self, ticket): """ 通过已获取的票据进行校验 :param ticket: 已获取的票据 :return: """ url = 'https://passport.jd.com/uc/qrCodeTicketValidation' headers = { 'User-Agent': self.spider_session.get_user_agent(), 'Referer': 'https://passport.jd.com/uc/login?ltype=logout', } resp = self.session.get(url=url, headers=headers, params={'t': ticket}) if not response_status(resp): return False resp_json = json.loads(resp.text) if resp_json['returnCode'] == 0: return True else: logger.info(resp_json) return False
def make_reserve(self): """商品预约""" logger.info('商品名称:{}'.format(self.get_sku_title())) url = 'https://yushou.jd.com/youshouinfo.action?' payload = { 'callback': 'fetchJSON', 'sku': self.sku_id, '_': str(int(time.time() * 1000)), } headers = { 'User-Agent': self.user_agent, 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id), } resp = self.session.get(url=url, params=payload, headers=headers) resp_json = parse_json(resp.text) reserve_url = resp_json.get('url') resp.raise_for_status() self.timers.start() retry_time = 3 is_reserve_ok = false while retry_time > 0: retry_time -= 1 try: resp = self.session.get(url='https:' + reserve_url) if resp.status_code != 200: logger.error("预约请求失败:{}, 重试中....".format( resp.status_code)) continue logger.info('预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约') is_reserve_ok = True if global_config.getRaw('messenger', 'enable') == 'true': success_message = "预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约" send_wechat(success_message) break except Exception as e: logger.error('预约失败正在重试...') if not is_reserve_ok: logger.error('预约失败...') return is_reserve_ok
def _get_seckill_init_info(self): """获取秒杀初始化信息(包括:地址,发票,token) :return: 初始化信息组成的dict """ logger.info('获取秒杀初始化信息...') url = 'https://marathon.jd.com/seckillnew/orderService/pc/init.action' data = { 'sku': self.sku_id, 'num': self.seckill_num, 'isModifyAddress': 'false', } headers = { 'User-Agent': self.user_agent, 'Host': 'marathon.jd.com', } resp = self.session.post(url=url, data=data, headers=headers) resp_json = None try: resp_json = parse_json(resp.text) except Exception: raise SKException('抢购失败,返回信息:{}'.format(resp.text[0:128])) return resp_json
def __init__(self, sleep_interval=0.5): # '2018-09-28 22:45:50.000' # buy_time = 2020-12-22 09:59:59.500 localtime = time.localtime(time.time()) buy_time_everyday = global_config.getRaw('config', 'buy_time').__str__() last_purchase_time_everyday = global_config.getRaw( 'config', 'last_purchase_time').__str__() relogin_time_everyday = global_config.getRaw('config', 'relogin_time').__str__() # 最后购买时间 self.last_purchase_time = datetime.strptime( localtime.tm_year.__str__() + '-' + localtime.tm_mon.__str__() + '-' + localtime.tm_mday.__str__() + ' ' + last_purchase_time_everyday, "%Y-%m-%d %H:%M:%S.%f") logger.info("最后购买时间:%s" % self.last_purchase_time) buy_time_config = datetime.strptime( localtime.tm_year.__str__() + '-' + localtime.tm_mon.__str__() + '-' + localtime.tm_mday.__str__() + ' ' + buy_time_everyday, "%Y-%m-%d %H:%M:%S.%f") relogin_time_config = datetime.strptime( localtime.tm_year.__str__() + '-' + localtime.tm_mon.__str__() + '-' + localtime.tm_mday.__str__() + ' ' + relogin_time_everyday, "%Y-%m-%d %H:%M:%S.%f") if time.mktime(localtime) < time.mktime(buy_time_config.timetuple()): # 取正确的购买时间 self.buy_time = buy_time_config self.relogin_time = relogin_time_config # elif time.mktime(localtime) > time.mktime(self.last_purchase_time.timetuple()): # # 取明天的时间 购买时间 # self.buy_time = datetime.strptime( # localtime.tm_year.__str__() + '-' + localtime.tm_mon.__str__() + '-' + ( # localtime.tm_mday + 1).__str__() + ' ' + buy_time_everyday, # "%Y-%m-%d %H:%M:%S.%f") else: self.buy_time = datetime.strptime( localtime.tm_year.__str__() + '-' + localtime.tm_mon.__str__() + '-' + (localtime.tm_mday + 1).__str__() + ' ' + buy_time_everyday, "%Y-%m-%d %H:%M:%S.%f") self.relogin_time = datetime.strptime( localtime.tm_year.__str__() + '-' + localtime.tm_mon.__str__() + '-' + (localtime.tm_mday + 1).__str__() + ' ' + relogin_time_everyday, "%Y-%m-%d %H:%M:%S.%f") # self.buy_time = buy_time_config logger.info("开始购买时间:{}".format(self.buy_time)) logger.info("重新登陆时间:{}".format(self.relogin_time)) self.buy_time_ms = int( time.mktime(self.buy_time.timetuple()) * 1000.0 + self.buy_time.microsecond / 1000) self.relogin_ms = int( time.mktime(self.relogin_time.timetuple()) * 1000.0 + self.relogin_time.microsecond / 1000) self.sleep_interval = sleep_interval self.diff_time = self.local_jd_time_diff() seckill_duration_ms = int( global_config.getRaw('config', 'seckill_duration_ms')) self.buy_endtime_ms = self.buy_time_ms + seckill_duration_ms logger.info("抢购结束时间ms: {}".format(self.buy_endtime_ms))
def reserve_and_seckill(self, work_count=5): """ 预约并抢购 """ user_run_once = self.run_once while True: self.run_once = True # 每天抢购结束后,需要重新获取新的时间点 logger.info("重新获取秒杀开始时间...") self.timers = Timer() logger.info("开始预约") try: self.make_reserve() except Exception as e: logger.info('预约发生异常!', e) logger.info("等待验证Cookie是否已经过期") if self.timers.need_relogin(): self.validate_cookie() logger.info("等待进入秒杀") with ProcessPoolExecutor(work_count) as pool: for i in range(work_count): pool.submit(self._seckill) logger.info("本次抢购进程全部结束并退出") if user_run_once: logger.info("用户设定只运行一次,退出循环...") break else: wait_some_time()
def validate_cookie(self): logger.info("重新加载最新Cookie") self.spider_session.load_cookies_from_local() self.session = self.spider_session.get_session()
def new_func(self, *args, **kwargs): if not self.qrlogin.is_login: logger.info("{0} 需登陆后调用,开始扫码登陆".format(func.__name__)) self.login_by_qrcode() return func(self, *args, **kwargs)
if __name__ == '__main__': a = """ oooo oooooooooo. .oooooo..o oooo o8o oooo oooo `888 `888' `Y8b d8P' `Y8 `888 `"' `888 `888 888 888 888 Y88bo. .ooooo. .ooooo. 888 oooo oooo 888 888 888 888 888 `"Y8888o. d88' `88b d88' `"Y8 888 .8P' `888 888 888 888 888 888 8888888 `"Y88b 888ooo888 888 888888. 888 888 888 888 888 d88' oo .d8P 888 .o 888 .o8 888 `88b. 888 888 888 .o. 88P o888bood8P' 8""88888P' `Y8bod8P' `Y8bod8P' o888o o888o o888o o888o o888o `Y888P 功能列表: 1.预约商品 2.秒杀抢购商品 """ print(a) jd_seckill = JdSeckill() choice_function = input('请选择:') try: if choice_function == '1': jd_seckill.reserve() elif choice_function == '2': jd_seckill.seckill_by_proc_pool() else: print('没有此功能') sys.exit(1) except Exception as e: logger.info('执行异常', e)