├── .gitattributes
├── Console Version
├── JDAutoBuyTool.py
├── Please fill out this document.txt
├── jdAutoBuyGood.log
├── requirements.txt
└── test.py
├── GUI Version
├── autobuy.py
├── image
│ ├── down.png
│ ├── left.png
│ └── right.png
└── window.qss
├── LICENSE
├── README.md
└── assets
├── 1580963259089.png
├── 1580963623908.png
├── 1581038258274.png
├── 1581218076866.png
├── 1581218537205.png
├── 1581249279630.png
├── 1581508416184.png
├── 1581508444771.png
├── 1581768204211.png
├── 1581768213048.png
└── 1583151909197.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/Console Version/JDAutoBuyTool.py:
--------------------------------------------------------------------------------
1 | # -*- coding=utf-8 -*-
2 | import requests
3 | import logging
4 | import logging.handlers
5 | import time
6 | import json
7 | import sys
8 | import random
9 | from bs4 import BeautifulSoup
10 | import smtplib
11 | import re
12 | from email.mime.text import MIMEText
13 | from email.header import Header
14 |
15 | import traceback
16 |
17 | def setLogger(logFileName, logger):
18 | logger.setLevel(logging.INFO)
19 | formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
20 |
21 | console_handler = logging.StreamHandler()
22 | console_handler.setFormatter(formatter)
23 | logger.addHandler(console_handler)
24 |
25 | file_handler = logging.handlers.RotatingFileHandler(
26 | logFileName, maxBytes=10485760, backupCount=5, encoding="utf-8")
27 | file_handler.setFormatter(formatter)
28 | logger.addHandler(file_handler)
29 |
30 |
31 | def sendMail(url, isOrder, sendTo):
32 | if len(sendTo) == 0 or sendTo == '$$$$$$$@qq.com':
33 | return
34 |
35 | mailRe = re.compile('^\w{1,15}@\w{1,10}\.(com|cn|net)$')
36 | if not re.search(mailRe, sendTo):
37 | return
38 |
39 | sendFrom = '645064582@qq.com'
40 |
41 | smtp_server = 'smtp.qq.com'
42 | if isOrder:
43 | msg = MIMEText(url + ' 商品已下单, 请在尽快付款', 'plain', 'utf-8')
44 | else:
45 | msg = MIMEText(url + ' 商品下单失败.', 'plain', 'utf-8')
46 |
47 | msg['From'] = Header(sendFrom)
48 | msg['To'] = Header(sendTo)
49 | msg['Subject'] = Header('买到啦')
50 |
51 | server = smtplib.SMTP_SSL(host=smtp_server)
52 | server.connect(smtp_server, 465)
53 | server.login(sendFrom, 'nkrzicfjkzznbehi')
54 | server.sendmail(sendFrom, sendTo, msg.as_string())
55 | server.quit()
56 |
57 |
58 | def sendError(sendTo):
59 | if len(sendTo) == 0 or sendTo == '$$$$$$$@qq.com':
60 | return
61 |
62 | mailRe = re.compile('^\w{1,15}@\w{1,10}\.(com|cn|net)$')
63 | if not re.search(mailRe, sendTo):
64 | return
65 |
66 | sendFrom = '645064582@qq.com'
67 |
68 | # 发信服务器
69 | smtp_server = 'smtp.qq.com'
70 | # 邮箱正文内容, 第一个参数为内容, 第二个参数为格式(plain 为纯文本), 第三个参数为编码
71 | msg = MIMEText('main()函数出错嘞!', 'plain', 'utf-8')
72 |
73 | # 邮件头信息
74 | msg['From'] = Header(sendFrom)
75 | msg['To'] = Header(sendTo)
76 | msg['Subject'] = Header('ERROR!')
77 | # 开启发信服务, 加密传输
78 | server = smtplib.SMTP_SSL(host=smtp_server)
79 | server.connect(smtp_server, 465)
80 | server.login(sendFrom, 'nkrzicfjkzznbehi')
81 | server.sendmail(sendFrom, sendTo, msg.as_string())
82 | server.quit()
83 |
84 |
85 | def get_tag_value(tag, key='', index=0):
86 | if key:
87 | value = tag[index].get(key)
88 | else:
89 | value = tag[index].text
90 | return value.strip(' \t\r\n')
91 |
92 |
93 | def responseStatus(resp):
94 | if resp.status_code != requests.codes.OK:
95 | print('Status: %u, Url: %s' % (resp.status_code, resp.url))
96 | return False
97 | return True
98 | def validateCookies(logger, session):
99 | for flag in range(1, 3):
100 | try:
101 | targetURL = 'https://order.jd.com/center/list.action'
102 | payload = {
103 | 'rid': str(int(time.time() * 1000)),
104 | }
105 | resp = session.get(url=targetURL, params=payload, allow_redirects=False)
106 | if resp.status_code == requests.codes.OK:
107 | logger.info('登录成功!')
108 | return True
109 | else:
110 | logger.info('第[%s]次请重新获取cookie...', flag)
111 | time.sleep(5)
112 | continue
113 | except Exception as e:
114 | logger.info('第[%s]次请重新获取cookie...', flag)
115 | time.sleep(5)
116 | continue
117 |
118 |
119 | def getUsername(logger, session):
120 | userName_Url = 'https://passport.jd.com/new/helloService.ashx?callback=jQuery339448&_=' + str(
121 | int(time.time() * 1000))
122 | session.headers = {
123 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
124 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
125 | "Referer": "https://order.jd.com/center/list.action",
126 | "Connection": "keep-alive"
127 | }
128 | resp = session.get(url=userName_Url, allow_redirects=True)
129 | resultText = resp.text
130 | resultText = resultText.replace('jQuery339448(', '')
131 | resultText = resultText.replace(')', '')
132 | usernameJson = json.loads(resultText)
133 | logger.info('登录账号名称: ' + usernameJson['nick'])
134 |
135 |
136 | def cancelSelectCartItem(session):
137 | url = "https://cart.jd.com/cancelAllItem.action"
138 | data = {
139 | 't': 0,
140 | 'outSkus': '',
141 | 'random': random.random()
142 | }
143 | resp = session.post(url, data=data)
144 | if resp.status_code != requests.codes.OK:
145 | print('Status: %u, Url: %s' % (resp.status_code, resp.url))
146 | return False
147 | return True
148 |
149 | def cart_detail(session, logger, isOutput=False):
150 | url = 'https://cart.jd.com/cart.action'
151 | headers = {
152 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
153 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
154 | "Referer": "https://order.jd.com/center/list.action",
155 | "Host": "cart.jd.com",
156 | "Connection": "keep-alive"
157 | }
158 | resp = session.get(url, headers=headers)
159 | soup = BeautifulSoup(resp.text, "html.parser")
160 |
161 | cartDetail = dict()
162 | for item in soup.find_all(class_='item-item'):
163 | sku_id = item['skuid'] # 商品id
164 | try:
165 | # 例如: ['increment', '8888', '100001071956', '1', '13', '0', '50067652554']
166 | # ['increment', '8888', '100002404322', '2', '1', '0']
167 | item_attr_list = item.find(class_='increment')['id'].split('_')
168 | p_type = item_attr_list[4]
169 | promo_id = target_id = item_attr_list[-1] if len(item_attr_list) == 7 else 0
170 |
171 | cartDetail[sku_id] = {
172 | 'name': get_tag_value(item.select('div.p-name a')), # 商品名称
173 | 'verder_id': item['venderid'], # 商家id
174 | 'count': int(item['num']), # 数量
175 | 'unit_price': get_tag_value(item.select('div.p-price strong'))[1:], # 单价
176 | 'total_price': get_tag_value(item.select('div.p-sum strong'))[1:], # 总价
177 | 'is_selected': 'item-selected' in item['class'], # 商品是否被勾选
178 | 'p_type': p_type,
179 | 'target_id': target_id,
180 | 'promo_id': promo_id
181 | }
182 | except Exception as e:
183 | logger.error("商品%s在购物车中的信息无法解析, 报错信息: %s, 该商品自动忽略", sku_id, e)
184 |
185 | if isOutput == True:
186 | logger.info('当前购物车信息: %s', cartDetail)
187 | return cartDetail
188 |
189 | def change_item_num_in_cart(sku_id, vender_id, num, p_type, target_id, promo_id, session):
190 | url = "https://cart.jd.com/changeNum.action"
191 | data = {
192 | 't': 0,
193 | 'venderId': vender_id,
194 | 'pid': sku_id,
195 | 'pcount': num,
196 | 'ptype': p_type,
197 | 'targetId': target_id,
198 | 'promoID': promo_id,
199 | 'outSkus': '',
200 | 'random': random.random(),
201 | # 'locationId'
202 | }
203 | session.headers = {
204 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
205 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
206 | "Referer": "https://cart.jd.com/cart",
207 | "Connection": "keep-alive"
208 | }
209 | resp = session.post(url, data=data)
210 | return json.loads(resp.text)['sortedWebCartResult']['achieveSevenState'] == 2
211 |
212 |
213 | def addItemToCart(sku_id, session, logger):
214 | url = 'https://cart.jd.com/gate.action'
215 | payload = {
216 | 'pid': sku_id,
217 | 'pcount': 1,
218 | 'ptype': 1,
219 | }
220 | resp = session.get(url=url, params=payload)
221 | if 'https://cart.jd.com/cart.action' in resp.url: # 套装商品加入购物车后直接跳转到购物车页面
222 | result = True
223 | else: # 普通商品成功加入购物车后会跳转到提示 "商品已成功加入购物车!" 页面
224 | soup = BeautifulSoup(resp.text, "html.parser")
225 | result = bool(soup.select('h3.ftx-02')) # [
商品已成功加入购物车!
]
226 |
227 | if result:
228 | logger.info('%s 已成功加入购物车', sku_id)
229 | else:
230 | logger.error('%s 添加到购物车失败', sku_id)
231 |
232 |
233 | def get_checkout_page_detail(session, logger):
234 | url = 'http://trade.jd.com/shopping/order/getOrderInfo.action'
235 | # url = 'https://cart.jd.com/gotoOrder.action'
236 | payload = {
237 | 'rid': str(int(time.time() * 1000)),
238 | }
239 | headers = {
240 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
241 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
242 | "Referer": "https://cart.jd.com/cart.action",
243 | "Connection": "keep-alive",
244 | 'Host': 'trade.jd.com',
245 | }
246 | try:
247 | resp = session.get(url=url, params=payload, headers=headers)
248 | if not responseStatus(resp):
249 | logger.error('获取订单结算页信息失败')
250 | return ''
251 |
252 | soup = BeautifulSoup(resp.text, "html.parser")
253 | risk_control = get_tag_value(soup.select('input#riskControl'), 'value')
254 |
255 | order_detail = {
256 | 'address': soup.find('span', id='sendAddr').text[5:], # remove '寄送至: ' from the begin
257 | 'receiver': soup.find('span', id='sendMobile').text[4:], # remove '收件人:' from the begin
258 | 'total_price': soup.find('span', id='sumPayPriceId').text[1:], # remove '¥' from the begin
259 | 'items': []
260 | }
261 |
262 | logger.info("下单信息: %s", order_detail)
263 | return order_detail
264 | except requests.exceptions.RequestException as e:
265 | logger.error('订单结算页面获取异常: %s' % e)
266 | except Exception as e:
267 | logger.error('下单页面数据解析异常: %s', e)
268 | return None
269 |
270 |
271 | def submit_order(risk_control, session, logger, payment_pwd):
272 | url = 'https://trade.jd.com/shopping/order/submitOrder.action'
273 | # js function of submit order is included in https://trade.jd.com/shopping/misc/js/order.js?r=2018070403091
274 |
275 | # overseaPurchaseCookies:
276 | # vendorRemarks: []
277 | # submitOrderParam.sopNotPutInvoice: false
278 | # submitOrderParam.trackID: TestTrackId
279 | # submitOrderParam.ignorePriceChange: 0
280 | # submitOrderParam.btSupport: 0
281 | # riskControl:
282 | # submitOrderParam.isBestCoupon: 1
283 | # submitOrderParam.jxj: 1
284 | # submitOrderParam.trackId:
285 |
286 | data = {
287 | 'overseaPurchaseCookies': '',
288 | 'vendorRemarks': '[]',
289 | 'submitOrderParam.sopNotPutInvoice': 'false',
290 | 'submitOrderParam.trackID': 'TestTrackId',
291 | 'submitOrderParam.ignorePriceChange': '0',
292 | 'submitOrderParam.btSupport': '0',
293 | 'riskControl': risk_control,
294 | 'submitOrderParam.isBestCoupon': 1,
295 | 'submitOrderParam.jxj': 1,
296 | 'submitOrderParam.trackId': '9643cbd55bbbe103eef18a213e069eb0', # Todo: need to get trackId
297 | # 'submitOrderParam.eid': eid,
298 | # 'submitOrderParam.fp': fp,
299 | 'submitOrderParam.needCheck': 1,
300 | }
301 |
302 | def encrypt_payment_pwd(paymentPwd):
303 | return ''.join(['u3' + x for x in paymentPwd])
304 |
305 | if len(payment_pwd) > 0:
306 | data['submitOrderParam.payPassword'] = encrypt_payment_pwd(payment_pwd)
307 |
308 | headers = {
309 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
310 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
311 | "Referer": "http://trade.jd.com/shopping/order/getOrderInfo.action",
312 | "Connection": "keep-alive",
313 | 'Host': 'trade.jd.com',
314 | }
315 |
316 | try:
317 | resp = session.post(url=url, data=data, headers=headers)
318 | resp_json = json.loads(resp.text)
319 |
320 | if resp_json.get('success'):
321 | logger.info('订单提交成功! 订单号: %s', resp_json.get('orderId'))
322 | return True
323 | else:
324 | message, result_code = resp_json.get('message'), resp_json.get('resultCode')
325 | if result_code == 0:
326 | # self._save_invoice()
327 | message = message + '(下单商品可能为第三方商品, 将切换为普通发票进行尝试)'
328 | elif result_code == 60077:
329 | message = message + '(可能是购物车为空或未勾选购物车中商品)'
330 | elif result_code == 60123:
331 | message = message + '(未配置支付密码)'
332 | logger.info('订单提交失败, 错误码: %s, 返回信息: %s', result_code, message)
333 | logger.info(resp_json)
334 | return False
335 | except Exception as e:
336 | logger.error(e)
337 | return False
338 |
339 |
340 | def item_removed(sku_id):
341 | headers = {
342 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
343 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
344 | "Referer": "http://trade.jd.com/shopping/order/getOrderInfo.action",
345 | "Connection": "keep-alive",
346 | 'Host': 'trade.jd.com',
347 | }
348 | url = 'https://item.jd.com/{}.html'.format(sku_id)
349 | page = requests.get(url=url, headers=headers)
350 | return '该商品已下柜' not in page.text
351 |
352 |
353 | def buyGood(sku_id, session, logger, payment_pwd):
354 | for count in range(1, 5):
355 | logger.info('第[%s/%s]次尝试提交订单', count, 5)
356 | cancelSelectCartItem(session)
357 | cart = cart_detail(session, logger)
358 | if sku_id not in cart:
359 | addItemToCart(sku_id, session, logger)
360 | cart_detail(session, logger, True)
361 |
362 | risk_control = get_checkout_page_detail(session, logger)
363 | if len(risk_control) > 0:
364 | if submit_order(risk_control, session, logger, payment_pwd):
365 | return True
366 | logger.info('等待%ss', 3)
367 | time.sleep(3)
368 | else:
369 | logger.info('执行结束, 提交订单失败!')
370 | return False
371 |
372 | def main(sendTo, cookies_String, url):
373 | session = requests.session()
374 | session.headers = {
375 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
376 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
377 | "Connection": "keep-alive"
378 | }
379 |
380 | logFileName = 'jdAutoBuyGood.log'
381 | logger = logging.getLogger()
382 | setLogger(logFileName, logger)
383 |
384 | manual_cookies = {}
385 | for item in cookies_String.split(';'):
386 | # 用=号分割.
387 | name, value = item.strip().split('=', 1)
388 | manual_cookies[name] = value
389 |
390 | # print(manual_cookies)
391 | cookiesJar = requests.utils.cookiejar_from_dict(manual_cookies, cookiejar=None, overwrite=True)
392 | session.cookies = cookiesJar
393 |
394 | payment_pwd = ''
395 | flag = 1
396 | while (1):
397 | try:
398 | if flag == 1:
399 | validateCookies(logger, session)
400 | getUsername(logger, session)
401 | checkSession = requests.Session()
402 | checkSession.headers = {
403 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
404 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
405 | "Connection": "keep-alive"
406 | }
407 | logger.info('第' + str(flag) + '次 查询...')
408 | flag += 1
409 | for i in url:
410 | # 商品url
411 | skuId = i.split('skuId=')[1].split('&')[0]
412 | skuidUrl = 'https://item.jd.com/' + skuId + '.html'
413 | response = checkSession.get(i)
414 | if (response.text.find('无货') > 0):
415 | logger.info('[%s]商品无货.', skuId)
416 | else:
417 | if item_removed(skuId):
418 | logger.info('[%s]商品有货啦! 马上下单...', skuId)
419 | if buyGood(skuId, session, logger, payment_pwd):
420 | sendMail(skuidUrl, True, sendTo)
421 | sys.exit(1)
422 | else:
423 | sendMail(skuidUrl, False, sendTo)
424 | sys.exit(1)
425 | else:
426 | logger.info('[%s]商品有货, 但已下架.', skuId)
427 | time.sleep(5)
428 | if flag % 20 == 0:
429 | logger.info('校验是否还在登录...')
430 | validateCookies(logger, session)
431 | except Exception as e:
432 | print(traceback.format_exc())
433 | time.sleep(10)
434 |
435 |
436 | if __name__ == '__main__':
437 | sendTo = ''
438 | cookies_String = ''
439 | fp = open("./Please fill out this document.txt", 'r', encoding='utf-8')
440 | cont = fp.read()
441 | pattern = re.compile("'(.*)'")
442 | contRe = pattern.findall(cont)
443 | if len(contRe) >= 2:
444 | sendTo = contRe[0]
445 | cookies_String = contRe[1]
446 | print("Please confirm the email address: %s" % sendTo)
447 | if len(cookies_String) == 0:
448 | print("ERROR: Missing cookie.")
449 |
450 | contRe = contRe[2:]
451 |
452 | try:
453 | main(sendTo, cookies_String, contRe)
454 | except Exception:
455 | sendError(sendTo)
--------------------------------------------------------------------------------
/Console Version/Please fill out this document.txt:
--------------------------------------------------------------------------------
1 | 请注意所有讯息在单引号内填写
2 | 您接收下单成功讯息的邮箱(可不填): '$$$$$$$@qq.com'
3 |
4 | 您的登录cookie(请按照README.md内指南获取):
5 | ''
6 |
7 | 您需要自动购买的商品URL(请按照README.md内指南获取):
8 | 'https://c0.3.cn/stock?skuId=1835967&area=16_1362_44319_51500&venderId=1000084390&buyNum=1&choseSuitSkuIds=&cat=9192,12190,1517&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery1535213'
9 | 'https://c0.3.cn/stock?skuId=1835968&area=16_1362_44319_51500&venderId=1000084390&buyNum=1&choseSuitSkuIds=&cat=9192,12190,1517&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery366840'
10 | 'https://c0.3.cn/stock?skuId=1336984&area=16_1362_44319_51500&venderId=1000078145&buyNum=1&choseSuitSkuIds=&cat=9192,12190,1517&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery94700'
11 | 'https://c0.3.cn/stock?skuId=65466451629&area=16_1362_44319_51500&venderId=127922&buyNum=1&choseSuitSkuIds=&cat=9855,9858,9924&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery8432226'
12 | 'https://c0.3.cn/stock?skuId=7498169&area=16_1362_44319_51500&venderId=1000128491&buyNum=1&choseSuitSkuIds=&cat=9855,9858,9924&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery7323892'
13 | 'https://c0.3.cn/stock?skuId=7263128&area=16_1362_44319_51500&venderId=1000128491&buyNum=1&choseSuitSkuIds=&cat=9855,9858,9924&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery9621467'
14 | 'https://c0.3.cn/stock?skuId=4061438&area=16_1362_44319_51500&venderId=1000005584&buyNum=1&choseSuitSkuIds=&cat=9192,12190,1517&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery620656'
15 | 'https://c0.3.cn/stock?skuId=65421329578&area=16_1362_44319_51500&venderId=593210&buyNum=1&choseSuitSkuIds=&cat=9192,12190,1517&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery4952468'
16 | 'https://c0.3.cn/stock?skuId=100005678825&area=16_1362_44319_51500&venderId=1000090691&buyNum=1&choseSuitSkuIds=&cat=9855,9858,9924&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery2192795'
17 | 'https://c0.3.cn/stock?skuId=100005294853&area=16_1362_44319_51500&venderId=1000090691&buyNum=1&choseSuitSkuIds=&cat=9855,9858,9924&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery8766944'
18 | 'https://c0.3.cn/stock?skuId=45923412989&area=16_1362_44319_51500&venderId=10066244&buyNum=1&choseSuitSkuIds=&cat=9192,12190,1517&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery8072251'
19 | 'https://c0.3.cn/stock?skuId=62830056100&area=16_1362_44319_51500&venderId=10066244&buyNum=1&choseSuitSkuIds=&cat=9192,12190,1517&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery599501'
20 | 'https://c0.3.cn/stock?skuId=45006657879&area=16_1362_44319_51500&venderId=10066244&buyNum=1&choseSuitSkuIds=&cat=9192,12190,1517&extraParam={%22originid%22:%221%22}&fqsp=0&pdpin=jd_77a8935fb872d&pduid=526700225&ch=1&callback=jQuery6257903'
--------------------------------------------------------------------------------
/Console Version/jdAutoBuyGood.log:
--------------------------------------------------------------------------------
1 | 2020-02-06 13:01:10,916 INFO: 登录成功!
2 | 2020-02-06 13:01:11,168 INFO: 登录账号名称: jd_180519iya
3 | 2020-02-06 13:01:11,173 INFO: 第1次 查询...
4 | 2020-02-06 13:01:11,389 INFO: [1835967]商品无货.
5 | 2020-02-06 13:01:11,583 INFO: [1835968]商品无货.
6 | 2020-02-07 18:13:10,251 INFO: 登录成功!
7 | 2020-02-07 18:13:10,463 INFO: 登录账号名称: jd_180519iya
8 | 2020-02-07 18:13:10,467 INFO: 第1次 查询...
9 | 2020-02-07 18:13:10,699 INFO: [1835967]商品无货.
10 | 2020-02-07 18:13:10,947 INFO: [1835968]商品无货.
11 |
--------------------------------------------------------------------------------
/Console Version/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.22.0
2 | beautifulsoup4==4.8.2
3 |
--------------------------------------------------------------------------------
/Console Version/test.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | '''
4 | 【简介】
5 | PyQt5中QButton例子
6 | '''
7 |
8 | import sys
9 | from PyQt5.QtCore import *
10 | from PyQt5.QtGui import *
11 | from PyQt5.QtWidgets import *
12 |
13 | class Form(QDialog):
14 | def __init__(self, parent=None):
15 | super(Form, self).__init__(parent)
16 | layout = QVBoxLayout() #设置垂直布局
17 |
18 | self.btn1 = QPushButton("Button1")
19 | # setChenkable():设置按钮是否已经被选中,true表示按钮将被保持已点击和释放状态
20 | self.btn1.setCheckable(True)
21 | # toggle():在按钮之间进行切换
22 | self.btn1.toggle()
23 | # 通过lambda方式来传递额外的参数btn1,将clicked信号发送给槽函数whichbtn()
24 | self.btn1.clicked.connect(lambda:self.whichbtn(self.btn1) )
25 | self.btn1.clicked.connect(self.btnstate)
26 | layout.addWidget(self.btn1)
27 |
28 | self.btn2 = QPushButton('image')
29 | self.btn2.setIcon(QIcon(QPixmap("./images/python.png")))
30 | self.btn2.clicked.connect(lambda:self.whichbtn(self.btn2) )
31 | layout.addWidget(self.btn2)
32 | self.setLayout(layout)
33 |
34 | self.btn3 = QPushButton("Disabled")
35 | # setEnabled():设置按钮是否可用,false时按钮变成不可用状态,点击它不会发送信号
36 | self.btn3.setEnabled(False)
37 | layout.addWidget(self.btn3)
38 |
39 | self.btn4= QPushButton("&Download")
40 | self.btn4.setDefault(True) #设置按钮的默认状态
41 | self.btn4.clicked.connect(lambda:self.whichbtn(self.btn4))
42 | layout.addWidget(self.btn4)
43 | self.setWindowTitle("Button demo")
44 |
45 | def btnstate(self):
46 | if self.btn1.isChecked(): #返回按钮的状态
47 | print("button pressed" )
48 |
49 | else:
50 | print("button released" )
51 |
52 | def whichbtn(self,btn):
53 | print("clicked button is " + btn.text() )
54 |
55 | if __name__ == '__main__':
56 | app = QApplication(sys.argv)
57 | btnDemo = Form()
58 | btnDemo.show()
59 | sys.exit(app.exec_())
--------------------------------------------------------------------------------
/GUI Version/autobuy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file 'qsswindow.ui'
4 | #
5 | # Created by: PyQt5 UI code generator 5.14.1
6 | #
7 | # WARNING! All changes made in this file will be lost!
8 |
9 | from PyQt5 import QtCore, QtGui, QtWidgets
10 | from PyQt5.QtWidgets import QApplication
11 | from PyQt5.QtCore import QTimer
12 |
13 | import sys
14 |
15 | import argparse
16 | import os
17 | import pickle
18 | import random
19 | import time
20 | import json
21 | import requests
22 | import re
23 | import logging
24 | import logging.handlers
25 | from bs4 import BeautifulSoup
26 | import smtplib
27 | from email.mime.text import MIMEText
28 | from email.header import Header
29 |
30 | class Ui_QSSWindow(object):
31 | def setupUi(self, QSSWindow):
32 | QSSWindow.setObjectName("QSSWindow")
33 | QSSWindow.resize(950, 450)
34 | self.centralwidget = QtWidgets.QWidget(QSSWindow)
35 | self.centralwidget.setObjectName("centralwidget")
36 | self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget)
37 | self.verticalLayout_3.setObjectName("verticalLayout_3")
38 | self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
39 | self.tabWidget.setObjectName("tabWidget")
40 | self.tab = QtWidgets.QWidget()
41 | self.tab.setObjectName("tab")
42 |
43 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tab)
44 | self.verticalLayout_2.setObjectName("verticalLayout_2")
45 | self.groupBox = QtWidgets.QGroupBox(self.tab)
46 | self.groupBox.setObjectName("groupBox")
47 | self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
48 | self.verticalLayout.setObjectName("verticalLayout")
49 | self.goods = QtWidgets.QHBoxLayout()
50 | self.goods.setObjectName("goods")
51 | self.labelGoods = QtWidgets.QLabel(self.groupBox)
52 | self.labelGoods.setObjectName("labelGoods")
53 | self.goods.addWidget(self.labelGoods)
54 | self.inputGoods = QtWidgets.QLineEdit(self.groupBox)
55 | self.inputGoods.setObjectName("inputGoods")
56 | self.goods.addWidget(self.inputGoods)
57 | self.verticalLayout.addLayout(self.goods)
58 | self.area = QtWidgets.QHBoxLayout()
59 | self.area.setObjectName("area")
60 | self.labelArea = QtWidgets.QLabel(self.groupBox)
61 | self.labelArea.setObjectName("labelArea")
62 | self.area.addWidget(self.labelArea)
63 | self.inputArea = QtWidgets.QLineEdit(self.groupBox)
64 | self.inputArea.setObjectName("inputArea")
65 | self.area.addWidget(self.inputArea)
66 |
67 | self.labelMail = QtWidgets.QLabel(self.groupBox)
68 | self.labelMail.setObjectName("labelMail")
69 | self.area.addWidget(self.labelMail)
70 | self.inputMail = QtWidgets.QLineEdit(self.groupBox)
71 | self.inputMail.setObjectName("inputMail")
72 | self.area.addWidget(self.inputMail)
73 |
74 | self.labelNum = QtWidgets.QLabel(self.groupBox)
75 | self.labelNum.setObjectName("labelNum")
76 | self.area.addWidget(self.labelNum)
77 | self.comboBox = QtWidgets.QComboBox(self.groupBox)
78 | self.comboBox.setObjectName("comboBox")
79 | self.area.addWidget(self.comboBox)
80 |
81 | self.checkBox = QtWidgets.QCheckBox(self.groupBox)
82 | self.checkBox.setObjectName("checkBox")
83 | self.area.addWidget(self.checkBox)
84 |
85 | self.verticalLayout.addLayout(self.area)
86 | self.speedLayout = QtWidgets.QHBoxLayout()
87 | self.speedLayout.setObjectName("speedLayout")
88 | self.horizontalSlider = QtWidgets.QSlider(self.groupBox)
89 | self.horizontalSlider.setMaximum(100)
90 | self.horizontalSlider.setProperty("value", 50)
91 | self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
92 | self.horizontalSlider.setTickInterval(0)
93 | self.horizontalSlider.setObjectName("horizontalSlider")
94 | self.speedLayout.addWidget(self.horizontalSlider)
95 | self.progressBar = QtWidgets.QProgressBar(self.groupBox)
96 | self.progressBar.setProperty("value", 24)
97 | self.progressBar.setObjectName("progressBar")
98 | self.speedLayout.addWidget(self.progressBar)
99 |
100 | self.verticalLayout.addLayout(self.speedLayout)
101 | self.textEdit = QtWidgets.QTextEdit(self.groupBox)
102 | self.textEdit.setObjectName("textEdit")
103 | self.verticalLayout.addWidget(self.textEdit)
104 | self.buttonLayout = QtWidgets.QHBoxLayout()
105 | self.buttonLayout.setObjectName("buttonLayout")
106 | self.loginBtn = QtWidgets.QPushButton(self.groupBox)
107 | self.loginBtn.setStyleSheet("")
108 | self.loginBtn.setObjectName("loginBtn")
109 | self.buttonLayout.addWidget(self.loginBtn)
110 | self.startBtn = QtWidgets.QPushButton(self.groupBox)
111 | self.startBtn.setStyleSheet("")
112 | self.startBtn.setObjectName("startBtn")
113 | self.buttonLayout.addWidget(self.startBtn)
114 | self.stopBtn = QtWidgets.QPushButton(self.groupBox)
115 | self.stopBtn.setStyleSheet("")
116 | self.stopBtn.setObjectName("stopBtn")
117 | self.buttonLayout.addWidget(self.stopBtn)
118 | self.verticalLayout.addLayout(self.buttonLayout)
119 | self.verticalLayout_2.addWidget(self.groupBox)
120 | self.tabWidget.addTab(self.tab, "")
121 | self.tab_1 = QtWidgets.QWidget()
122 | self.tab_1.setObjectName("tab_1")
123 |
124 | self.labelAboutMe = QtWidgets.QLabel(self.tab_1)
125 | # self.labelAboutMe.setGeometry(QtCore.QRect(50, 30, 280, 180))
126 | self.labelAboutMe.setObjectName("labelAboutMe")
127 |
128 | self.tabWidget.addTab(self.tab_1, "")
129 | self.verticalLayout_3.addWidget(self.tabWidget)
130 | QSSWindow.setCentralWidget(self.centralwidget)
131 | self.statusbar = QtWidgets.QStatusBar(QSSWindow)
132 | self.statusbar.setObjectName("statusbar")
133 | QSSWindow.setStatusBar(self.statusbar)
134 | self.menubar = QtWidgets.QMenuBar(QSSWindow)
135 | self.menubar.setGeometry(QtCore.QRect(0, 0, 680, 25))
136 | self.menubar.setObjectName("menubar")
137 | self.file = QtWidgets.QMenu(self.menubar)
138 | self.file.setObjectName("file")
139 | self.edit = QtWidgets.QMenu(self.menubar)
140 | self.edit.setObjectName("edit")
141 | QSSWindow.setMenuBar(self.menubar)
142 | self.file.addSeparator()
143 | self.menubar.addAction(self.file.menuAction())
144 | self.menubar.addAction(self.edit.menuAction())
145 |
146 | self.retranslateUi(QSSWindow)
147 | self.tabWidget.setCurrentIndex(5)
148 | QtCore.QMetaObject.connectSlotsByName(QSSWindow)
149 |
150 |
151 |
152 |
153 |
154 | def retranslateUi(self, QSSWindow):
155 | _translate = QtCore.QCoreApplication.translate
156 | QSSWindow.setWindowTitle(_translate("QSSWindow", "MainWindow"))
157 | self.tabWidget.setToolTip(_translate("QSSWindow", "
"))
158 | self.tabWidget.setWhatsThis(_translate("QSSWindow", "
"))
159 | self.groupBox.setTitle(_translate("QSSWindow", "一定能尽快战胜疫情, 中国加油!"))
160 | self.labelGoods.setText(_translate("QSSWindow", "请输入商品ID(以逗号间隔):"))
161 | self.labelArea.setText(_translate("QSSWindow", "请输入收件地区编码:"))
162 | self.labelMail.setText(_translate("QSSWindow", "接受讯息邮箱:"))
163 | self.labelNum.setText(_translate("QSSWindow", "购买数量:"))
164 | self.loginBtn.setText(_translate("QSSWindow", "扫码登录"))
165 | self.startBtn.setText(_translate("QSSWindow", "开始监控(可自动登录)"))
166 | self.stopBtn.setText(_translate("QSSWindow", "停止监控"))
167 | self.checkBox.setText(_translate("QSSWindow", "是否自动忽略下架商品"))
168 |
169 |
170 | self.labelAboutMe.setText(_translate("QSSWindow", '使用指南
请点击这里跳转 战疫情, 加油!
欢迎在GitHub上加星. 谢谢!
Tips: 登录一次之后本地会保存登录信息, 重启软件之后仍然可以记住账号登录信息
只需点击"开始监控"就可以自动登录, 不必重复扫码哦
'))
171 | self.labelAboutMe.setOpenExternalLinks(True)
172 |
173 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("QSSWindow", "Console"))
174 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), _translate("QSSWindow", "指南"))
175 |
176 | self.file.setTitle(_translate("QSSWindow", "文件"))
177 | self.edit.setTitle(_translate("QSSWindow", "编辑"))
178 |
179 |
180 |
181 | class Autobuy(QtWidgets.QMainWindow, Ui_QSSWindow):
182 | def __init__(self):
183 | super().__init__()
184 | self.sess = requests.Session()
185 | self.sess.headers = {
186 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
187 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
188 | "Referer": "https://order.jd.com/center/list.action",
189 | "Connection": "keep-alive"
190 | }
191 |
192 | self.headers = {
193 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
194 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
195 | "Referer": "https://cart.jd.com/cart.action",
196 | "Connection": "keep-alive"
197 | }
198 |
199 | self.speed = 5000
200 | self.isMonitorSoldout = True
201 | # self.isLogin = False
202 | self.cookiesString = ''
203 | self.cookies = {}
204 | self.skuidString = ''
205 | self.skuid = []
206 | self.cont = 1
207 | self.timer = QTimer(self)
208 | self.logger = logging.getLogger()
209 | self.loadQSS()
210 | self.setupUi(self)
211 | self.connectSign()
212 | self.initData()
213 | self.show()
214 |
215 |
216 | def loadQSS(self):
217 | file = 'window.qss'
218 | with open(file, 'rt', encoding='utf8') as f:
219 | styleSheet = f.read()
220 | self.setStyleSheet(styleSheet)
221 | f.close()
222 |
223 | def setLogger(self, logFileName='jdAutoBuyGood.log'):
224 | self.logger.setLevel(logging.INFO)
225 | formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
226 |
227 | console_handler = logging.StreamHandler()
228 | console_handler.setFormatter(formatter)
229 | self.logger.addHandler(console_handler)
230 |
231 | file_handler = logging.handlers.RotatingFileHandler(
232 | logFileName, maxBytes=10485760, backupCount=5, encoding="utf-8")
233 | file_handler.setFormatter(formatter)
234 | self.logger.addHandler(file_handler)
235 |
236 | def initData(self):
237 | self.inputGoods.setText("65466451629,65437208345,7498169,7498165,1835968,7263128,7498167,17449572304,37934196731,100001086804,56657322838,56657322841,100005294853,1938795,15595191653,15595191654,45923412989,1835967,1336984,65466451629,7498169,7263128,4061438,65421329578,100005678825,100005294853,45923412989,62830056100,45006657879")
238 | self.inputArea.setText("16_1362_44319_51500")
239 | self.inputMail.setText("$$$$$$$@qq.com")
240 | self.comboBox.addItems(['1', '2', '3'])
241 | self.horizontalSlider.setValue(50)
242 | self.horizontalSlider.setMaximum(100)
243 | self.horizontalSlider.setMinimum(0)
244 | self.setProgressBar()
245 |
246 | self.setLogger()
247 |
248 | if len(self.cookiesString) != 0:
249 | manual_cookies = {}
250 | for item in self.cookiesString.split(';'):
251 | # 用=号分割.
252 | name, value = item.strip().split('=', 1)
253 | manual_cookies[name] = value
254 | self.cookies = requests.utils.cookiejar_from_dict(manual_cookies, cookiejar=None, overwrite=True)
255 | self.sess.cookies = self.cookies
256 |
257 | def connectSign(self):
258 | self.horizontalSlider.valueChanged.connect(self.setProgressBar)
259 | self.horizontalSlider.valueChanged[int].connect(self.slvaluechange)
260 |
261 | self.loginBtn.clicked.connect(self.loginByQR)
262 | self.startBtn.clicked.connect(self.monitorConnect)
263 | self.stopBtn.clicked.connect(self.stopConnect)
264 |
265 | def stopConnect(self):
266 | self.updateStateText("已停止监控.")
267 | self.timer.stop()
268 |
269 | def stopNow(self):
270 | self.updateStateText("已停止监控.")
271 | self.timer.stop()
272 |
273 | def slvaluechange(self, val):
274 | # self.updateStateText("设置抢购速度: %d" % val)
275 | if val > 85:
276 | self.speed = 500
277 | elif val > 60:
278 | self.speed = 9000 - val * 100
279 | else:
280 | self.speed = 10000 - val * 100
281 |
282 | def setProgressBar(self):
283 | self.progressBar.setValue(self.horizontalSlider.value())
284 |
285 | def checkLogin(self):
286 | self.updateStateText('正在验证登录状态...')
287 | for flag in range(1, 3):
288 | try:
289 | targetURL = 'https://order.jd.com/center/list.action'
290 | payload = {
291 | 'rid': str(int(time.time() * 1000)),
292 | }
293 | resp = self.sess.get(url=targetURL, params=payload, allow_redirects=False)
294 | if resp.status_code == requests.codes.OK:
295 | self.updateStateText('登录成功!')
296 | return True
297 | else:
298 | self.updateStateText('第 %s 次再尝试验证cookie...' % flag)
299 | self.updateStateText('正在尝试从历史讯息中恢复...')
300 | with open('cookie', 'rb') as f:
301 | cookies = requests.utils.cookiejar_from_dict(pickle.load(f))
302 | # print(cookies)
303 | self.sess.cookies = cookies
304 | continue
305 | except Exception as e:
306 | self.updateStateText(str(e))
307 | self.updateStateText('第 %s 次再尝试验证cookie...' % flag)
308 | continue
309 | self.updateStateText('请登录!')
310 | return False
311 |
312 | def loginByQR(self):
313 | # jd login by QR code
314 | try:
315 | self.updateStateText('请您打开京东手机客户端或微信扫一扫, 准备扫码登录')
316 |
317 | urls = (
318 | 'https://passport.jd.com/new/login.aspx',
319 | 'https://qr.m.jd.com/show',
320 | 'https://qr.m.jd.com/check',
321 | 'https://passport.jd.com/uc/qrCodeTicketValidation'
322 | )
323 | # step 1: open login page
324 | response = self.sess.get(
325 | urls[0],
326 | headers=self.headers
327 | )
328 | if response.status_code != requests.codes.OK:
329 | self.updateStateText(f'获取登录页失败: {response.status_code}')
330 | # self.isLogin = False
331 | return False
332 | # update cookies
333 | self.cookies.update(response.cookies)
334 | self.sess.cookies = response.cookies
335 |
336 | # step 2: get QR image
337 | response = self.sess.get(
338 | urls[1],
339 | headers=self.headers,
340 | cookies=self.cookies,
341 | params={
342 | 'appid': 133,
343 | 'size': 147,
344 | 't': int(time.time() * 1000),
345 | }
346 | )
347 | if response.status_code != requests.codes.OK:
348 | self.updateStateText(f'获取二维码失败: {response.status_code}')
349 | # self.isLogin = False
350 | return False
351 |
352 | # update cookies
353 | self.cookies.update(response.cookies)
354 | self.sess.cookies = response.cookies
355 |
356 | # save QR code
357 | image_file = 'qr.png'
358 | with open(image_file, 'wb') as f:
359 | for chunk in response.iter_content(chunk_size=1024):
360 | f.write(chunk)
361 |
362 | # scan QR code with phone
363 | if os.name == "nt":
364 | # for windows
365 | os.system('start ' + image_file)
366 | else:
367 | if os.uname()[0] == "Linux":
368 | # for linux platform
369 | os.system("eog " + image_file)
370 | else:
371 | # for Mac platform
372 | os.system("open " + image_file)
373 |
374 | # step 3: check scan result
375 | self.headers['Host'] = 'qr.m.jd.com'
376 | self.headers['Referer'] = 'https://passport.jd.com/new/login.aspx'
377 |
378 | # check if QR code scanned
379 | qr_ticket = None
380 | retry_times = 1000 # 尝试1000次
381 | while retry_times:
382 | retry_times -= 1
383 | response = self.sess.get(
384 | urls[2],
385 | headers=self.headers,
386 | cookies=self.cookies,
387 | params={
388 | 'callback': 'jQuery%d' % random.randint(1000000, 9999999),
389 | 'appid': 133,
390 | 'token': self.cookies['wlfstk_smdl'],
391 | '_': int(time.time() * 1000)
392 | }
393 | )
394 | if response.status_code != requests.codes.OK:
395 | continue
396 | rs = json.loads(re.search(r'{.*?}', response.text, re.S).group())
397 | if rs['code'] == 200:
398 | self.updateStateText(f"{rs['ticket']}(Response Code: {rs['code']})")
399 | qr_ticket = rs['ticket']
400 | break
401 | else:
402 | self.updateStateText(f"{rs['msg']}(Response Code: {rs['code']})")
403 | time.sleep(2)
404 |
405 | if not qr_ticket:
406 | self.updateStateText("ERROR: 二维码登录失败.")
407 | # self.isLogin = False
408 | return False
409 |
410 | # step 4: validate scan result
411 | self.headers['Host'] = 'passport.jd.com'
412 | self.headers['Referer'] = 'https://passport.jd.com/new/login.aspx'
413 | response = requests.get(
414 | urls[3],
415 | headers=self.headers,
416 | cookies=self.cookies,
417 | params={'t': qr_ticket},
418 | )
419 | if response.status_code != requests.codes.OK:
420 | self.updateStateText(f"二维码登录校验失败: {response.status_code}")
421 | # self.isLogin = False
422 | return False
423 |
424 | # 京东有时会认为当前登录有危险, 需要手动验证
425 | # url: https://safe.jd.com/dangerousVerify/index.action?username=...
426 | res = json.loads(response.text)
427 | if not response.headers.get('p3p'):
428 | if 'url' in res:
429 | self.updateStateText(f"请进行手动安全验证: {res['url']}")
430 | # self.isLogin = False
431 | return False
432 | else:
433 | self.updateStateText('登录失败, ERROR message: ' + res)
434 | # self.isLogin = False
435 | return False
436 |
437 | # login succeed
438 | self.headers['P3P'] = response.headers.get('P3P')
439 | self.cookies.update(response.cookies)
440 | self.sess.cookies = response.cookies
441 |
442 |
443 | # save cookie
444 | with open('cookie', 'wb') as f:
445 | pickle.dump(self.cookies, f)
446 |
447 | self.updateStateText("登录成功!")
448 | # self.isLogin = True
449 | self.getUsername()
450 | return True
451 |
452 | except Exception as e:
453 | # self.isLogin = False
454 | self.updateStateText('ERROR message: ' + str(e))
455 | raise
456 |
457 | def getUsername(self):
458 | userName_Url = 'https://passport.jd.com/new/helloService.ashx?callback=jQuery339448&_=' + str(
459 | int(time.time() * 1000))
460 |
461 | resp = self.sess.get(url=userName_Url, allow_redirects=True)
462 | resultText = resp.text
463 | resultText = resultText.replace('jQuery339448(', '')
464 | resultText = resultText.replace(')', '')
465 | usernameJson = json.loads(resultText)
466 | self.updateStateText('账号名称: ' + usernameJson['nick'])
467 |
468 | def checkStock(self):
469 | url = 'https://c0.3.cn/stocks'
470 |
471 | callback = 'jQuery' + str(random.randint(1000000, 9999999))
472 |
473 | headers = {
474 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
475 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
476 | "Referer": "https://cart.jd.com/cart.action",
477 | "Connection": "keep-alive",
478 | }
479 |
480 | payload = {
481 | 'type': 'getstocks',
482 | 'skuIds': self.skuidString,
483 | 'area': self.areaID,
484 | 'callback': callback,
485 | '_': int(time.time() * 1000),
486 | }
487 | resp = self.sess.get(url=url, params=payload, headers=headers)
488 | respText = resp.text.replace(callback + '(', '').replace(')', '')
489 | respJson = json.loads(respText)
490 | inStockSkuid = []
491 | nohasSkuid = []
492 | abnormalSkuid = []
493 |
494 | soldOut = 0
495 | alloca = 0
496 | for i in self.skuid:
497 | try:
498 | if respJson[i]['StockStateName'] == '无货':
499 | nohasSkuid.append(i)
500 | soldOut += 1
501 | elif respJson[i]['StockStateName'] == '可配货':
502 | nohasSkuid.append(i)
503 | alloca += 1
504 | else:
505 | inStockSkuid.append(i)
506 |
507 | except Exception as e:
508 | abnormalSkuid.append(i)
509 |
510 | if soldOut != 0:
511 | self.updateStateText('监控的 %d 个商品无货.' % soldOut)
512 | if alloca != 0:
513 | self.updateStateText('监控的 %d 个商品所在地区暂无货, 未来可能配货.' % alloca)
514 | if len(abnormalSkuid) > 0:
515 | self.updateStateText('WARNING: %s 编号商品查询异常.' % ','.join(abnormalSkuid))
516 | return inStockSkuid
517 |
518 | def isSoldOut(self, sku_id):
519 | headers = {
520 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
521 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
522 | "Referer": "http://trade.jd.com/shopping/order/getOrderInfo.action",
523 | "Connection": "keep-alive",
524 | 'Host': 'item.jd.com',
525 | }
526 | url = 'https://item.jd.com/{}.html'.format(sku_id)
527 | page = requests.get(url=url, headers=headers)
528 | return '该商品已下柜' not in page.text
529 |
530 | def selectAll(self):
531 | url = "https://cart.jd.com/selectAllItem.action"
532 | data = {
533 | 't': 0,
534 | 'outSkus': '',
535 | 'random': random.random()
536 | }
537 | resp = self.sess.post(url, data=data)
538 | if resp.status_code != requests.codes.OK:
539 | self.updateStateText('全选购物车商品出错! status_code: %u, URL: %s' % (resp.status_code, resp.url))
540 | return False
541 | self.updateStateText('全选购物车商品成功.')
542 | return True
543 |
544 | def cart_detail(self, isOutput=False):
545 | url = 'https://cart.jd.com/cart.action'
546 | resp = self.sess.get(url, headers=self.headers)
547 | soup = BeautifulSoup(resp.text, "html.parser")
548 |
549 | cartDetail = dict()
550 | for item in soup.find_all(class_='item-item'):
551 | sku_id = item['skuid'] # 商品id
552 | try:
553 | # 例如: ['increment', '8888', '100001071956', '1', '13', '0', '50067652554']
554 | # ['increment', '8888', '100002404322', '2', '1', '0']
555 | item_attr_list = item.find(class_='increment')['id'].split('_')
556 | p_type = item_attr_list[4]
557 | promo_id = target_id = item_attr_list[-1] if len(item_attr_list) == 7 else 0
558 |
559 | def get_tag_value(tag, key='', index=0):
560 | if key:
561 | value = tag[index].get(key)
562 | else:
563 | value = tag[index].text
564 | return value.strip(' \t\r\n')
565 |
566 | cartDetail[sku_id] = {
567 | 'name': get_tag_value(item.select('div.p-name a')), # 商品名称
568 | 'verder_id': item['venderid'], # 商家id
569 | 'count': int(item['num']), # 数量
570 | 'unit_price': get_tag_value(item.select('div.p-price strong'))[1:], # 单价
571 | 'total_price': get_tag_value(item.select('div.p-sum strong'))[1:], # 总价
572 | 'is_selected': 'item-selected' in item['class'], # 商品是否被勾选
573 | 'p_type': p_type,
574 | 'target_id': target_id,
575 | 'promo_id': promo_id
576 | }
577 | except Exception as e:
578 | self.updateStateText("ERROR: 商品%s在购物车中的信息无法解析, 报错信息: %s, 该商品自动忽略.", sku_id, e)
579 |
580 | if isOutput == True:
581 | self.updateStateText('当前购物车信息: %s.' % cartDetail)
582 | return cartDetail
583 |
584 | def addItemToCart(self, sku_id):
585 | url = 'https://cart.jd.com/gate.action'
586 | addNum = self.comboBox.currentIndex() + 1
587 | payload = {
588 | 'pid': sku_id,
589 | 'pcount': addNum,
590 | 'ptype': 1
591 | }
592 | resp = self.sess.get(url=url, params=payload)
593 | if 'https://cart.jd.com/cart.action' in resp.url: # 套装商品加入购物车后直接跳转到购物车页面
594 | result = True
595 | else: # 普通商品成功加入购物车后会跳转到提示 "商品已成功加入购物车!" 页面
596 | soup = BeautifulSoup(resp.text, "html.parser")
597 | result = bool(soup.select('h3.ftx-02')) # [商品已成功加入购物车!
]
598 |
599 | if result:
600 | self.updateStateText('%s 编号商品已成功加入购物车, 数量: %d.' % (sku_id, addNum))
601 | else:
602 | self.updateStateText('ERROR: %s 编号商品添加到购物车失败.' % sku_id)
603 |
604 | def responseStatus(self, resp):
605 | if resp.status_code != requests.codes.OK:
606 | self.updateStateText('Status: %u, Url: %s' % (resp.status_code, resp.url))
607 | return False
608 | return True
609 |
610 | def getCheckoutPageDetail(self):
611 | url = 'http://trade.jd.com/shopping/order/getOrderInfo.action'
612 | # url = 'https://cart.jd.com/gotoOrder.action'
613 | payload = {
614 | 'rid': str(int(time.time() * 1000)),
615 | }
616 | headers = {
617 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
618 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
619 | "Referer": "https://cart.jd.com/cart.action",
620 | "Connection": "keep-alive",
621 | 'Host': 'trade.jd.com',
622 | }
623 | try:
624 | resp = self.sess.get(url=url, params=payload, headers=headers)
625 |
626 | if not self.responseStatus(resp):
627 | self.updateStateText('ERROR: 获取订单结算页信息失败.')
628 | return None
629 | soup = BeautifulSoup(resp.text, "html.parser")
630 |
631 | order_detail = {
632 | 'address': soup.find('span', id='sendAddr').text[5:],
633 | 'receiver': soup.find('span', id='sendMobile').text[4:],
634 | 'total_price': soup.find('span', id='sumPayPriceId').text[1:],
635 | 'items': []
636 | }
637 |
638 | self.updateStateText("下单信息: %s" % order_detail)
639 | return order_detail
640 | except requests.exceptions.RequestException as e:
641 | self.updateStateText('订单结算页面获取异常, ERROR message: %s.' % e)
642 | except Exception as e:
643 | self.updateStateText('下单页面数据解析异常, ERROR message: %s.' % e)
644 | return None
645 |
646 | def submit_order(self, risk_control, payment_pwd):
647 | url = 'https://trade.jd.com/shopping/order/submitOrder.action'
648 | # js function of submit order is included in https://trade.jd.com/shopping/misc/js/order.js?r=2018070403091
649 |
650 | # overseaPurchaseCookies:
651 | # vendorRemarks: []
652 | # submitOrderParam.sopNotPutInvoice: false
653 | # submitOrderParam.trackID: TestTrackId
654 | # submitOrderParam.ignorePriceChange: 0
655 | # submitOrderParam.btSupport: 0
656 | # riskControl:
657 | # submitOrderParam.isBestCoupon: 1
658 | # submitOrderParam.jxj: 1
659 | # submitOrderParam.trackId:
660 |
661 | data = {
662 | 'overseaPurchaseCookies': '',
663 | 'vendorRemarks': '[]',
664 | 'submitOrderParam.sopNotPutInvoice': 'false',
665 | 'submitOrderParam.trackID': 'TestTrackId',
666 | 'submitOrderParam.ignorePriceChange': '0',
667 | 'submitOrderParam.btSupport': '0',
668 | 'riskControl': risk_control,
669 | 'submitOrderParam.isBestCoupon': 1,
670 | 'submitOrderParam.jxj': 1,
671 | 'submitOrderParam.trackId': '9643cbd55bbbe103eef18a213e069eb0', # Todo: need to get trackId
672 | # 'submitOrderParam.eid': eid,
673 | # 'submitOrderParam.fp': fp,
674 | 'submitOrderParam.needCheck': 1,
675 | }
676 |
677 | def encrypt_payment_pwd(paymentPwd):
678 | return ''.join(['u3' + x for x in paymentPwd])
679 |
680 | if len(payment_pwd) > 0:
681 | data['submitOrderParam.payPassword'] = encrypt_payment_pwd(payment_pwd)
682 |
683 | headers = {
684 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
685 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
686 | "Referer": "http://trade.jd.com/shopping/order/getOrderInfo.action",
687 | "Connection": "keep-alive",
688 | 'Host': 'trade.jd.com',
689 | }
690 |
691 | try:
692 | resp = self.sess.post(url=url, data=data, headers=headers)
693 | resp_json = json.loads(resp.text)
694 |
695 | if resp_json.get('success'):
696 | self.updateStateText('订单提交成功! 订单号: %s' % resp_json.get('orderId'))
697 | return True
698 | else:
699 | message, result_code = resp_json.get('message'), resp_json.get('resultCode')
700 | if result_code == 0:
701 | # self._save_invoice()
702 | message = message + '(下单商品可能为第三方商品, 将切换为普通发票进行尝试)'
703 | elif result_code == 60077:
704 | message = message + '(可能是购物车为空或未勾选购物车中商品)'
705 | elif result_code == 60123:
706 | message = message + '(未配置支付密码)'
707 | self.updateStateText('订单提交失败, 错误码: %s, 返回信息: %s.' % (result_code, message))
708 | self.updateStateText(resp_json)
709 | return False
710 | except Exception as e:
711 | self.updateStateText("ERROR: " + str(e))
712 | return False
713 |
714 | def cancelSelectCartItem(self):
715 | url = "https://cart.jd.com/cancelAllItem.action"
716 | data = {
717 | 't': 0,
718 | 'outSkus': '',
719 | 'random': random.random()
720 | }
721 | resp = self.sess.post(url, data=data)
722 | if resp.status_code != requests.codes.OK:
723 | self.updateStateText('cancelSelectCartItem() function WARNING: %u, Url: %s' % (resp.status_code, resp.url))
724 | return False
725 | return True
726 |
727 | def buyGoods(self, sku_id):
728 | for i in range(1, 2):
729 | self.updateStateText('第 %d/%d 次尝试提交订单...' % (i, 2))
730 | self.cancelSelectCartItem()
731 | cart = self.cart_detail()
732 | if sku_id not in cart:
733 | self.addItemToCart(sku_id)
734 | self.cart_detail(True)
735 |
736 | risk_control = self.getCheckoutPageDetail()
737 | if risk_control != None:
738 | if self.submit_order(risk_control, ''):
739 | return True
740 | time.sleep(1)
741 | else:
742 | self.updateStateText('执行结束,提交订单失败.')
743 | return False
744 |
745 | def monitorConnect(self):
746 | if self.checkLogin() == False:
747 | return False
748 | self.skuidString = self.inputGoods.text()
749 | pattern = re.compile(",|,")
750 | self.skuid = pattern.split(self.skuidString)
751 |
752 | self.areaID = self.inputArea.text()
753 |
754 | self.timer.timeout.connect(self.monitorMain)
755 | self.updateStateText("当前轮询速度为 %f 秒/次." % (self.speed / 1000))
756 | if self.checkBox.isChecked():
757 | self.isMonitorSoldout = False
758 | self.updateStateText('当前模式将为您自动忽略并删除下架商品.')
759 | else:
760 | self.isMonitorSoldout = True
761 | self.updateStateText('当前模式将为您保持监控下架商品, 若其上架则立即抢购.')
762 | self.timer.start(self.speed) # 设置计时间隔并启动
763 | return True
764 |
765 | def sendMail(self, url, isOrder):
766 | sendTo = self.inputMail.text()
767 | if len(sendTo) == 0 or sendTo == '$$$$$$$@qq.com':
768 | return
769 |
770 | mailRe = re.compile('^\w{1,15}@\w{1,10}\.(com|cn|net)$')
771 | if not re.search(mailRe, sendTo):
772 | return
773 |
774 | sendFrom = '645064582@qq.com'
775 |
776 | smtp_server = 'smtp.qq.com'
777 | if isOrder:
778 | msg = MIMEText('您抢购的 ' + url + ' 商品已下单, 请在尽快付款.', 'plain', 'utf-8')
779 | else:
780 | msg = MIMEText('您抢购的 ' + url + ' 商品下单失败.', 'plain', 'utf-8')
781 |
782 | msg['From'] = Header(sendFrom)
783 | msg['To'] = Header(sendTo)
784 | msg['Subject'] = Header('京东商品自动购买程序提示讯息')
785 |
786 | server = smtplib.SMTP_SSL(host=smtp_server)
787 | server.connect(smtp_server, 465)
788 | server.login(sendFrom, 'nkrzicfjkzznbehi')
789 | server.sendmail(sendFrom, sendTo, msg.as_string())
790 | server.quit()
791 |
792 | def removeItem(self):
793 | url = "https://cart.jd.com/batchRemoveSkusFromCart.action"
794 | data = {
795 | 't': 0,
796 | 'null': '',
797 | 'outSkus': '',
798 | 'random': random.random(),
799 | 'locationId': '19-1607-4773-0'
800 | }
801 | headers = {
802 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.37",
803 | "Accept": "application/json, text/javascript, */*; q=0.01",
804 | "Referer": "https://cart.jd.com/cart.action",
805 | "Host": "cart.jd.com",
806 | "Content-Type": "application/x-www-form-urlencoded",
807 | "Accept-Encoding": "gzip, deflate, br",
808 | "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
809 | "Origin": "https://cart.jd.com",
810 | "Connection": "keep-alive"
811 | }
812 |
813 | resp = self.sess.post(url, data=data, headers=headers)
814 | if resp.status_code != requests.codes.OK:
815 | self.updateStateText('清空购物车勾选商品出错. status_code: %u, URL: %s.' % (resp.status_code, resp.url))
816 | return False
817 |
818 | self.updateStateText('清空购物车勾选商品成功!')
819 | return True
820 |
821 | def monitorMain(self):
822 | try:
823 | checkSession = requests.Session()
824 | checkSession.headers = {
825 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
826 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
827 | "Connection": "keep-alive"
828 | }
829 | self.updateStateText('第 ' + str(self.cont) + ' 次查询:')
830 | self.cont += 1
831 | inStockSkuid = self.checkStock()
832 | soldOutNum = 0
833 | for skuId in inStockSkuid:
834 | if self.isSoldOut(skuId):
835 | self.updateStateText('%s 编号商品有货啦! 马上下单.' % skuId)
836 | skuidUrl = 'https://item.jd.com/' + skuId + '.html'
837 | if self.buyGoods(skuId):
838 | self.sendMail(skuidUrl, True)
839 | else:
840 | self.sendMail(skuidUrl, False)
841 | self.stopNow()
842 |
843 | else:
844 | if self.isMonitorSoldout == False:
845 | self.updateStateText('%s 编号商品已下架.' % skuId)
846 | self.skuid.remove(skuId)
847 | idBeg = self.skuidString.find(str(skuId))
848 | idEnd = idBeg + len(str(skuId))
849 | self.skuidString = self.skuidString[0:idBeg] + self.skuidString[idEnd + 1:]
850 | self.inputGoods.setText(self.skuidString)
851 | self.updateStateText('已将 %s 编号的下架商品清除, 并更新了商品编号输入框.' % skuId)
852 | self.selectAll()
853 | self.removeItem()
854 | else:
855 | soldOutNum += 1
856 | if soldOutNum != 0:
857 | self.updateStateText('监控的 %d 个商品已下架, 但当前模式保持监控.' % soldOutNum)
858 |
859 | if self.cont % 300 == 0:
860 | self.checkLogin()
861 | except Exception as e:
862 | import traceback
863 | self.updateStateText(traceback.format_exc())
864 |
865 | def updateStateText(self, stateText):
866 | # print(stateText)
867 | self.logger.info(stateText)
868 | self.textEdit.moveCursor(QtGui.QTextCursor.End)
869 | self.textEdit.insertPlainText(f'{time.ctime()} > ' + stateText + '\n')
870 |
871 | def main():
872 | app = QApplication(sys.argv)
873 | test = Autobuy()
874 | sys.exit(app.exec_())
875 |
876 |
877 |
878 | if __name__ == '__main__':
879 | main()
--------------------------------------------------------------------------------
/GUI Version/image/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/GUI Version/image/down.png
--------------------------------------------------------------------------------
/GUI Version/image/left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/GUI Version/image/left.png
--------------------------------------------------------------------------------
/GUI Version/image/right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/GUI Version/image/right.png
--------------------------------------------------------------------------------
/GUI Version/window.qss:
--------------------------------------------------------------------------------
1 | /*
2 | Notice:
3 | 01 注释只能用多行注释,单行注释会出错
4 | */
5 | /*******定义字体,背景颜色*********/
6 | *{
7 | font: "微软雅黑";
8 | color: rgb(220, 220, 220);
9 | background-color: rgb(68, 68, 68);
10 | border-radius: 2px;
11 | padding: 3px;
12 | margin: 2px;
13 | }
14 | /****************定义边框*****************/
15 | QLineEdit, QPushButton, QTimeEdit,
16 | QSpinBox, QDoubleSpinBox, QComboBox,
17 | QDateEdit, QDateTimeEdit, QGroupBox,
18 | QTextEdit, QTabWidget {
19 | border:1px solid rgb(36, 36, 36);
20 | }
21 | /*************菜单下拉按钮*****************/
22 | QMenu::item {
23 | background-color: transparent;
24 | }
25 | /********菜单下拉按钮(被选择时)***********/
26 | QMenu::item:selected {
27 | background-color: rgb(100, 100, 100);
28 | }
29 | /****************定义菜单栏***************/
30 | QMenuBar {
31 | border-radius: 0;
32 | background-color: rgb(72, 72, 72);
33 | border-bottom: 1px solid rgb(36, 36, 36);
34 | }
35 | /************菜单栏按钮(被选择时)********/
36 | QMenuBar::item:selected {
37 | background-color: rgb(100, 100, 100);
38 | }
39 | /************菜单栏按钮(鼠标悬停)********/
40 | QMenu:hover {
41 | background-color: rgb(100, 100, 100);
42 | }
43 | /*********按钮(渐变:突出显示)***********/
44 | QPushButton {
45 | border-style: inset;
46 | background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 rgba(72, 72, 72, 255), stop:1 rgba(56, 56, 56, 255));
47 | border:1px solid rgb(36, 36, 36);
48 | border-radius: 6px;
49 | min-width: 5em;
50 | min-height: 1em;
51 | padding: 6px 6px;
52 | margin: 5px
53 | }
54 | /**************按钮(鼠标悬停)**********/
55 | QPushButton:hover {
56 | background-color: rgb(91, 91, 91);
57 | }
58 | /**************按钮(按下)*************/
59 | QPushButton:pressed {
60 | background-color: rgb(68, 68, 68);
61 | }
62 | QSpinBox::up-button, QTimeEdit::up-button,
63 | QDateEdit::up-button, QDateTimeEdit::up-button,
64 | QDoubleSpinBox::up-button {
65 | subcontrol-position: right;
66 | border: 0px;
67 | }
68 | QSpinBox::down-button, QTimeEdit::down-button,
69 | QDateEdit::down-button, QDateTimeEdit::down-button,
70 | QDoubleSpinBox::down-button {
71 | subcontrol-position: left;
72 | border: 0px;
73 | }
74 | QSpinBox::up-arrow, QTimeEdit::up-arrow,
75 | QDateEdit::up-arrow, QDateTimeEdit::up-arrow,
76 | QDoubleSpinBox::up-arrow {
77 | image: url(image/right.png);
78 | }
79 | QSpinBox::down-arrow, QTimeEdit::down-arrow,
80 | QDateEdit::down-arrow, QDateTimeEdit::down-arrow,
81 | QDoubleSpinBox::down-arrow {
82 | image: url(image/left.png);
83 | }
84 | /*********定义Slider槽的形态**************/
85 | QSlider::groove:horizontal {
86 | height: 6px;
87 | border-radius: 3px;
88 | }
89 | /*********定义Slider柄的形态**************/
90 | QSlider::handle:horizontal {
91 | background-color: rgb(68, 68, 68);
92 | border-radius: 5px;
93 | border: 2px solid rgb(36, 36, 36);
94 | height: 8px;
95 | width: 8px;
96 | margin: -3px -3px;
97 | }
98 | /*********定义handle右边的背景**************/
99 | QSlider::add-page:horizontal{
100 | background: rgb(72, 72, 72);
101 | }
102 | /*********定义handle左边的背景**************/
103 | QSlider::sub-page:horizontal{
104 | background: rgb(36, 36, 36);
105 | }
106 | /****************定义进度条****************/
107 | QProgressBar {
108 | border: 2px solid grey;
109 | border-radius: 5px;
110 | text-align: center;
111 | height: 12px;
112 | }
113 | /*************定义进度条背景**************/
114 | QProgressBar::chunk {
115 | background-color: rgb(128, 128, 128);
116 | width: 8px;
117 | margin: 0.5px;
118 | }
119 |
120 | QTabWidget::pane { /* The tab widget frame */
121 | border: 1px solid rgb(36, 36, 36);
122 | }
123 | QTabWidget::tab-bar {
124 | border: 1px solid rgb(36, 36, 36);
125 | }
126 | QTabBar::tab {
127 | background: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 rgba(100, 100, 100, 255), stop:1 rgba(82, 82, 82, 255));
128 | border: 1px solid rgb(36, 36, 36);
129 | border-bottom-color: #C2C7CB; /* same as the pane color */
130 | min-width: 12ex;
131 | min-height: 6ex;
132 | padding: 2px 4px;
133 | }
134 | QTabBar::tab:selected, QTabBar::tab:hover {
135 | border-top: 2px solid red;
136 | padding-top: 3px;
137 | background: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 rgba(72, 72, 72, 255), stop:1 rgba(56, 56, 56, 255));
138 | }
139 | /*************QComboBox******************/
140 | QComboBox::drop-down {
141 | subcontrol-origin: padding;
142 | subcontrol-position: top right;
143 | width: 20px;
144 | border: 0px;
145 | }
146 | QComboBox::down-arrow {
147 | image: url(image/down.png);
148 | }
149 | QComboBox::down-arrow:on { /* shift the arrow when popup is open */
150 | top: 1px;
151 | left: 1px;
152 | }
153 | /*********下拉列表的item背景***************/
154 | QComboBox QAbstractItemView {
155 | border: 0px;
156 | selection-background-color: rgb(100, 100, 100);
157 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Yikai Zhang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # auto-buy-Python-tool
2 | 图形界面, 电脑小白也会用, 下载可直接运行! 京东自动购买口罩 实时抢购口罩 工具, 抗击疫情 中国加油!
3 |
4 | `4.0` :fire:
5 |
6 | 点击这里 下载`JDAutoBuy-release.zip`, 解压后可**直接运行!**
7 |
8 | ### 欢迎加星
9 |
10 | > 修复了商品下架后的问题, 更新了交互界面; 修复了可配货商品的判断, 更新了数量调整接口, 更新了是否监控下架商品选项
11 |
12 | :star2:
13 |
14 |
15 |
16 |
17 |
18 | ## 使用指南
19 |
20 | ### :notebook_with_decorative_cover: Tips: 登录一次之后本地会保存登录信息, 重启软件(注意重启之后也行)之后仍然可以记住账号登录信息, 重启之后只需点击"开始监控"就可以登录! 不必重复扫码!
21 |
22 | 运行界面**如下图**:
23 |
24 | 
25 |
26 | Update at 2020-3-2: Continuously monitor goods removed from JD.
27 |
28 | Update at 2020-2-15: quantity can be modified
29 |
30 | ### 填写方式:
31 |
32 | Tips: 软件启动时带有标准填写格式的默认值, 请留意.
33 |
34 | + **输入商品ID:** 比如`URL`为: https://item.jd.com/1835967.html 的商品ID为**1835967**.
35 | + **输入收件地区编码**: 使用Chrome浏览器(如果是其他浏览器请用同样方式打开**开发者工具**)登录京东并访问商品页, 选择派送地址后按`F12`查找`stock?`开头的`URL`讯息, 如下图: 
36 | + **接受讯息邮箱**: 您的接受讯息邮箱.
37 | + **滑动条**: 控制监控时查询的速度(频率).
38 | + **购买数量**: 调整一次购买数量.
39 | + **是否自动忽略下架商品**: 未打勾 - 下架商品上架时立即抢购, 打勾 - 自动忽略下架商品并删除.
40 |
41 |
42 |
43 | 以下选项您可以不看也会使用:
44 |
45 | **扫码登录**: 如果京东手机客户端无法扫码, 请用**微信**扫一扫试一下.
46 |
47 | **开始监控**: 如果**未登录点击此按钮**, 您之前登录成功过则会读取历史登录记录尝试登录, 否则会提示您先扫码登录.
48 |
49 | 以下是有货时购买的界面, 祝您成功.
50 |
51 | 
52 |
53 | 更新后`2.0`运行界面:
54 |
55 | 
56 |
57 | Update at 2020-2-15 20:06:14: 最近比较难抢了, 因为京东一些商品是预约完再抢购, 预约完之后即被放入购物车, 导致监控有货无货较困难. 见谅.
58 |
59 | ---
60 |
61 | ## 以下是控制台版本程序的说明, 如果您使用图形界面版本, 可以不看
62 |
63 | **获取登录*cookie***: Chrome 登录京东后`F12`, 如下图.
64 |
65 | 
66 |
67 |
68 |
69 | **获取商品URL**: 登录并访问商品页, 选择派送地址后查找**stock?**开头的URL讯息, 如下图.
70 |
71 | 
72 |
73 | 请注意商品`URL`需要是您的地址编号, 如TXT文件中`area=`参数.
74 |
75 |
76 |
77 | **以上两步**获得的讯息填入`Please fill out this document.txt`文件.
78 |
79 | ```shell
80 | pip install -r requirements.txt
81 | python JDAutoBuyTool.py
82 | ```
83 |
84 |
85 |
86 | **运行环境**:
87 |
88 | `Windows 10, Python 3`
89 |
90 | **运行示例**:
91 |
92 | 
93 |
94 | ---
95 |
96 | 感谢[cycz](https://github.com/cycz)大佬, 参考了部分代码.
97 |
98 | 心情复杂, 也不懂抢口罩工具能不能帮上忙, **一定能尽快战胜疫情! 中国加油!** :star2:
--------------------------------------------------------------------------------
/assets/1580963259089.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1580963259089.png
--------------------------------------------------------------------------------
/assets/1580963623908.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1580963623908.png
--------------------------------------------------------------------------------
/assets/1581038258274.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1581038258274.png
--------------------------------------------------------------------------------
/assets/1581218076866.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1581218076866.png
--------------------------------------------------------------------------------
/assets/1581218537205.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1581218537205.png
--------------------------------------------------------------------------------
/assets/1581249279630.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1581249279630.png
--------------------------------------------------------------------------------
/assets/1581508416184.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1581508416184.png
--------------------------------------------------------------------------------
/assets/1581508444771.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1581508444771.png
--------------------------------------------------------------------------------
/assets/1581768204211.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1581768204211.png
--------------------------------------------------------------------------------
/assets/1581768213048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1581768213048.png
--------------------------------------------------------------------------------
/assets/1583151909197.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyikaii/auto-buy-Python-tool/5aa039dcefb7efce34f8d8174d4410860fadbf4e/assets/1583151909197.png
--------------------------------------------------------------------------------