qrpa 1.0.83__tar.gz → 1.0.84__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of qrpa might be problematic. Click here for more details.
- {qrpa-1.0.83 → qrpa-1.0.84}/PKG-INFO +1 -1
- {qrpa-1.0.83 → qrpa-1.0.84}/pyproject.toml +1 -1
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/shein_lib.py +203 -66
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa.egg-info/PKG-INFO +1 -1
- {qrpa-1.0.83 → qrpa-1.0.84}/README.md +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/RateLimitedSender.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/__init__.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/db_migrator.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/feishu_bot_app.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/feishu_client.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/feishu_logic.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/fun_base.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/fun_excel.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/fun_file.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/fun_web.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/fun_win.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/mysql_module/__init__.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/mysql_module/shein_product_model.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/mysql_module/shein_return_order_model.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/shein_daily_report_model.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/shein_excel.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/shein_mysql.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/shein_sqlite.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/shein_ziniao.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/temu_chrome.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/temu_excel.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/temu_lib.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/time_utils.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/time_utils_example.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa/wxwork.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa.egg-info/SOURCES.txt +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa.egg-info/dependency_links.txt +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/qrpa.egg-info/top_level.txt +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/setup.cfg +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/setup.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/tests/test_db_migrator.py +0 -0
- {qrpa-1.0.83 → qrpa-1.0.84}/tests/test_wxwork.py +0 -0
|
@@ -27,31 +27,37 @@ class SheinLib:
|
|
|
27
27
|
|
|
28
28
|
self.deal_auth()
|
|
29
29
|
|
|
30
|
-
# 处理鉴权
|
|
30
|
+
# 处理鉴权 - 优化版本
|
|
31
31
|
def deal_auth(self):
|
|
32
32
|
web_page = self.web_page
|
|
33
33
|
|
|
34
34
|
# 定义最大重试次数
|
|
35
|
-
MAX_RETRIES =
|
|
35
|
+
MAX_RETRIES = 3 # 减少重试次数,避免过度重试
|
|
36
36
|
retries = 0
|
|
37
37
|
wait_count = 0
|
|
38
38
|
is_send = False
|
|
39
39
|
|
|
40
|
-
# close_modal(web_page) # 不能开启 需要勾选协议弹窗
|
|
41
|
-
|
|
42
40
|
while retries < MAX_RETRIES:
|
|
43
41
|
try:
|
|
42
|
+
# 检查页面是否已崩溃,如果是则重新创建页面
|
|
43
|
+
if self.is_page_crashed():
|
|
44
|
+
self.recreate_page()
|
|
45
|
+
web_page = self.web_page
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
# 等待页面稳定加载
|
|
48
|
+
self.wait_for_page_stable()
|
|
46
49
|
|
|
47
50
|
while not web_page.locator('//div[contains(text(),"商家后台")]').nth(1).is_visible():
|
|
48
51
|
|
|
52
|
+
# 处理鉴权确定按钮
|
|
49
53
|
if web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).is_visible():
|
|
50
54
|
if 'https://sso.geiwohuo.com/#/home' not in web_page.url:
|
|
51
55
|
log("鉴权确定按钮可见 点击'确定'按钮", web_page.title(), web_page.url, self.store_username, self.store_name)
|
|
52
56
|
web_page.locator('xpath=//div[@id="container" and @alita-name="gmpsso"]//button[@type="button" and @id]').nth(0).click()
|
|
53
|
-
|
|
57
|
+
# 等待导航完成
|
|
58
|
+
self.wait_for_navigation()
|
|
54
59
|
|
|
60
|
+
# 处理验证码
|
|
55
61
|
while web_page.locator('//div[text()="验证码"]').is_visible():
|
|
56
62
|
log(f'等待输入验证码: {wait_count}', self.store_username, self.store_name)
|
|
57
63
|
if not is_send:
|
|
@@ -62,92 +68,223 @@ class SheinLib:
|
|
|
62
68
|
time.sleep(5)
|
|
63
69
|
wait_count += 1
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
log('等待协议内容出现')
|
|
68
|
-
web_page.wait_for_timeout(1000)
|
|
69
|
-
|
|
70
|
-
if web_page.locator('//div[contains(text(),"同意签署协议")]').count() > 0:
|
|
71
|
-
log('检测到同意签署协议')
|
|
72
|
-
web_page.wait_for_timeout(1000)
|
|
73
|
-
log('点击同意复选框')
|
|
74
|
-
web_page.locator('//i[@class="so-checkinput-indicator so-checkinput-checkbox"]').click()
|
|
75
|
-
web_page.wait_for_timeout(1000)
|
|
76
|
-
log('点击同意按钮')
|
|
77
|
-
web_page.locator('//button[span[text()="同意"]]').click()
|
|
71
|
+
# 处理协议签署
|
|
72
|
+
self.handle_agreement()
|
|
78
73
|
|
|
74
|
+
# 处理登录按钮
|
|
79
75
|
if web_page.locator('//input[@name="username"]').is_visible():
|
|
80
76
|
log("用户名输入框可见 等待5秒点击'登录'按钮", self.store_username, self.store_name)
|
|
81
77
|
web_page.wait_for_timeout(5000)
|
|
82
78
|
log('点击"登录"', self.store_username, self.store_name)
|
|
83
79
|
web_page.locator('//button[contains(@class,"login_btn")]').click()
|
|
80
|
+
# 等待登录完成
|
|
81
|
+
self.wait_for_navigation()
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
web_page.wait_for_timeout(5000)
|
|
87
|
-
|
|
83
|
+
# 检查是否已成功进入商品管理
|
|
88
84
|
if web_page.locator('//span[contains(text(),"商品管理")]').nth(1).is_visible():
|
|
89
85
|
log('商品管理菜单可见 退出鉴权处理', self.store_username, self.store_name)
|
|
90
86
|
return
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
web_page.wait_for_timeout(5000)
|
|
95
|
-
web_page.reload()
|
|
96
|
-
|
|
97
|
-
# while r'=/CN' in web_page.url:
|
|
98
|
-
# safe_goto(web_page, 'https://sso.geiwohuo.com/#/home?q=0')
|
|
99
|
-
#
|
|
100
|
-
# web_page.wait_for_timeout(5000)
|
|
101
|
-
# if web_page.locator('//input[@name="username"]').is_visible():
|
|
102
|
-
# log("用户名输入框可见 等待5秒点击'登录'按钮", self.store_username, self.store_name)
|
|
103
|
-
# web_page.wait_for_timeout(5000)
|
|
104
|
-
# log('点击"登录"', self.store_username, self.store_name)
|
|
105
|
-
# web_page.locator('//button[contains(@class,"login_btn")]').click()
|
|
106
|
-
#
|
|
107
|
-
# log('再延时5秒', self.store_username, self.store_name)
|
|
108
|
-
# web_page.wait_for_timeout(5000)
|
|
88
|
+
# 处理各种页面状态
|
|
89
|
+
self.handle_page_states()
|
|
109
90
|
|
|
110
91
|
web_page.wait_for_timeout(3000)
|
|
111
92
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
log(web_page.title(), '中断循环', self.store_username, self.store_name)
|
|
116
|
-
web_page.wait_for_timeout(5000)
|
|
117
|
-
break
|
|
118
|
-
|
|
119
|
-
if 'mrs.biz.sheincorp.cn' in web_page.url and '商家后台' in web_page.title():
|
|
120
|
-
safe_goto(web_page, 'https://sso.geiwohuo.com/#/home?q=1')
|
|
121
|
-
web_page.wait_for_timeout(5000)
|
|
122
|
-
|
|
123
|
-
if web_page.locator('//h1[contains(text(),"鉴权")]').is_visible():
|
|
124
|
-
log('检测到鉴权 刷新页面', self.store_username, self.store_name)
|
|
125
|
-
web_page.reload()
|
|
126
|
-
web_page.wait_for_timeout(5000)
|
|
127
|
-
web_page.reload()
|
|
128
|
-
web_page.wait_for_timeout(5000)
|
|
129
|
-
|
|
130
|
-
if web_page.title() == 'SHEIN':
|
|
131
|
-
safe_goto(web_page, 'https://sso.geiwohuo.com/#/home?q=2')
|
|
132
|
-
web_page.wait_for_timeout(5000)
|
|
93
|
+
# 检查成功条件
|
|
94
|
+
if self.is_auth_successful():
|
|
95
|
+
break
|
|
133
96
|
|
|
134
97
|
break
|
|
98
|
+
|
|
135
99
|
except Exception as e:
|
|
136
100
|
log(f"错误发生: {e}, 重试中...({self.store_username}, {self.store_name})")
|
|
137
101
|
log(traceback.format_exc())
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
102
|
+
|
|
103
|
+
# 特殊处理页面崩溃错误
|
|
104
|
+
if 'crashed' in str(e):
|
|
105
|
+
log("检测到页面崩溃,尝试恢复...")
|
|
106
|
+
self.handle_page_crash()
|
|
107
|
+
|
|
141
108
|
retries += 1
|
|
142
109
|
if retries >= MAX_RETRIES:
|
|
143
110
|
log(f"达到最大重试次数,停止尝试({self.store_username}, {self.store_name})")
|
|
144
|
-
|
|
145
|
-
|
|
111
|
+
raise e # 重新抛出异常以便上层处理
|
|
112
|
+
|
|
113
|
+
# 指数退避等待
|
|
114
|
+
time.sleep(2 ** retries)
|
|
115
|
+
|
|
116
|
+
# 辅助方法
|
|
117
|
+
def is_page_crashed(self):
|
|
118
|
+
"""检查页面是否已崩溃"""
|
|
119
|
+
try:
|
|
120
|
+
# 尝试访问页面基本属性
|
|
121
|
+
self.web_page.url
|
|
122
|
+
self.web_page.title()
|
|
123
|
+
return False
|
|
124
|
+
except Exception as e:
|
|
125
|
+
if 'crashed' in str(e):
|
|
126
|
+
return True
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
def recreate_page(self):
|
|
130
|
+
"""重新创建页面实例"""
|
|
131
|
+
try:
|
|
132
|
+
# 关闭当前页面
|
|
133
|
+
if hasattr(self, 'web_page') and self.web_page:
|
|
134
|
+
try:
|
|
135
|
+
self.web_page.close()
|
|
136
|
+
except:
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
# 重新创建页面
|
|
140
|
+
self.web_page = self.browser.new_page()
|
|
141
|
+
|
|
142
|
+
# 设置页面选项
|
|
143
|
+
self.web_page.set_default_timeout(30000) # 30秒超时
|
|
144
|
+
self.web_page.set_default_navigation_timeout(30000)
|
|
145
|
+
|
|
146
|
+
# 导航到目标页面
|
|
147
|
+
self.safe_goto('https://sso.geiwohuo.com/#/home')
|
|
146
148
|
|
|
149
|
+
except Exception as e:
|
|
150
|
+
log(f"重新创建页面失败: {e}")
|
|
151
|
+
raise
|
|
152
|
+
|
|
153
|
+
def wait_for_page_stable(self, timeout=10000):
|
|
154
|
+
"""等待页面稳定加载"""
|
|
155
|
+
try:
|
|
156
|
+
# 等待网络空闲
|
|
157
|
+
self.web_page.wait_for_load_state('networkidle', timeout=timeout)
|
|
158
|
+
# 额外等待一下确保JavaScript执行完成
|
|
159
|
+
self.web_page.wait_for_timeout(2000)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
log(f"等待页面稳定超时: {e}")
|
|
162
|
+
|
|
163
|
+
def wait_for_navigation(self, timeout=30000):
|
|
164
|
+
"""等待导航完成"""
|
|
165
|
+
try:
|
|
166
|
+
self.web_page.wait_for_load_state('networkidle', timeout=timeout)
|
|
167
|
+
self.web_page.wait_for_timeout(2000)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
log(f"等待导航完成超时: {e}")
|
|
170
|
+
|
|
171
|
+
def safe_goto(self, url, retries=3):
|
|
172
|
+
"""安全的页面导航"""
|
|
173
|
+
for i in range(retries):
|
|
174
|
+
try:
|
|
175
|
+
# 设置较长的超时时间处理302重定向
|
|
176
|
+
response = self.web_page.goto(url, wait_until='networkidle', timeout=60000)
|
|
177
|
+
|
|
178
|
+
# 检查响应状态
|
|
179
|
+
if response and response.status >= 400:
|
|
180
|
+
log(f"页面返回错误状态: {response.status}")
|
|
181
|
+
if i < retries - 1:
|
|
182
|
+
time.sleep(2)
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
# 等待页面稳定
|
|
186
|
+
self.wait_for_page_stable()
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
log(f"导航到 {url} 失败 (尝试 {i + 1}/{retries}): {e}")
|
|
191
|
+
if 'crashed' in str(e):
|
|
192
|
+
# 页面崩溃,需要重新创建
|
|
193
|
+
if i < retries - 1:
|
|
194
|
+
self.recreate_page()
|
|
195
|
+
continue
|
|
196
|
+
else:
|
|
197
|
+
raise
|
|
198
|
+
|
|
199
|
+
if i < retries - 1:
|
|
200
|
+
time.sleep(2 ** i) # 指数退避
|
|
201
|
+
continue
|
|
202
|
+
raise
|
|
203
|
+
|
|
204
|
+
def handle_agreement(self):
|
|
205
|
+
"""处理协议签署"""
|
|
206
|
+
if self.web_page.locator('//div[contains(text(),"同意签署协议")]').count() > 0:
|
|
207
|
+
# 等待协议内容完全加载
|
|
208
|
+
self.web_page.wait_for_selector('//div[contains(text(),"同意签署协议")]', timeout=10000)
|
|
209
|
+
log('检测到同意签署协议')
|
|
210
|
+
self.web_page.wait_for_timeout(1000)
|
|
211
|
+
log('点击同意复选框')
|
|
212
|
+
self.web_page.locator('//i[@class="so-checkinput-indicator so-checkinput-checkbox"]').click()
|
|
213
|
+
self.web_page.wait_for_timeout(1000)
|
|
214
|
+
log('点击同意按钮')
|
|
215
|
+
self.web_page.locator('//button[span[text()="同意"]]').click()
|
|
216
|
+
# 等待处理完成
|
|
217
|
+
self.wait_for_navigation()
|
|
218
|
+
|
|
219
|
+
def handle_page_states(self):
|
|
220
|
+
"""处理各种页面状态"""
|
|
221
|
+
current_url = self.web_page.url
|
|
222
|
+
current_title = self.web_page.title()
|
|
223
|
+
|
|
224
|
+
log(f'当前页面状态 - URL: {current_url}, Title: {current_title}', self.store_username, self.store_name)
|
|
225
|
+
|
|
226
|
+
if 'https://sso.geiwohuo.com/#/home' in current_url:
|
|
227
|
+
if current_title == 'SHEIN' or '后台首页' in current_title or '商家后台' in current_title:
|
|
228
|
+
log(f'{current_title} 页面加载完成', self.store_username, self.store_name)
|
|
229
|
+
self.web_page.wait_for_timeout(5000)
|
|
230
|
+
else:
|
|
231
|
+
# 页面可能还在加载,刷新一下
|
|
232
|
+
self.web_page.reload()
|
|
233
|
+
self.wait_for_page_stable()
|
|
234
|
+
|
|
235
|
+
elif 'mrs.biz.sheincorp.cn' in current_url and '商家后台' in current_title:
|
|
236
|
+
self.safe_goto('https://sso.geiwohuo.com/#/home?q=1')
|
|
237
|
+
|
|
238
|
+
elif self.web_page.locator('//h1[contains(text(),"鉴权")]').is_visible():
|
|
239
|
+
log('检测到鉴权页面,刷新页面', self.store_username, self.store_name)
|
|
240
|
+
self.web_page.reload()
|
|
241
|
+
self.wait_for_page_stable()
|
|
242
|
+
|
|
243
|
+
elif current_title == 'SHEIN' and 'sso.geiwohuo.com' not in current_url:
|
|
244
|
+
self.safe_goto('https://sso.geiwohuo.com/#/home?q=2')
|
|
245
|
+
|
|
246
|
+
def is_auth_successful(self):
|
|
247
|
+
"""检查鉴权是否成功"""
|
|
248
|
+
try:
|
|
249
|
+
current_url = self.web_page.url
|
|
250
|
+
current_title = self.web_page.title()
|
|
251
|
+
|
|
252
|
+
success_conditions = [
|
|
253
|
+
'SHEIN全球商家中心' in current_title,
|
|
254
|
+
'后台首页' in current_title,
|
|
255
|
+
'商家后台' in current_title,
|
|
256
|
+
self.web_page.locator('//span[contains(text(),"商品管理")]').nth(1).is_visible()
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
return any(success_conditions)
|
|
260
|
+
except:
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
def handle_page_crash(self):
|
|
264
|
+
"""处理页面崩溃"""
|
|
265
|
+
try:
|
|
266
|
+
log("处理页面崩溃...")
|
|
267
|
+
|
|
268
|
+
# 如果是特定URL的崩溃,直接导航到安全页面
|
|
269
|
+
if hasattr(self, 'web_page'):
|
|
270
|
+
try:
|
|
271
|
+
current_url = self.web_page.url
|
|
272
|
+
if 'https://sso.geiwohuo.com//#/auth/SSLS' in current_url:
|
|
273
|
+
self.safe_goto('https://sso.geiwohuo.com/#/home?q=3')
|
|
274
|
+
return
|
|
275
|
+
except:
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
# 重新创建页面
|
|
279
|
+
self.recreate_page()
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
log(f"处理页面崩溃失败: {e}")
|
|
283
|
+
raise
|
|
147
284
|
log('鉴权处理结束')
|
|
148
285
|
# web_page.wait_for_load_state("load")
|
|
149
286
|
# web_page.wait_for_load_state("networkidle")
|
|
150
|
-
web_page.wait_for_timeout(3000)
|
|
287
|
+
self.web_page.wait_for_timeout(3000)
|
|
151
288
|
|
|
152
289
|
# 获取质检报告pdf地址
|
|
153
290
|
def get_qc_report_url(self, deliverCode, purchaseCode):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|