jd_spider_requests.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import random
  2. import sys
  3. import time
  4. from jd_logger import logger
  5. from timer import Timer
  6. import requests
  7. from util import parse_json, get_session, get_sku_title,send_wechat
  8. from config import global_config
  9. from concurrent.futures import ProcessPoolExecutor
  10. class JdSeckill(object):
  11. def __init__(self):
  12. # 初始化信息
  13. self.session = get_session()
  14. self.sku_id = global_config.getRaw('config', 'sku_id')
  15. self.seckill_num = 2
  16. self.seckill_init_info = dict()
  17. self.seckill_url = dict()
  18. self.seckill_order_data = dict()
  19. self.timers = Timer()
  20. self.default_user_agent = global_config.getRaw('config', 'DEFAULT_USER_AGENT')
  21. def reserve(self):
  22. """
  23. 预约
  24. """
  25. self.__reserve()
  26. def seckill(self):
  27. """
  28. 抢购
  29. """
  30. self.__seckill()
  31. def system_exit(self):
  32. sys.exit(1)
  33. def wait_some_time(self):
  34. time.sleep(random.randint(100, 300) / 1000)
  35. def seckill_by_proc_pool(self, work_count=5):
  36. """
  37. 多进程进行抢购
  38. work_count:进程数量
  39. """
  40. with ProcessPoolExecutor(work_count) as pool:
  41. for i in range(work_count):
  42. pool.submit(self.seckill)
  43. def __reserve(self):
  44. """
  45. 预约
  46. """
  47. self.login()
  48. while True:
  49. try:
  50. self.make_reserve()
  51. except Exception as e:
  52. logger.info('预约发生异常!', e)
  53. self.wait_some_time()
  54. def __seckill(self):
  55. """
  56. 抢购
  57. """
  58. self.login()
  59. while True:
  60. try:
  61. self.request_seckill_url()
  62. while True:
  63. self.request_seckill_checkout_page()
  64. if self.submit_seckill_order():
  65. self.system_exit()
  66. except Exception as e:
  67. logger.info('抢购发生异常,稍后继续执行!', e)
  68. self.wait_some_time()
  69. def login(self):
  70. for flag in range(1, 3):
  71. try:
  72. targetURL = 'https://order.jd.com/center/list.action'
  73. payload = {
  74. 'rid': str(int(time.time() * 1000)),
  75. }
  76. resp = self.session.get(
  77. url=targetURL, params=payload, allow_redirects=False)
  78. if resp.status_code == requests.codes.OK:
  79. logger.info('校验是否登录[成功]')
  80. logger.info('用户:{}'.format(self.get_username()))
  81. return True
  82. else:
  83. logger.info('校验是否登录[失败]')
  84. logger.info('请重新输入cookie')
  85. time.sleep(1)
  86. continue
  87. except Exception as e:
  88. logger.info('第【%s】次失败请重新获取cookie', flag)
  89. time.sleep(1)
  90. continue
  91. self.system_exit()
  92. def make_reserve(self):
  93. """商品预约"""
  94. logger.info('商品名称:{}'.format(get_sku_title()))
  95. url = 'https://yushou.jd.com/youshouinfo.action?'
  96. payload = {
  97. 'callback': 'fetchJSON',
  98. 'sku': self.sku_id,
  99. '_': str(int(time.time() * 1000)),
  100. }
  101. headers = {
  102. 'User-Agent': self.default_user_agent,
  103. 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id),
  104. }
  105. resp = self.session.get(url=url, params=payload, headers=headers)
  106. resp_json = parse_json(resp.text)
  107. reserve_url = resp_json.get('url')
  108. self.timers.start()
  109. while True:
  110. try:
  111. self.session.get(url='https:' + reserve_url)
  112. logger.info('预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约')
  113. if global_config.getRaw('messenger', 'enable') == 'true':
  114. success_message = "预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约"
  115. send_wechat(success_message)
  116. break
  117. except Exception as e:
  118. logger.error('预约失败正在重试...')
  119. def get_username(self):
  120. """获取用户信息"""
  121. url = 'https://passport.jd.com/user/petName/getUserInfoForMiniJd.action'
  122. payload = {
  123. 'callback': 'jQuery'.format(random.randint(1000000, 9999999)),
  124. '_': str(int(time.time() * 1000)),
  125. }
  126. headers = {
  127. 'User-Agent': self.default_user_agent,
  128. 'Referer': 'https://order.jd.com/center/list.action',
  129. }
  130. resp = self.session.get(url=url, params=payload, headers=headers)
  131. try_count = 5
  132. while not resp.text.startswith("jQuery"):
  133. try_count = try_count - 1
  134. if try_count > 0:
  135. resp = self.session.get(url=url, params=payload, headers=headers)
  136. else:
  137. break
  138. self.wait_some_time()
  139. # 响应中包含了许多用户信息,现在在其中返回昵称
  140. # jQuery2381773({"imgUrl":"//storage.360buyimg.com/i.imageUpload/xxx.jpg","lastLoginTime":"","nickName":"xxx","plusStatus":"0","realName":"xxx","userLevel":x,"userScoreVO":{"accountScore":xx,"activityScore":xx,"consumptionScore":xxxxx,"default":false,"financeScore":xxx,"pin":"xxx","riskScore":x,"totalScore":xxxxx}})
  141. return parse_json(resp.text).get('nickName')
  142. def get_seckill_url(self):
  143. """获取商品的抢购链接
  144. 点击"抢购"按钮后,会有两次302跳转,最后到达订单结算页面
  145. 这里返回第一次跳转后的页面url,作为商品的抢购链接
  146. :return: 商品的抢购链接
  147. """
  148. url = 'https://itemko.jd.com/itemShowBtn'
  149. payload = {
  150. 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)),
  151. 'skuId': self.sku_id,
  152. 'from': 'pc',
  153. '_': str(int(time.time() * 1000)),
  154. }
  155. headers = {
  156. 'User-Agent': self.default_user_agent,
  157. 'Host': 'itemko.jd.com',
  158. 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id),
  159. }
  160. while True:
  161. resp = self.session.get(url=url, headers=headers, params=payload)
  162. resp_json = parse_json(resp.text)
  163. if resp_json.get('url'):
  164. # https://divide.jd.com/user_routing?skuId=8654289&sn=c3f4ececd8461f0e4d7267e96a91e0e0&from=pc
  165. router_url = 'https:' + resp_json.get('url')
  166. # https://marathon.jd.com/captcha.html?skuId=8654289&sn=c3f4ececd8461f0e4d7267e96a91e0e0&from=pc
  167. seckill_url = router_url.replace(
  168. 'divide', 'marathon').replace(
  169. 'user_routing', 'captcha.html')
  170. logger.info("抢购链接获取成功: %s", seckill_url)
  171. return seckill_url
  172. else:
  173. logger.info("抢购链接获取失败,稍后自动重试")
  174. self.wait_some_time()
  175. def request_seckill_url(self):
  176. """访问商品的抢购链接(用于设置cookie等"""
  177. logger.info('用户:{}'.format(self.get_username()))
  178. logger.info('商品名称:{}'.format(get_sku_title()))
  179. self.timers.start()
  180. self.seckill_url[self.sku_id] = self.get_seckill_url()
  181. logger.info('访问商品的抢购连接...')
  182. headers = {
  183. 'User-Agent': self.default_user_agent,
  184. 'Host': 'marathon.jd.com',
  185. 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id),
  186. }
  187. self.session.get(
  188. url=self.seckill_url.get(
  189. self.sku_id),
  190. headers=headers,
  191. allow_redirects=False)
  192. def request_seckill_checkout_page(self):
  193. """访问抢购订单结算页面"""
  194. logger.info('访问抢购订单结算页面...')
  195. url = 'https://marathon.jd.com/seckill/seckill.action'
  196. payload = {
  197. 'skuId': self.sku_id,
  198. 'num': self.seckill_num,
  199. 'rid': int(time.time())
  200. }
  201. headers = {
  202. 'User-Agent': self.default_user_agent,
  203. 'Host': 'marathon.jd.com',
  204. 'Referer': 'https://item.jd.com/{}.html'.format(self.sku_id),
  205. }
  206. self.session.get(url=url, params=payload, headers=headers, allow_redirects=False)
  207. def _get_seckill_init_info(self):
  208. """获取秒杀初始化信息(包括:地址,发票,token)
  209. :return: 初始化信息组成的dict
  210. """
  211. logger.info('获取秒杀初始化信息...')
  212. url = 'https://marathon.jd.com/seckillnew/orderService/pc/init.action'
  213. data = {
  214. 'sku': self.sku_id,
  215. 'num': self.seckill_num,
  216. 'isModifyAddress': 'false',
  217. }
  218. headers = {
  219. 'User-Agent': self.default_user_agent,
  220. 'Host': 'marathon.jd.com',
  221. }
  222. resp = self.session.post(url=url, data=data, headers=headers)
  223. return parse_json(resp.text)
  224. def _get_seckill_order_data(self):
  225. """生成提交抢购订单所需的请求体参数
  226. :return: 请求体参数组成的dict
  227. """
  228. logger.info('生成提交抢购订单所需参数...')
  229. # 获取用户秒杀初始化信息
  230. self.seckill_init_info[self.sku_id] = self._get_seckill_init_info()
  231. init_info = self.seckill_init_info.get(self.sku_id)
  232. default_address = init_info['addressList'][0] # 默认地址dict
  233. invoice_info = init_info.get('invoiceInfo', {}) # 默认发票信息dict, 有可能不返回
  234. token = init_info['token']
  235. data = {
  236. 'skuId': self.sku_id,
  237. 'num': self.seckill_num,
  238. 'addressId': default_address['id'],
  239. 'yuShou': 'true',
  240. 'isModifyAddress': 'false',
  241. 'name': default_address['name'],
  242. 'provinceId': default_address['provinceId'],
  243. 'cityId': default_address['cityId'],
  244. 'countyId': default_address['countyId'],
  245. 'townId': default_address['townId'],
  246. 'addressDetail': default_address['addressDetail'],
  247. 'mobile': default_address['mobile'],
  248. 'mobileKey': default_address['mobileKey'],
  249. 'email': default_address.get('email', ''),
  250. 'postCode': '',
  251. 'invoiceTitle': invoice_info.get('invoiceTitle', -1),
  252. 'invoiceCompanyName': '',
  253. 'invoiceContent': invoice_info.get('invoiceContentType', 1),
  254. 'invoiceTaxpayerNO': '',
  255. 'invoiceEmail': '',
  256. 'invoicePhone': invoice_info.get('invoicePhone', ''),
  257. 'invoicePhoneKey': invoice_info.get('invoicePhoneKey', ''),
  258. 'invoice': 'true' if invoice_info else 'false',
  259. 'password': '',
  260. 'codTimeType': 3,
  261. 'paymentType': 4,
  262. 'areaCode': '',
  263. 'overseas': 0,
  264. 'phone': '',
  265. 'eid': global_config.getRaw('config', 'eid'),
  266. 'fp': global_config.getRaw('config', 'fp'),
  267. 'token': token,
  268. 'pru': ''
  269. }
  270. return data
  271. def submit_seckill_order(self):
  272. """提交抢购(秒杀)订单
  273. :return: 抢购结果 True/False
  274. """
  275. url = 'https://marathon.jd.com/seckillnew/orderService/pc/submitOrder.action'
  276. payload = {
  277. 'skuId': self.sku_id,
  278. }
  279. self.seckill_order_data[self.sku_id] = self._get_seckill_order_data()
  280. logger.info('提交抢购订单...')
  281. headers = {
  282. 'User-Agent': self.default_user_agent,
  283. 'Host': 'marathon.jd.com',
  284. 'Referer': 'https://marathon.jd.com/seckill/seckill.action?skuId={0}&num={1}&rid={2}'.format(
  285. self.sku_id, self.seckill_num, int(time.time())),
  286. }
  287. resp = self.session.post(
  288. url=url,
  289. params=payload,
  290. data=self.seckill_order_data.get(
  291. self.sku_id),
  292. headers=headers)
  293. resp_json = parse_json(resp.text)
  294. # 返回信息
  295. # 抢购失败:
  296. # {'errorMessage': '很遗憾没有抢到,再接再厉哦。', 'orderId': 0, 'resultCode': 60074, 'skuId': 0, 'success': False}
  297. # {'errorMessage': '抱歉,您提交过快,请稍后再提交订单!', 'orderId': 0, 'resultCode': 60017, 'skuId': 0, 'success': False}
  298. # {'errorMessage': '系统正在开小差,请重试~~', 'orderId': 0, 'resultCode': 90013, 'skuId': 0, 'success': False}
  299. # 抢购成功:
  300. # {"appUrl":"xxxxx","orderId":820227xxxxx,"pcUrl":"xxxxx","resultCode":0,"skuId":0,"success":true,"totalMoney":"xxxxx"}
  301. if resp_json.get('success'):
  302. order_id = resp_json.get('orderId')
  303. total_money = resp_json.get('totalMoney')
  304. pay_url = 'https:' + resp_json.get('pcUrl')
  305. logger.info(
  306. '抢购成功,订单号:{}, 总价:{}, 电脑端付款链接:{}'.format(order_id,total_money,pay_url)
  307. )
  308. if global_config.getRaw('messenger', 'enable') == 'true':
  309. success_message = "抢购成功,订单号:{}, 总价:{}, 电脑端付款链接:{}".format(order_id, total_money, pay_url)
  310. send_wechat(success_message)
  311. return True
  312. else:
  313. logger.info('抢购失败,返回信息:{}'.format(resp_json))
  314. if global_config.getRaw('messenger', 'enable') == 'true':
  315. error_message = '抢购失败,返回信息:{}'.format(resp_json)
  316. send_wechat(error_message)
  317. return False