forked from shenjianwei/dewuautoputaway
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
1816 lines (1619 loc) · 72.6 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import _thread
import json
import logging
import math
import os
import re
import sys
import threading
import requests
import time
import wx
import wx.adv
import hashlib
import tkinter as tk
from tkinter import Menu, Toplevel, messagebox
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.chrome.options import Options
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pynput.mouse import Button, Controller as c1
# from pynput.keyboard import Key, Controller as c2
# ----------------------------------------------------
# | 编译语句 -- --onefile... 用于影藏的调用鼠标键盘工具
# | pyinstaller auto_putaway.py --onefile --hidden-import=pynput.keyboard._xorg --hidden-import=pynput.mouse._xorg --hidden-import=pynput.keyboard._win32 --hidden-import=pynput.mouse._win32
# ----------------------------------------------------
class App(wx.adv.TaskBarIcon):
"""
config
默认的初始化配置参数
"""
DEBUGER = True
# 参数
appVersions = "得物APP自动化脚本 V0.5.6" # 项目信息
enterDeposit = 0 # 保证金
enterDepositPlenty = True # 保证金是否充足
intervalTime = (10 if DEBUGER else 300) # 执行间隔时间(秒)
token = "" # Token值
url = 'https://stark.dewu.com' # 请求域名
api = url + '/api/v1/h5/biz' # 请求地址
dewuTokenFilePath = "./dewuToken.txt"
root = ""
# 进程相关参数
endThread = False # 进程管理
endCycleThread = False # 循环进程管理
startCycleTasks = False # 是否开始循环任务
startCycle = False # 是否开始循环任务2
autoNum = 0 # 自动循环次数
firstPutaway = False # 是否第一次执行 用于修改价格过低的输入框清除重置操作
firstGetSubNum = True # 是否第一次进订单 第一次,则默认使用最大订单号,作为之后的比较用
orderSubNum = "" # 比对订单号
autoStart = False # 自动开始状态
# 请求相关工具参数
dewuRequestMax = 3 # 请求最大次数
dewuRequestWaitTime = 5 # 请求等待间隔时间
dewuRequestAgainToken = False # 重新获取token机会
# 库存相关参数
saleGoodsList = [] # 库存商品列表
txtParamNum = 3 # 库存规格字段数
# 订单相关
orderList = []
haveUpdate = False # 是否有新订单
everyHaveUpdate = False # 每次是否有新订单
newOrderCount = 0 # 新订单数量
newUpGoodsInfo = "" # 新上架商品信息
firstOrder = False
# 元素
intervalTimeEntry = "" # 间隔时间输入框
intervalTimeSetBtn = "" # 间隔时间 设置按钮
cycleTimes = (5 if DEBUGER else 10) # 循环次数
cycleTimesEntry = "" # 循环次数输入框
cycleTimesSetBtn = "" # 循环次数 设置按钮
tokenEntry = "" # 文本输入框 Token 值
logListBoxDom = "" # 日志 List 列表框
logTextDom = "" # 日志 Text 框
startBtn = "" # 开始 Button 按钮
saleGoodsListText = "" # 库存 Text 框
orderListText = "" # 销售日志 Text 框
# 弹窗
toplevelSetInterval = "" # 设置间隔时间弹窗
topSetIntervalEntry = "" # 设置间隔时间文本输入框 Token 值
# 查看渠道价弹窗
topWatchCurMinPrice = "" # 弹窗
# 浏览器参数
driverSelf = ""
# 托盘参数
ICON = './lib/favicon.ico'
TITLE = '得物App自动上架系统托盘图标'
HAVE_NEW_MSG = False
STOP_FLASH = True
MENU_ID1, MENU_ID2 = wx.NewIdRef(count=2)
# 请求相关参数
TimeOutTime = 15 # 重新请求等待时间
def __init__(self):
super().__init__()
# 设置图标和提示
self.SetIcon(wx.Icon(self.ICON), self.TITLE)
# 绑定菜单项事件
self.Bind(wx.EVT_MENU, self.onShow, id=self.MENU_ID1)
self.Bind(wx.EVT_MENU, self.onExit, id=self.MENU_ID2)
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DCLICK, self.onShow)
self.initLogging() # 初始化日志
self.root = tk.Tk()
self.root.title(self.appVersions)
frame0 = tk.Frame(self.root, relief="ridge")
# 设置填充和布局
frame0.pack(fill="x", ipady=2)
self.userInfo = tk.Label(frame0, text=self.setInfo())
self.userInfo.pack(padx=2, pady=0, side="left", fill="x", expand="no")
# times.pack_forget()
self.intervalTimeEntry = tk.Entry(frame0, width=10, justify='center')
self.intervalTimeEntry.pack(padx=2, pady=0, side="left", fill="x", expand="no")
self.intervalTimeEntry.insert("0", self.intervalTime)
tk.Label(frame0, text="秒/次", anchor="w").pack(padx=2, pady=0, side="left", fill="x", expand="no")
self.intervalTimeSetBtn = tk.Button(frame0, text="设置", height=1,
command=lambda: self.thread_it(App.setInterval, self))
self.intervalTimeSetBtn.pack(padx=2, pady=0, side="left", fill="x", expand="no")
# self.intervalTimeConfirmBtn = tk.Button(frame0, text="确定")
tk.Label(frame0, text="循环次数:", anchor="w").pack(padx=2, pady=0, side="left", fill="x", expand="no")
self.cycleTimesEntry = tk.Entry(frame0, width=10, justify='center')
self.cycleTimesEntry.insert("0", self.cycleTimes)
self.cycleTimesEntry.pack(padx=2, pady=0, side="left", fill="x", expand="no")
tk.Label(frame0, text="次", anchor="w").pack(padx=2, pady=0, side="left", fill="x", expand="no")
self.cycleTimesSetBtn = tk.Button(frame0, text="设置", height=1,
command=lambda: self.thread_it(App.setCycleTimes, self))
self.cycleTimesSetBtn.pack(padx=2, pady=0, side="left", fill="x", expand="no")
# self.cycleTimesConfirmBtn = tk.Button(frame0, text="确定")
# self.userInfo = tk.Label(self.root, text=self.setInfo(), justify="left")
# self.userInfo.pack()
# tk.Label(self.root, text="Passport Token:", anchor="w").pack(side="top", fill="x")
tk.Label(self.root, text="Token说明:用于请求接口时所用到的验证参数。Token获取方式:在登录得物商家后台,请求接口中passporttoken字段的值。", anchor="w",
fg="red").pack(side="top", fill="x")
self.tokenEntry = tk.Entry(self.root)
self.tokenEntry.pack(side="top", fill="x")
frame1 = tk.Frame(self.root, relief="ridge")
# 设置填充和布局
frame1.pack(fill="x", ipady=2)
tk.Label(frame1, text="上架库存:", anchor="w", height=1, width=40).pack(padx=2, pady=0, side="left", fill="x", expand="yes")
tk.Label(frame1, text="价格过低商品日志:", anchor="w", height=1, width=40).pack(padx=2, pady=0, side="left", fill="x", expand="yes")
tk.Label(frame1, text="待发货销售日志:", anchor="w", height=1, width=40).pack(padx=2, pady=0, side="right", fill="x", expand="yes")
frame2 = tk.Frame(self.root, relief="ridge")
# 设置填充和布局
frame2.pack(fill="x", ipady=3)
self.saleGoodsListText = tk.Text(frame2, width=40, height=12) # 43
self.saleGoodsListText.pack(padx=2, pady=0, side="left", fill="x", expand="yes")
self.saleLowGoodsListText = tk.Text(frame2, width=40, height=12) # 47
self.saleLowGoodsListText.pack(padx=2, pady=0, side="left", fill="x", expand="yes")
self.orderListText = tk.Text(frame2, width=40, height=12)
self.orderListText.pack(padx=2, pady=0, side="right", fill="x", expand="yes")
tk.Label(self.root, text="执行日志:", anchor="w").pack(side="top", fill="x")
# self.logListBoxDom = tk.Listbox(self.root)
# self.logListBoxDom.pack(side="top", expand="yes", fill="both")
self.logTextDom = tk.Text(self.root, width=100) # 执行日志文本
self.logTextDom.pack(side="top", expand="yes", fill="both")
# 菜单
menu = Menu(self.root)
menus = Menu(menu, tearoff=0)
menus.add_command(label="设置自动监测间隔时间(秒)", command=lambda: self.thread_it(App.topSetInterval, self))
menu.add_cascade(label="设置", menu=menus)
menu.add_command(label='关于', command=self.about)
self.root.config(menu=menu)
frameBtn = tk.Frame(self.root, relief="ridge")
# 设置填充和布局
frameBtn.pack(fill="x", ipady=10)
self.startBtn = tk.Button(frameBtn, text="开始执行", command=lambda: self.thread_it(App.startTask, self))
self.startBtn.pack(padx=15, pady=10, side="left", fill="both", expand="yes")
# button1.grid(row=0, column=1)
tk.Button(frameBtn, text="结束执行", fg="red", command=lambda: self.thread_it(App.endTask, self)).pack(padx=15, pady=10, side="left", fill="both", expand="yes")
tk.Button(frameBtn, text="获取价格", fg="#2db7f5", command=lambda: self.thread_it(App.watchCurMinPrice, self)).pack(padx=15, pady=10, side="right", fill="both", expand="yes")
if self.DEBUGER:
tk.Button(self.root, text="读取文本数据", command=lambda: self.thread_it(App.test, self, "read")).pack(
side="left")
tk.Button(self.root, text="测试下架", command=lambda: self.thread_it(App.test, self, "down")).pack(side="left")
tk.Button(self.root, text="测试上架和修改价格", command=lambda: self.thread_it(App.test, self, "up")).pack(
side="left")
# tk.Button(self.root, text="测试修改价格", command=lambda: self.thread_it(App.test, self, "update")).pack(side="left")
tk.Button(self.root, text="测试订单", command=lambda: self.thread_it(App.test, self, "order")).pack(side="left")
tk.Button(self.root, text="测试消息提示闪烁", command=lambda: self.thread_it(App.test, self, "test_msg")).pack(side="left")
tk.Button(self.root, text="测试", command=lambda: self.thread_it(App.test, self, "test")).pack(side="left")
# 初始化获得Token
self.textLog("正在初始化程序\n", "info")
self.textLog("开始获取校验Token\n")
self.thread_it(App.getToken, self)
self.root.protocol("WM_DELETE_WINDOW", self.callbackClose)
self.root.mainloop()
def callbackClose(self):
if self.driverSelf != "":
self.driverSelf.close()
wx.Exit()
def endTask(self):
"""
结束任务
:return:
"""
end = messagebox.askokcancel('提示', '要执行此操作吗')
if end:
if self.driverSelf != "":
time.sleep(1)
self.driverSelf.close()
self.autoStart = False # 关闭自动开始标记
self.endThread = True
def startTask(self):
"""
开始任务
:return:
"""
"""初始化任务"""
self.logTextDom.delete('1.0', 'end') # 清空文本日志
self.endThread = False
self.firstOrder = False
self.autoNum = 0
logging.info("[脚本开始]")
cookies = self.getToken()
if cookies:
self.textLog("脚本开始", "info")
self.setStartBtn(False)
# 手动重新开始,订单操作重新获取最大订单号,否则继续沿用之前的订单号(手动可能包含操了库存)
if not self.autoStart: # 手动执行才操作的内容
self.firstGetSubNum = True
"""文本数据读取"""
self.getSaleGoodsList() # 在第一次手动中获取防止再次重新获取文本数据
"""获取保证金"""
self.enterDeposit = self.getMerchantInfo()
"""订单同步操作"""
self.syncOrder()
"""下架操作"""
self.downGoods()
"""上架操作"""
# self.upTask()
"""下架加修改操作(合并上架与下架操作)"""
self.upAndChangeTask()
self.startCycleTasks = True # 标记循环任务开启
self.doWileChangePrice()
else:
# 未获取到Token 取消按钮禁用
self.setStartBtn()
def autoStartTask(self):
self.autoStart = True
self.startTask()
def doWileChangePrice(self):
if not self.startCycle:
while True:
timeSleepStop = self.timeSleep(self.intervalTime)
if timeSleepStop:
break
else:
# 循环重置
if self.autoNum >= self.cycleTimes:
self.autoStartTask()
else:
"""下架加修改操作(合并上架与下架操作)"""
self.upAndChangeTask()
def endThreadIt(self):
self.autoNum = 0
self.setStartBtn()
self.textLog("进程结束", "info")
sys.exit()
def thread_it(self, func, *args):
"""
将函数打包进线程
:param func: 方法(Class.func)
:param args: 参数 (self|*args)
:return:
"""
# 创建
t = threading.Thread(target=func, args=args)
# 守护 !!!
t.setDaemon(True)
# 启动
t.start()
# 阻塞--卡死界面!
# t.join()
def about(self):
"""
关于产品 - 弹窗提示
"""
messagebox.showinfo("关于", self.appVersions)
def setInfo(self):
"""
设置顶部信息
:return: String
"""
return "保证金:" + str(self.enterDeposit) + ";执行间隔:" # "保证金:" + str(self.enterDeposit) + " ;执行间隔 " + str(self.intervalTime) + "秒/次;"
def getToken(self):
"""
获取Token
文本无值从当前目录的dewuToken.txt文件获取token
token 失效则自动打开浏览器获取
:return: token|False
"""
token = self.tokenEntry.get()
if token == "": # 文本中token为空值
try:
with open(self.dewuTokenFilePath, 'r', encoding='UTF-8') as f:
token = f.read()
f.close()
self.tokenEntry.insert("0", token) # 回显token到文本
except:
messagebox.showerror("消息", "读取本地文件" + self.dewuTokenFilePath + "失败:" + str(sys.exc_info()[0]))
return False
else:
with open(self.dewuTokenFilePath, 'w', encoding='UTF-8') as f:
f.write(token)
f.close()
self.token = token
# 请求判断是否有效token
res = self.dewuRequest('get', '/home/merchantInfo', '')
if res:
if res['code'] == 200:
return token
return False
def getSaleGoodsList(self):
"""
文本内容读取
:return: Array
"""
self.textLog("读取数据")
f = self.saleGoodsListText.get("1.0", "end")
f = f.split("\n")
lists = []
for item in f:
if self.endThread:
self.endThreadIt()
items = item.split("\t")
if len(items) >= self.txtParamNum: # 大于3个的可执行
haveGoods = False
for listsItem in lists:
if listsItem[0] == items[0]:
haveGoods = True
self.textLog("存在相同型号商品:" + listsItem[0], "warning")
if haveGoods == False:
for i in range(len(items)): # 排除掉大于3个的数据
if not i < self.txtParamNum:
print(items, i)
del items[self.txtParamNum]
self.textLog(str(items))
items.append(True)
lists.append(items)
if len(lists) <= 0:
self.textLog("读取数据失败,请检查上架库存文本信息", "error")
self.setStartBtn()
self.endThreadIt() # 返回结束进程
self.saleGoodsList = lists
def authLogin(self):
"""
打开浏览器登录获取token
:return: token|""
"""
mouse = c1()
url = self.url + '/business/login.html'
options = Options()
options.binary_location = "./lib/brower/chrome.exe" # 指定浏览器位置
if not os.path.isfile(options.binary_location):
messagebox.showerror("警告", "浏览器获取失败,请将浏览器放入当前目录 lib/brower 目录中")
# 设置浏览器初始 位置x,y & 宽高x,y
# 不加载图片,加快访问速度
options.add_experimental_option(
"prefs", {"profile.managed_default_content_settings.images": 2})
# 设置中文
options.add_argument('lang=zh_CN.UTF-8')
# 关闭自动测试状态显示
# 此步骤很重要,设置为开发者模式,防止被各大网站识别出来使用了Selenium
options.add_experimental_option(
'excludeSwitches', ['enable-automation'])
# 关闭开发者模式
options.add_experimental_option("useAutomationExtension", False)
# 添加本地代理
# options.add_argument("--proxy--server=127.0.0.1:8080")
# 添加UA
ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
options.add_argument('user-agent=' + ua)
chromedriver = "./lib/chromedriver.exe"
if not os.path.isfile(chromedriver):
messagebox.showerror("警告", "缺少浏览器 chromedriver.exe 驱动,请将驱动放置在 lib 目录中")
self.driverSelf = driver = webdriver.Chrome(executable_path=chromedriver, options=options)
# 通过浏览器的dev_tool在get页面钱将.webdriver属性改为"undefined"
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """Object.defineProperty(navigator, 'webdriver', {get: () => undefined})""",
})
driver.maximize_window()
WebDriverWait(driver, 10)
driver.get(url)
while True:
try:
if self.endThread:
driver.close()
self.endThreadIt()
time.sleep(2)
mouse.position = (1450, 370)
mouse.press(Button.left)
mouse.move(1450, 373)
time.sleep(2)
mouse.release(Button.left)
WebDriverWait(driver, 5, 0.5).until(
EC.presence_of_element_located((By.CLASS_NAME, 'nc-lang-cnt')))
if driver.find_element_by_class_name('nc-lang-cnt').text == '验证通过':
break
else:
driver.refresh()
except WebDriverException as e:
#
self.endThreadIt()
break
time.sleep(1)
driver.find_element_by_xpath(
'//*[@placeholder="请输入手机号码"]').send_keys(15355979998)
driver.find_element_by_xpath(
'//*[@placeholder="请输入密码"]').send_keys('jeefNO1')
driver.find_element_by_class_name('el-button').click()
getCount = 0
getCookies = False
token = ""
while getCount < 10:
time.sleep(1)
getCount += 1
cookies = driver.get_cookies()
token = self.getJsonToken(cookies, "mchToken")
if token != "":
getCount = 10
getCookies = True
if not getCookies:
self.textLog("Token获取失败请重新尝试", "error")
time.sleep(1)
driver.close()
self.endThreadIt()
time.sleep(1)
driver.close() # 获取cookies便可以关闭浏览器
self.driverSelf = ""
# 获取cookies中的token
self.openFile(self.dewuTokenFilePath, 'w', token) # 写入token文件中
self.tokenEntry.delete("0", "end")
self.tokenEntry.insert("0", token) # 写入输入框展示
return token
def getJsonToken(self, tokenJson, tokenKey):
"""
获取json中的token值
:param tokenJson: Json参数
:param tokenKey: 键
:return: token|""
"""
# tokenJson = json.loads(tokenJson)
tokenValue = ""
for jsonItem in tokenJson:
if jsonItem['name'] == tokenKey:
tokenValue = jsonItem['value']
return tokenValue
def watchCurMinPrice(self):
"""
查询渠道最低价格弹窗
:return:
"""
def getPriceText():
"""
查询渠道价格,并输出至页面
:return:
"""
model = priceText.get("1.0", "end")
model = model.split("\n")
print(model)
priceText.delete("1.0", "end")
priceText.insert("end", "获取中,请稍等...")
first = False
for item in model:
print(item)
time.sleep(1)
item = item.split("\t")
itemModel = item[0]
if "型号" in itemModel or "获取完成。" in itemModel:
continue
if len(item[0]) > 1:
if not first:
priceText.delete("1.0", "end")
priceText.insert("end", "型号\t渠道最低价\t销量\t标题\n")
first = True
# 最低价查询
searchGoods = self.searchGoods(itemModel)
if len(searchGoods) > 0:
# 商品信息查询
goodsInfo = self.appSearchGoods(searchGoods[0]["articleNumber"], 0)
goodsText = ""
if goodsInfo:
goodsText = str(goodsInfo["soldNum"]) + "\t" + goodsInfo["title"]
spuId = searchGoods[0]["spuId"]
goodsDetail = self.getGoodsDetail(spuId)
if "minPriceList" in goodsDetail:
minPrice = goodsDetail["minPriceList"][0] # 接口获取列表,可能会存在多个最低价
priceText.insert("end", itemModel + "\t" + str(int(minPrice["curMinPrice"] / 100)) + "\t" + goodsText + "\n")
else:
priceText.insert("end", itemModel + "\t" + "暂无人上架\t" + goodsText + "\n")
else:
priceText.insert("end", itemModel + "\t" + "未查询到商品\n")
priceText.see("end")
priceText.insert("end", "获取完成。")
self.topWatchCurMinPrice = top = Toplevel()
top.title("查看渠道最低价格")
self.endThread = True
tk.Label(top, text="价格获取框", anchor="w", justify="left").pack(fill="both", expand="no")
priceText = tk.Text(top)
priceText.pack(fill="both", expand="yes")
tk.Button(top, text="获取价格", width=30, height=2, command=lambda: self.thread_it(getPriceText)).pack()
def topSetInterval(self):
"""
时间设置弹窗
:return:
"""
self.toplevelSetInterval = top = Toplevel()
top.title("设置执行时间间隔(秒):")
top.geometry("400x30")
tk.Label(top, text="请输入设置执行时间间隔:", width=15).pack(side="left", expand="yes", fill="both")
self.topSetIntervalEntry = tk.Entry(top, width=10, justify="center")
self.topSetIntervalEntry.pack(side="left", expand="yes", fill="both")
tk.Label(top, text="秒", width=1, justify="left").pack(side="left", expand="yes", fill="both")
tk.Button(top, text="确定", command=lambda: self.thread_it(App.setInterval, self)).pack(side="right",
expand="yes", fill="both")
def setInterval(self):
"""
设置自动执行间隔时间
:return:
"""
def setInl(intervalTime):
self.intervalTime = int(intervalTime) # 获取输入值
# self.userInfo.configure(text=self.setInfo()) # 设置信息输出内容
messagebox.showinfo("消息", "设置成功,将在下一次执行生效")
if self.startCycleTasks: # 标记任务在执行时,立马结束上个进程,等待下个进程重新进入
""" 间隔时间操作 """
"""主要解决定时30分钟的时候,能够修改为此次等待时间"""
self.endCycleThread = True # 结束上一个循环
time.sleep(3)
self.endCycleThread = False
self.doWileChangePrice()
if self.topSetIntervalEntry != "":
self.toplevelSetInterval.destroy() # 关闭弹窗
setInl(self.topSetIntervalEntry.get())
elif self.intervalTimeEntry != "":
setInl(self.intervalTimeEntry.get())
def setCycleTimes(self):
self.cycleTimes = int(self.cycleTimesEntry.get())
messagebox.showinfo("消息", "设置成功")
def calculateFollowPrice(self, price, goodsDetail):
price = int(price)
curMinPrice = 0
addPrice = price
while curMinPrice < price:
addPrice += 1
poundagePrice = self.poundagePrice(addPrice, goodsDetail["poundageInfoList"][0]["poundageDetailInfoList"])
curMinPrice = addPrice - poundagePrice
print(addPrice, curMinPrice)
return [addPrice, curMinPrice]
# def upTask(self):
# """
# 上架任务
# :return:
# """
# # 上架商品操作
# # TODO 上架添加查询是否有上架操作
# self.textLog("======================================上架操作======================================", "sub")
# self.textLog("开始上架操作\n")
# for item in self.saleGoodsList:
# if self.endThread:
# self.endThreadIt()
# no = str(item[0])
# # 搜索货号,判断有无相关商品
# self.textLog("查询商品:货号:" + no)
# goods = self.searchGoods(item[0])
# if len(goods) > 0:
# spuId = goods[0]["spuId"]
# # 查询商品详情
# if not self.enterDepositPlenty: # 保证金不足跳出循环
# break
# self.upGoods(spuId, item, no)
# else:
# self.textLog("未查询到商品,跳过出价\n", "error")
# time.sleep(1)
#
# self.firstPutaway = False
# self.textLog("======================================上架操作结束======================================", "sub")
def upAndChangeTask(self):
"""
上架&更新任务
逻辑:
下架完后操作该方法 -> 读取文本库存 -> 查询商品是否有上架 -有-> 修改操作 -> 先下架当前商品,再重新上架商品(保证金问题)
-无-> 上架操作
:return:
"""
# 初始化
self.startCycle = True
if self.autoNum > 0:
self.logTextDom.delete("1.0", "end") # 每次循环清空文本
self.firstPutaway = False
self.autoNum += 1
self.textLog("======================================第 " + str(
self.autoNum) + " 次循环======================================", "sub")
self.textLog("======================================出价和改价操作======================================", "sub")
notUpdateCount = 0 # 未更新数量
updateCount = 0 # 更新数量
successCount = 0 # 更新成功数量
errorCount = 0 # 更新失败数量
upCount = 0 # 更新失败数量
for item in self.saleGoodsList: # 循环库存
if self.endThread:
self.endThreadIt()
goodsNo = str(item[0]) # 库存商品型号
# goodsCount = int(item[1]) # 库存商品数量
goodsPrice = int(item[2]) # 库存商品价格
haveGoods = item[3] # 是否存在商品
binddingGoods = [] # 上架商品信息
biddingList = self.getGoodsList() # 查询上架商品
for listItem in biddingList: # 循环筛选出已上架商品
if listItem['articleNumber'] == goodsNo:
binddingGoods = listItem
# 改价前检查订单
# TODO 不做修改 提示专用 !!!!!!
self.updateOrder()
if binddingGoods:
"""修改已经上架的商品"""
# 商品详情
spuId = binddingGoods["spuId"]
goodsDetail = self.getGoodsDetail(spuId)
if not ("poundageInfoList" in goodsDetail):
self.textLog("查询接口失败:取消商品[" + goodsNo + "]价格更新\n", "error")
else:
self.textLog("开始更新价格:货号:" + goodsNo)
# 价格计算
updateSalePrice = self.updateSalePrice(goodsPrice, binddingGoods['curMinPrice'],
binddingGoods['myMaxPrice'], goodsDetail)
uspStatus = updateSalePrice[0] # 价格计算状态
uspSalePrice = updateSalePrice[1] # 出价
uspPrice = updateSalePrice[2] # 库存价 0:接口欧问题
if uspStatus: # 计算价格返回true 或 与当前你最低价不同
if int(uspSalePrice) == int(binddingGoods['curMinPrice']): # 出价 与 当前最低价
notUpdateCount += 1
self.textLog("价格相同,不作更新\n", "warning")
else:
# 修改价格
# # TODO 打包时打开
# 下架再重新上架
# 下架
detailItem = goodsDetail["detailResponseList"][0]
self.deleteGoods(
detailItem['remainQuantity'], detailItem['price'], detailItem['biddingNo'],
detailItem['skuId'])
# 上架 下架后最低价变动,需重新获取最低价
upGoods = self.upGoods(spuId, item, goodsNo, True)
# update = self.addGoods(uspSalePrice, 1, size, goodsNo)
#
# if upGoods == True: # 必须 == True,数组[...]判断也是True
# successCount += 1
# self.textLog("更新价格成功", "success")
# # biddingNo, minPrice
# followPrice = self.calculateFollowPrice(goodsPrice, goodsDetail)
# self.textLog("计算跟价价格:" + str(followPrice[0]))
# setFollow = self.setFollowPrice(detailItem["biddingNo"], int(followPrice[0] * 100))
# if setFollow:
# self.textLog("设置自动跟价成功,跟价价格:" + str(followPrice[0]) + "除去手续费到手价:" + str(followPrice[1]) + "\n", "success")
# else:
# self.textLog("设置自动跟价失败\n", "error")
else:
notUpdateCount += 1
if uspPrice == 0:
self.textLog("不作更新\n", "warning")
else:
self.textLog("取消更新:价格过低" + "\n", "error")
text = ""
if not self.firstPutaway:
self.saleLowGoodsListText.delete('1.0', "end")
self.firstPutaway = True
text += "型号\t数量\t库存价格\t渠道最低价\t实际到手价\t上架时间\n"
text += goodsNo + "\t1\t" + str(goodsPrice) + "\t" + str(
int(int(binddingGoods["curMinPrice"]) / 100)) + "\t" + str(uspPrice) + "\t[" + str(
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "]\n"
self.saleLowGoodsListText.insert("end", text)
updateCount += 1
else:
"""与仓库商品未查询到,操作上架"""
# 搜索货号,判断有无相关商品
if self.enterDepositPlenty: # 保证金不足跳出循环
if haveGoods: # 默认True有商品
self.textLog("查询商品:货号:" + goodsNo)
goods = self.searchGoods(goodsNo)
if len(goods) > 0:
spuId = goods[0]["spuId"]
# 查询商品详情
upGoods = self.upGoods(spuId, item, goodsNo)
if upGoods:
upCount += 1
else:
item[3] = False # 修改商品存在字段
self.textLog("未查询到商品,跳过出价\n", "error")
self.setSaleGoodsText(goodsNo, ["无商品"])
# 统计订单操作,由于每次改价查询都要操作检查订单
if not self.haveUpdate:
self.textLog("无新增库存相关商品订单")
else:
self.textLog("新增订单:" + str(self.newOrderCount) + " 单")
self.textLog(self.newUpGoodsInfo)
self.textLog("")
# 每1分钟执行一次
self.textLog("======================================第 " + str(
self.autoNum) + " 次循环结束======================================\n", "sub")
self.startCycle = False
def orderTask(self):
"""
订单任务
逻辑:
查询订单,设置最大订单号(用于比较) -> 循环进入新订单 -> 比对订单号 -大于-> 修改文本库存中对应的商品库存 -> 还有库存则再上架一个
:return:
"""
self.updateOrder()
def downGoods(self):
"""
下架操作
:return:
"""
"""下架操作"""
self.textLog("======================================全部下架操作======================================", "sub")
biddingList = self.getGoodsList(True)
def down(item):
self.textLog("下架:" + str(item['articleNumber']))
detailLists = self.getGoodsDetail(item['spuId'])
if self.endThread:
self.endThreadIt()
# 必须全部下架操作
if "detailResponseList" in detailLists:
for detailItem in detailLists["detailResponseList"]:
if "remainQuantity" in detailItem and "price" in detailItem and "biddingNo" in detailItem and "skuId" in detailItem:
self.deleteGoods(
detailItem['remainQuantity'], detailItem['price'], detailItem['biddingNo'],
detailItem['skuId'])
else:
self.textLog("下架参数错误,请重新尝试", "error")
self.endThreadIt()
# 循环获取上架商品列表,下架所有商品操作
for item in biddingList:
# TODO 打包时删除
if self.DEBUGER:
for saleItem in self.saleGoodsList:
if item['articleNumber'] == saleItem[0]:
print(item['articleNumber'])
down(item)
else:
down(item)
if not self.DEBUGER:
time.sleep(1)
self.textLog("======================================全部下架操作结束======================================\n", "sub")
def upGoods(self, spuId, item, no, update=False):
"""
上架操作
:param spuId: 商品ID
:param item: 库存信息
:param no: 型号
:param update:
:return:
"""
counts = item[1]
if int(counts) <= 0:
self.textLog("上架失败:库存不足\n", "error")
return False
else:
price = item[2] # 库存价
goodsDetail = self.getGoodsDetail(spuId)
if "detailResponseList" in goodsDetail: # 判断商品是否已经有货出价中
if len(goodsDetail["detailResponseList"]) > 0:
self.textLog("已在出价\n", "warning")
return True
if not ("poundageInfoList" in goodsDetail):
self.textLog("查询接口失败:取消商品[" + no + "]上架\n", "error")
else:
minPrice = 0 # 最低出价
if "minPriceList" in goodsDetail and len(goodsDetail["minPriceList"]) > 0 and "curMinPrice" in goodsDetail["minPriceList"][0]: # 检测是否有同款商品在售
minPrice = goodsDetail["minPriceList"][0]['curMinPrice']
salePrice = self.updateSalePrice(price, minPrice, 0, goodsDetail) # 上架价格计算
else:
# 上架 计算其他商户为上价的价格
salePrice = self.addSalePrice(price, goodsDetail)
self.textLog(
"最低价:" + str(int(minPrice / 100)) + "元;出价:" + str(int(salePrice[1] / 100)) + "元(实际到手价:" + str(
salePrice[2]) + "元);库存价:" + str(price) + "元")
if salePrice[0]:
# # TODO 打包时打开
# 查询规格
size = self.sizeGoods(spuId)
if len(size) > 0:
# TODO 多规格需要做弹窗选择,记录第一次的选择,后续自动记录
skuId = size[0]["skuId"]
else:
skuId = size[0]["skuId"]
# 上架商品
# self.textLog("上架商品:货号:" + no)
add = self.addGoods(salePrice[1], 1, skuId, no)
if add == True:
self.textLog(("改价成功" if update else "上架成功"), "success")
goodsDetail = self.getGoodsDetail(spuId)
# 设置自动跟价
if "detailResponseList" in goodsDetail and len(goodsDetail["detailResponseList"]) > 0 and "poundageInfoList" in goodsDetail and len(goodsDetail["poundageInfoList"]) > 0 and "poundageDetailInfoList" in goodsDetail["poundageInfoList"][0]: # goodsDetail["poundageInfoList"][0]["poundageDetailInfoList"]
followPrice = self.calculateFollowPrice(price, goodsDetail)
self.textLog("计算跟价价格:" + str(followPrice[0]))
setFollow = self.setFollowPrice(goodsDetail["detailResponseList"][0]["biddingNo"], int(followPrice[0] * 100))
if setFollow:
self.textLog("设置自动跟价成功,跟价价格:" + str(followPrice[0]) + "除去手续费到手价:" + str(followPrice[1]) + "\n", "success")
return True
else:
self.textLog("设置自动跟价失败\n", "error")
return False
else:
self.textLog("设置自动跟价失败, 未找到必须参数\n", "error")
return False
else:
self.textLog(("改价失败:" if update else "上架失败:") + add["msg"] + "\n", "error")
return False
else:
if salePrice[2] == 0:
self.textLog(("取消改价:" if update else "取消上架:") + salePrice[3] + "\n", "error")
return False
else:
self.textLog(("取消改价:价格过低" if update else "取消上架:价格过低") + "\n", "error")
text = ""
if not self.firstPutaway:
self.saleLowGoodsListText.delete('1.0', "end")
self.firstPutaway = True
text += "型号\t数量\t库存价格\t渠道最低价\t实际到手价\t上架时间\n"
# text += "上架时间:" + time.strftime("%Y-%m-%d %H:%M:%S %p", time.localtime()) + "\n"
text += no + "\t1\t" + str(price) + "\t" + str(int(minPrice / 100)) + "\t" + str(
salePrice[2]) + "\t[" + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "]\n"
self.saleLowGoodsListText.insert("end", text)
return False
def syncOrder(self):
"""
同步订单
:return:
"""
self.textLog("初始化订单")
biddingList = self.getOrders()
if len(biddingList) > 0:
for item in biddingList:
if not item["subOrderNo"] in self.orderList:
self.orderList.append(item["subOrderNo"]) # 存入订单数组
def updateOrder(self):
"""
订单操作
他能买,有订单,说明某件商品他上架过
仓库商品是否有库存
从最新的一笔订单,开始等待下一笔订单
:return:
"""
# self.textLog("======================================订单操作======================================", "sub")
orderList = self.getOrders() # 获取订单列表,倒序排列
self.everyHaveUpdate = False
if len(orderList) > 0: # 有订单
for orderItem in orderList:
if not orderItem["subOrderNo"] in self.orderList: # 在订单中不执行,说明已经执行过了
self.newOrderCount += 1 # 计算新订单总数
self.orderList.append(orderItem["subOrderNo"]) # 记录到库存中
for saleItem in self.saleGoodsList: # 查找匹配的库存商品
if saleItem[0] == orderItem['articleNumber']:
count = int(saleItem[1])
if count > 0: # 有库存
count -= 1
self.everyHaveUpdate = True
self.newUpGoodsInfo += "新上架:[货号:" + saleItem[0] + ";库存(余):" + str(count) + " (" + saleItem[1] + "-1)]\n"
if not self.firstOrder:
self.firstOrder = True
self.orderListText.insert("end", "货号\t数量\t库存价格\t到手价格\t下单时间\n")
self.orderListText.insert("end", str(saleItem[0]) + "\t1\t" + str(saleItem[2]) + "\t" + str(orderItem["actualAmount"] / 100) + "\t[" + str(orderItem["payTime"]) + "]\n")
saleItem[1] = str(count) # 修改库存
# 修改文本库存显示
txt = ""
# 重新拼接文本
for txtItem in self.saleGoodsList:
txt += txtItem[0] + "\t" + txtItem[1] + "\t" + txtItem[2] + "\n"
self.saleGoodsListText.delete('1.0', 'end')
self.saleGoodsListText.insert('end', txt) # 文本写入
try:
_thread.start_new_thread(self.newMessage, ()) # 打开闪烁消息提示
except:
print("Error: 无法启动 [ 打开闪烁消息提示 ] 线程")
self.haveUpdate = True
# else:
# self.textLog("库存不足:货号:" + saleItem[0])