qrpa 1.0.89__tar.gz → 1.0.91__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.89 → qrpa-1.0.91}/PKG-INFO +1 -1
- {qrpa-1.0.89 → qrpa-1.0.91}/pyproject.toml +1 -1
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/db_migrator.py +1 -1
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_excel.py +35 -8
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_web.py +48 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/mysql_module/shein_product_model.py +11 -3
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_excel.py +1 -1
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_lib.py +58 -17
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_ziniao.py +73 -2
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/time_utils.py +17 -6
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa.egg-info/PKG-INFO +1 -1
- {qrpa-1.0.89 → qrpa-1.0.91}/README.md +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/RateLimitedSender.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/__init__.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/feishu_bot_app.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/feishu_client.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/feishu_logic.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_base.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_file.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/fun_win.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/mysql_module/__init__.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/mysql_module/shein_return_order_model.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_daily_report_model.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_mysql.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/shein_sqlite.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/temu_chrome.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/temu_excel.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/temu_lib.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/time_utils_example.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa/wxwork.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa.egg-info/SOURCES.txt +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa.egg-info/dependency_links.txt +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/qrpa.egg-info/top_level.txt +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/setup.cfg +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/setup.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/tests/test_db_migrator.py +0 -0
- {qrpa-1.0.89 → qrpa-1.0.91}/tests/test_wxwork.py +0 -0
|
@@ -14,6 +14,9 @@ import threading
|
|
|
14
14
|
from playwright.sync_api import sync_playwright
|
|
15
15
|
import psutil
|
|
16
16
|
|
|
17
|
+
import os, sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
17
20
|
from .fun_base import log, sanitize_filename, create_file_path, copy_file, add_https, send_exception
|
|
18
21
|
|
|
19
22
|
excel_color_index = {
|
|
@@ -195,13 +198,13 @@ def merge_by_column_v2(sheet, column_name, other_columns):
|
|
|
195
198
|
# 更安全的数据获取方式,确保获取完整的数据范围
|
|
196
199
|
last_row = get_last_row(sheet, col_letter)
|
|
197
200
|
data = sheet.range(f'{col_letter}1:{col_letter}{last_row}').value
|
|
198
|
-
|
|
201
|
+
|
|
199
202
|
# 确保data是列表格式
|
|
200
203
|
if not isinstance(data, list):
|
|
201
204
|
data = [data]
|
|
202
|
-
|
|
205
|
+
|
|
203
206
|
log(f'数据范围: {col_letter}1:{col_letter}{last_row}, 数据长度: {len(data)}')
|
|
204
|
-
|
|
207
|
+
|
|
205
208
|
start_row = 2 # 从第2行开始,跳过表头
|
|
206
209
|
merge_row_ranges = [] # 用来存储需要合并的行范围 (start_row, end_row)
|
|
207
210
|
|
|
@@ -211,13 +214,13 @@ def merge_by_column_v2(sheet, column_name, other_columns):
|
|
|
211
214
|
col_name = find_column_by_data(sheet, 1, col)
|
|
212
215
|
if col_name:
|
|
213
216
|
all_columns.append(col_name)
|
|
214
|
-
|
|
217
|
+
|
|
215
218
|
log(f'需要合并的列: {all_columns}')
|
|
216
219
|
|
|
217
220
|
# 遍历数据行,从第3行开始比较(因为第1行是表头,第2行是第一个数据行)
|
|
218
221
|
for row in range(3, len(data) + 1):
|
|
219
|
-
log(f'查找 {row}/{len(data)}, 当前值: {data[row-1] if row-1 < len(data) else "超出范围"}, 前一个值: {data[row-2] if row-2 < len(data) else "超出范围"}')
|
|
220
|
-
|
|
222
|
+
log(f'查找 {row}/{len(data)}, 当前值: {data[row - 1] if row - 1 < len(data) else "超出范围"}, 前一个值: {data[row - 2] if row - 2 < len(data) else "超出范围"}')
|
|
223
|
+
|
|
221
224
|
# 检查值是否发生变化
|
|
222
225
|
if row <= len(data) and data[row - 1] != data[row - 2]:
|
|
223
226
|
# 值发生变化,处理前一组
|
|
@@ -233,7 +236,7 @@ def merge_by_column_v2(sheet, column_name, other_columns):
|
|
|
233
236
|
merge_row_ranges.append((start_row, end_row))
|
|
234
237
|
|
|
235
238
|
log(f'行合并范围: {merge_row_ranges}')
|
|
236
|
-
|
|
239
|
+
|
|
237
240
|
# 对每个行范围,在所有指定列中执行合并
|
|
238
241
|
for start_row, end_row in merge_row_ranges:
|
|
239
242
|
if start_row < end_row: # 只有当开始行小于结束行时才合并(多行)
|
|
@@ -756,9 +759,33 @@ def download_images_concurrently(image_urls, platform='shein', img_save_dir=None
|
|
|
756
759
|
results = list(executor.map(lambda url: download_img_v2(url, platform, img_save_path=img_save_dir), image_urls))
|
|
757
760
|
return results
|
|
758
761
|
|
|
762
|
+
def get_chromium_executable():
|
|
763
|
+
"""
|
|
764
|
+
返回 Chromium 可执行文件路径,兼容 PyInstaller 打包后的 exe
|
|
765
|
+
"""
|
|
766
|
+
# PyInstaller 临时目录
|
|
767
|
+
if getattr(sys, 'frozen', False):
|
|
768
|
+
base_path = Path(sys._MEIPASS)
|
|
769
|
+
else:
|
|
770
|
+
base_path = Path(__file__).parent
|
|
771
|
+
|
|
772
|
+
# 尝试查找打包后的浏览器文件
|
|
773
|
+
possible_path = base_path / "playwright" / "driver" / "package" / "chromium_headless_shell-1169" / "chrome-win" / "headless_shell.exe"
|
|
774
|
+
if possible_path.exists():
|
|
775
|
+
return str(possible_path)
|
|
776
|
+
|
|
777
|
+
# fallback: 系统 Playwright 安装目录
|
|
778
|
+
local_appdata = Path(os.environ.get("LOCALAPPDATA", ""))
|
|
779
|
+
fallback_path = local_appdata / "ms-playwright" / "chromium" / "chrome-win" / "chrome.exe"
|
|
780
|
+
if fallback_path.exists():
|
|
781
|
+
return str(fallback_path)
|
|
782
|
+
|
|
783
|
+
raise FileNotFoundError("Chromium 可执行文件未找到,请先执行 'playwright install' 下载浏览器。")
|
|
784
|
+
|
|
759
785
|
def download_img_by_chrome(image_url, save_name):
|
|
786
|
+
chromium_path = get_chromium_executable()
|
|
760
787
|
with sync_playwright() as p:
|
|
761
|
-
browser = p.chromium.launch(headless=True) # 运行时可以看到浏览器
|
|
788
|
+
browser = p.chromium.launch(headless=True, executable_path=chromium_path) # 运行时可以看到浏览器
|
|
762
789
|
context = browser.new_context()
|
|
763
790
|
page = context.new_page()
|
|
764
791
|
# 直接通过Playwright下载图片
|
|
@@ -146,6 +146,54 @@ def full_screen_shot(web_page: Page, config):
|
|
|
146
146
|
web_page.screenshot(path=full_screenshot_image_path, full_page=True)
|
|
147
147
|
return full_screenshot_image_path
|
|
148
148
|
|
|
149
|
+
def fetch_get(page: Page, url: str, headers: Optional[dict] = None, config: Optional[dict] = None) -> dict:
|
|
150
|
+
"""
|
|
151
|
+
发送 HTTP GET 请求,支持自定义 headers 和配置。
|
|
152
|
+
|
|
153
|
+
:param page: Playwright 的 Page 对象
|
|
154
|
+
:param url: 请求地址
|
|
155
|
+
:param headers: 自定义 headers 字典
|
|
156
|
+
:param config: 请求配置字典,可包含 credentials, mode, referrer, referrerPolicy 等
|
|
157
|
+
:return: 服务器返回的 JSON 响应(dict)
|
|
158
|
+
"""
|
|
159
|
+
if headers is not None and not isinstance(headers, dict):
|
|
160
|
+
raise ValueError("headers 参数必须是 dict 或 None")
|
|
161
|
+
if config is not None and not isinstance(config, dict):
|
|
162
|
+
raise ValueError("config 参数必须是 dict 或 None")
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
page.wait_for_load_state('load')
|
|
166
|
+
response = page.evaluate("""
|
|
167
|
+
async ({ url, extraHeaders, config }) => {
|
|
168
|
+
try {
|
|
169
|
+
const defaultHeaders = {
|
|
170
|
+
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const defaultConfig = {
|
|
174
|
+
method: 'GET',
|
|
175
|
+
credentials: 'include',
|
|
176
|
+
mode: 'cors'
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const headers = Object.assign({}, defaultHeaders, extraHeaders || {});
|
|
180
|
+
const options = Object.assign({}, defaultConfig, config || {}, { headers: headers });
|
|
181
|
+
|
|
182
|
+
const response = await fetch(url, options);
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
throw new Error(`HTTP ${response.status} - ${response.statusText}`);
|
|
185
|
+
}
|
|
186
|
+
return await response.json();
|
|
187
|
+
} catch (error) {
|
|
188
|
+
return { "error": "fetch_failed", "message": error.message };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
""", {"url": url, "extraHeaders": headers, "config": config})
|
|
192
|
+
|
|
193
|
+
return response
|
|
194
|
+
except Exception as e:
|
|
195
|
+
raise send_exception()
|
|
196
|
+
|
|
149
197
|
def safe_goto(page, url, **kwargs):
|
|
150
198
|
caller = inspect.stack()[1]
|
|
151
199
|
log(f"[DEBUG] goto called from {caller.filename}:{caller.lineno} url={url}")
|
|
@@ -64,6 +64,9 @@ class SheinProductSkc(Base):
|
|
|
64
64
|
created_at = Column(DateTime, default=datetime.now, comment='创建时间')
|
|
65
65
|
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
|
|
66
66
|
|
|
67
|
+
# 用户备注字段(供后续web界面使用)
|
|
68
|
+
user_notes = Column(Text, nullable=True, comment='用户备注')
|
|
69
|
+
|
|
67
70
|
# 定义索引
|
|
68
71
|
__table_args__ = (
|
|
69
72
|
Index('ix_skc_id', 'skc_id'),
|
|
@@ -108,6 +111,7 @@ class SheinProductSku(Base):
|
|
|
108
111
|
price = Column(DECIMAL(10, 2), nullable=True, comment='价格')
|
|
109
112
|
erp_cost_price = Column(DECIMAL(10, 2), nullable=True, comment='ERP成本价')
|
|
110
113
|
erp_supplier_name = Column(String(100), nullable=True, comment='ERP默认供货商')
|
|
114
|
+
erp_stock = Column(Integer, nullable=True, comment='ERP库存')
|
|
111
115
|
|
|
112
116
|
# 时间戳
|
|
113
117
|
created_at = Column(DateTime, default=datetime.now, comment='创建时间')
|
|
@@ -218,10 +222,10 @@ class SheinProductManager:
|
|
|
218
222
|
session = self.Session()
|
|
219
223
|
try:
|
|
220
224
|
for data in data_list:
|
|
221
|
-
|
|
225
|
+
|
|
222
226
|
# 处理SKC数据
|
|
223
227
|
skc_record = self._upsert_skc_data(session, data)
|
|
224
|
-
|
|
228
|
+
|
|
225
229
|
# 处理SKU数据
|
|
226
230
|
for sku_data in data.get('skuList', []):
|
|
227
231
|
sku_data['local_skc_id'] = skc_record.id
|
|
@@ -319,6 +323,7 @@ class SheinProductManager:
|
|
|
319
323
|
existing_sku.price = sku_data.get('price')
|
|
320
324
|
existing_sku.erp_cost_price = sku_data.get('erp_cost_price')
|
|
321
325
|
existing_sku.erp_supplier_name = sku_data.get('erp_supplier_name')
|
|
326
|
+
existing_sku.erp_stock = sku_data.get('erp_stock')
|
|
322
327
|
existing_sku.updated_at = datetime.now()
|
|
323
328
|
else:
|
|
324
329
|
# 插入新记录
|
|
@@ -473,4 +478,7 @@ def example_usage2():
|
|
|
473
478
|
|
|
474
479
|
if __name__ == "__main__":
|
|
475
480
|
pass
|
|
476
|
-
|
|
481
|
+
database_url = "mysql+pymysql://root:123wyk@localhost:3306/lz"
|
|
482
|
+
manager = SheinProductManager(database_url)
|
|
483
|
+
manager.create_tables()
|
|
484
|
+
# example_usage()
|
|
@@ -2341,7 +2341,7 @@ class SheinExcel:
|
|
|
2341
2341
|
self.dealFormula(sheet) # 有空再封装优化
|
|
2342
2342
|
colorize_by_field(sheet, 'SPU')
|
|
2343
2343
|
autofit_column(sheet, ['商品信息', '店铺名称', 'SKC点击率/SKC转化率', '自主参与活动'])
|
|
2344
|
-
column_to_left(sheet, ['店铺名称', 'SKC点击率/SKC转化率', '自主参与活动'])
|
|
2344
|
+
column_to_left(sheet, ['店铺名称', 'SKC点击率/SKC转化率', '自主参与活动','近7天SKU销量/SKC销量/SKC曝光'])
|
|
2345
2345
|
specify_column_width(sheet, ['商品标题'], 150 / 6)
|
|
2346
2346
|
add_borders(sheet)
|
|
2347
2347
|
InsertImageV2(sheet, ['SKC图片', 'SKU图片'], 'shein', 120, None, None, True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .fun_file import read_dict_from_file, write_dict_to_file, read_dict_from_file_ex, write_dict_to_file_ex
|
|
2
2
|
from .fun_base import log, send_exception, md5_string, get_safe_value, NetWorkIdleTimeout
|
|
3
|
-
from .fun_web import fetch, full_screen_shot, safe_goto
|
|
3
|
+
from .fun_web import fetch, fetch_get, full_screen_shot, safe_goto
|
|
4
4
|
from .time_utils import TimeUtils
|
|
5
5
|
from .wxwork import WxWorkBot
|
|
6
6
|
|
|
@@ -36,7 +36,7 @@ class SheinLib:
|
|
|
36
36
|
try:
|
|
37
37
|
current_url = web_page.url
|
|
38
38
|
log(f"尝试获取页面信息 - URL: {current_url}", self.store_username, self.store_name)
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
# 检查是否在认证页面,如果是则直接跳转到目标页面
|
|
41
41
|
if '/auth/SSLS' in current_url:
|
|
42
42
|
log("检测到SSLS认证页面,直接跳转到首页", self.store_username, self.store_name)
|
|
@@ -44,17 +44,17 @@ class SheinLib:
|
|
|
44
44
|
web_page.wait_for_timeout(3000)
|
|
45
45
|
current_url = web_page.url
|
|
46
46
|
log(f"跳转后URL: {current_url}", self.store_username, self.store_name)
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
# 等待导航完成
|
|
49
49
|
web_page.wait_for_load_state("domcontentloaded", timeout=6000)
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
final_url = web_page.url
|
|
52
52
|
final_title = web_page.title()
|
|
53
53
|
log(f"页面稳定 - URL: {final_url}, 标题: {final_title}", self.store_username, self.store_name)
|
|
54
54
|
break
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
except Exception as e:
|
|
57
|
-
log(f"第{attempt+1}次等待页面稳定失败: {e}", self.store_username, self.store_name)
|
|
57
|
+
log(f"第{attempt + 1}次等待页面稳定失败: {e}", self.store_username, self.store_name)
|
|
58
58
|
if "crashed" in str(e) or "Target" in str(e):
|
|
59
59
|
log("页面稳定检查时崩溃,直接继续", self.store_username, self.store_name)
|
|
60
60
|
break
|
|
@@ -66,7 +66,7 @@ class SheinLib:
|
|
|
66
66
|
log("页面稳定等待最终失败,继续执行", self.store_username, self.store_name)
|
|
67
67
|
break
|
|
68
68
|
web_page.wait_for_timeout(2000)
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
web_page.wait_for_timeout(2000)
|
|
71
71
|
|
|
72
72
|
# 定义最大重试次数
|
|
@@ -83,19 +83,19 @@ class SheinLib:
|
|
|
83
83
|
retries += 1
|
|
84
84
|
|
|
85
85
|
while not web_page.locator('//div[contains(text(),"商家后台")]').nth(1).is_visible():
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
try:
|
|
88
88
|
current_url = web_page.url
|
|
89
89
|
current_title = web_page.title()
|
|
90
90
|
log(f"循环检查 - URL: {current_url}, 标题: {current_title}", self.store_username, self.store_name)
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
# 如果在认证页面且出现问题,直接跳转
|
|
93
93
|
if '/auth/SSLS' in current_url:
|
|
94
94
|
log("在主循环中检测到SSLS认证页面,跳转到首页", self.store_username, self.store_name)
|
|
95
95
|
web_page.goto('https://sso.geiwohuo.com/#/home', wait_until='domcontentloaded', timeout=15000)
|
|
96
96
|
web_page.wait_for_timeout(3000)
|
|
97
97
|
continue
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
except Exception as status_error:
|
|
100
100
|
log(f"获取页面状态失败: {status_error}", self.store_username, self.store_name)
|
|
101
101
|
if "crashed" in str(status_error):
|
|
@@ -200,13 +200,13 @@ class SheinLib:
|
|
|
200
200
|
except Exception as e:
|
|
201
201
|
log(f"错误发生: {e}, 重试中...({self.store_username}, {self.store_name})")
|
|
202
202
|
log(traceback.format_exc())
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
# 收集崩溃时的详细信息
|
|
205
205
|
try:
|
|
206
206
|
crash_url = web_page.url
|
|
207
207
|
crash_title = web_page.title()
|
|
208
208
|
log(f"崩溃时页面信息 - URL: {crash_url}, 标题: {crash_title}", self.store_username, self.store_name)
|
|
209
|
-
|
|
209
|
+
|
|
210
210
|
# 尝试截图保存崩溃现场
|
|
211
211
|
try:
|
|
212
212
|
screenshot_path = f"crash_screenshot_{self.store_username}_{int(time.time())}.png"
|
|
@@ -214,19 +214,19 @@ class SheinLib:
|
|
|
214
214
|
log(f"已保存崩溃截图: {screenshot_path}", self.store_username, self.store_name)
|
|
215
215
|
except:
|
|
216
216
|
log("无法截取崩溃时的页面截图", self.store_username, self.store_name)
|
|
217
|
-
|
|
217
|
+
|
|
218
218
|
except:
|
|
219
219
|
log("无法获取崩溃时的页面信息", self.store_username, self.store_name)
|
|
220
|
-
|
|
220
|
+
|
|
221
221
|
# 检查特定类型的错误
|
|
222
222
|
if any(keyword in str(e).lower() for keyword in ['memory', 'out of memory', 'oom']):
|
|
223
223
|
log("检测到内存相关崩溃", self.store_username, self.store_name)
|
|
224
|
-
|
|
224
|
+
|
|
225
225
|
if "destroyed" in str(e) or "navigation" in str(e):
|
|
226
226
|
log("检测到导航中断,等待页面稳定后重试", self.store_username, self.store_name)
|
|
227
227
|
web_page.wait_for_timeout(5000)
|
|
228
228
|
continue
|
|
229
|
-
|
|
229
|
+
|
|
230
230
|
if 'crashed' in str(e) or 'Target' in str(e):
|
|
231
231
|
log("检测到页面或目标崩溃,直接退出当前循环", self.store_username, self.store_name)
|
|
232
232
|
raise e
|
|
@@ -241,6 +241,44 @@ class SheinLib:
|
|
|
241
241
|
# web_page.wait_for_load_state("networkidle")
|
|
242
242
|
web_page.wait_for_timeout(3000)
|
|
243
243
|
|
|
244
|
+
# 获取用户信息
|
|
245
|
+
def get_user(self, uuid=None):
|
|
246
|
+
log(f'获取用户信息:{self.store_username} {self.store_name}')
|
|
247
|
+
|
|
248
|
+
# 生成 uuid 参数,如果没有提供则使用时间戳
|
|
249
|
+
if uuid is None:
|
|
250
|
+
import time
|
|
251
|
+
uuid = str(int(time.time() * 1000))
|
|
252
|
+
|
|
253
|
+
url = f"https://sso.geiwohuo.com/sso-prefix/auth/getUser?uuid={uuid}"
|
|
254
|
+
|
|
255
|
+
# 设置请求头,根据 Chrome 请求
|
|
256
|
+
headers = {
|
|
257
|
+
"gmpsso-language": "CN",
|
|
258
|
+
"origin-url" : "https://sso.geiwohuo.com/#/home/",
|
|
259
|
+
"x-sso-scene" : "gmpsso"
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
# 特定于此请求的配置
|
|
263
|
+
fetch_config = {
|
|
264
|
+
"credentials" : "omit",
|
|
265
|
+
"referrer" : "https://sso.geiwohuo.com/",
|
|
266
|
+
"referrerPolicy": "strict-origin-when-cross-origin"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
response_text = fetch_get(self.web_page, url, headers, fetch_config)
|
|
270
|
+
error_code = response_text.get('code')
|
|
271
|
+
if str(error_code) != '0':
|
|
272
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
273
|
+
info = response_text.get('info', {})
|
|
274
|
+
log(info)
|
|
275
|
+
cache_file = f'{self.config.auto_dir}/shein_user.json'
|
|
276
|
+
info['store_username'] = self.store_username
|
|
277
|
+
info['store_name'] = self.store_name
|
|
278
|
+
write_dict_to_file_ex(cache_file, {self.store_username: info}, [self.store_username])
|
|
279
|
+
|
|
280
|
+
return info
|
|
281
|
+
|
|
244
282
|
# 获取质检报告pdf地址
|
|
245
283
|
def get_qc_report_url(self, deliverCode, purchaseCode):
|
|
246
284
|
log(f'获取质检报告:{deliverCode} {purchaseCode}')
|
|
@@ -849,6 +887,8 @@ class SheinLib:
|
|
|
849
887
|
cost_price = self.bridge.get_sku_cost(sku_item['supplierSku'], self.config.erp_source)
|
|
850
888
|
sku_item['erp_cost_price'] = cost_price if isinstance(cost_price, (int, float)) else None
|
|
851
889
|
sku_item['erp_supplier_name'] = self.bridge.get_sku_supplier(sku_item['supplierSku'], self.config.erp_source)
|
|
890
|
+
stock = self.bridge.get_sku_stock(sku_item['supplierSku'], self.config.erp_source)
|
|
891
|
+
sku_item['erp_stock'] = stock if isinstance(stock, (int, float)) else None
|
|
852
892
|
|
|
853
893
|
cache_file = f'{self.config.auto_dir}/shein/product/skc_list_{self.store_username}.json'
|
|
854
894
|
write_dict_to_file_ex(cache_file, {self.store_username: skc_list}, [self.store_username])
|
|
@@ -2276,6 +2316,7 @@ class SheinLib:
|
|
|
2276
2316
|
skc = str(spu_info['skc'])
|
|
2277
2317
|
# if not shein_db.exists_sales_1_days_ago(skc):
|
|
2278
2318
|
# log(f'未查到昨天销量: {skc}')
|
|
2319
|
+
self.get_skc_week_actual_sales(skc)
|
|
2279
2320
|
self.get_skc_sales(skc, date_60_days_ago, date_1_days_ago)
|
|
2280
2321
|
skcCode = spu_info['supplierCode']
|
|
2281
2322
|
product_name = DictSpuInfo[spu]['product_name_en']
|
|
@@ -2396,7 +2437,7 @@ class SheinLib:
|
|
|
2396
2437
|
# SKC趋势数据
|
|
2397
2438
|
sku_item.append(skc_trend.get('saleCnt', 0)) # SKC近7天销量
|
|
2398
2439
|
sku_item.append(skc_trend.get('epsUvIdx', 0)) # SKC近7天曝光人数
|
|
2399
|
-
sku_item.append(skc_trend.get('
|
|
2440
|
+
sku_item.append(skc_trend.get('goodsUv', 0)) # SKC近7天商详访客
|
|
2400
2441
|
sku_item.append(skc_trend.get('epsGdsCtrIdx', 0)) # SKC近7天点击率
|
|
2401
2442
|
sku_item.append(skc_trend.get('payUvIdx', 0)) # SKC近7天支付人数
|
|
2402
2443
|
sku_item.append(skc_trend.get('gdsPayCtrIdx', 0)) # SKC近7天支付率
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import os
|
|
6
6
|
import platform
|
|
7
7
|
import shutil
|
|
8
|
-
import time
|
|
8
|
+
import time, datetime
|
|
9
9
|
import traceback
|
|
10
10
|
import uuid
|
|
11
11
|
import json
|
|
@@ -74,7 +74,7 @@ class ZiniaoClient:
|
|
|
74
74
|
"""启动客户端"""
|
|
75
75
|
try:
|
|
76
76
|
if self.is_windows:
|
|
77
|
-
cmd = [self.client_path, '--run_type=web_driver', '--ipc_type=http', '--port=' + str(self.socket_port)]
|
|
77
|
+
cmd = [self.client_path, '--run_type=web_driver', '--show_sidb=true', '--ipc_type=http', '--port=' + str(self.socket_port)]
|
|
78
78
|
elif self.is_mac:
|
|
79
79
|
cmd = ['open', '-a', self.client_path, '--args', '--run_type=web_driver', '--ipc_type=http',
|
|
80
80
|
'--port=' + str(self.socket_port)]
|
|
@@ -272,6 +272,73 @@ class ZiniaoTaskManager:
|
|
|
272
272
|
self.browser = browser
|
|
273
273
|
self.config = config
|
|
274
274
|
|
|
275
|
+
def daily_cleanup_superbrowser(self, browser_id):
|
|
276
|
+
"""
|
|
277
|
+
每天删除一次SuperBrowser缓存文件夹
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
browser_id (str): 浏览器ID,如 '26986387919128'
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
# 获取本地AppData路径
|
|
284
|
+
local_appdata = os.getenv('LOCALAPPDATA')
|
|
285
|
+
if not local_appdata:
|
|
286
|
+
log("错误: 无法获取LOCALAPPDATA路径")
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
# 构建路径
|
|
290
|
+
cache_path = os.path.join(local_appdata, 'SuperBrowser')
|
|
291
|
+
target_folder = os.path.join(cache_path, f'User Data\\Chromium_{browser_id}')
|
|
292
|
+
flag_file = os.path.join(cache_path, f'User Data\\cleanup_flag_{browser_id}.txt')
|
|
293
|
+
|
|
294
|
+
# 检查目标文件夹是否存在
|
|
295
|
+
if not os.path.exists(target_folder):
|
|
296
|
+
log(f"目标文件夹不存在: {target_folder}")
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
# 获取当前日期
|
|
300
|
+
today = datetime.date.today()
|
|
301
|
+
today_str = today.strftime('%Y-%m-%d')
|
|
302
|
+
|
|
303
|
+
# 检查标志文件
|
|
304
|
+
need_cleanup = True
|
|
305
|
+
|
|
306
|
+
if os.path.exists(flag_file):
|
|
307
|
+
try:
|
|
308
|
+
# 读取标志文件中的日期
|
|
309
|
+
with open(flag_file, 'r', encoding='utf-8') as f:
|
|
310
|
+
last_cleanup_date = f.read().strip()
|
|
311
|
+
|
|
312
|
+
# 如果是今天已经清理过,则跳过
|
|
313
|
+
if last_cleanup_date == today_str:
|
|
314
|
+
log(f"今天({today_str})已经清理过,跳过删除操作")
|
|
315
|
+
need_cleanup = False
|
|
316
|
+
|
|
317
|
+
except Exception as e:
|
|
318
|
+
log(f"读取标志文件时出错: {e}")
|
|
319
|
+
# 如果读取出错,继续执行清理
|
|
320
|
+
|
|
321
|
+
if need_cleanup:
|
|
322
|
+
try:
|
|
323
|
+
# 删除目标文件夹
|
|
324
|
+
log(f"正在删除文件夹: {target_folder}")
|
|
325
|
+
shutil.rmtree(target_folder)
|
|
326
|
+
log("删除成功!")
|
|
327
|
+
|
|
328
|
+
# 创建/更新标志文件
|
|
329
|
+
os.makedirs(os.path.dirname(flag_file), exist_ok=True)
|
|
330
|
+
with open(flag_file, 'w', encoding='utf-8') as f:
|
|
331
|
+
f.write(today_str)
|
|
332
|
+
|
|
333
|
+
log(f"已创建标志文件: {flag_file}")
|
|
334
|
+
return True
|
|
335
|
+
|
|
336
|
+
except Exception as e:
|
|
337
|
+
log(f"删除文件夹时出错: {e}")
|
|
338
|
+
return False
|
|
339
|
+
|
|
340
|
+
return True
|
|
341
|
+
|
|
275
342
|
def run_single_store_task(self, browser_info: Dict[str, Any],
|
|
276
343
|
run_func: Callable, task_key: str,
|
|
277
344
|
just_store_username: Optional[List[str]] = None,
|
|
@@ -282,6 +349,10 @@ class ZiniaoTaskManager:
|
|
|
282
349
|
store_name = browser_info.get("browserName")
|
|
283
350
|
store_username = browser_info.get("store_username")
|
|
284
351
|
|
|
352
|
+
# 删除浏览器缓存,一天一删
|
|
353
|
+
browser_id = browser_info.get("browserId")
|
|
354
|
+
self.daily_cleanup_superbrowser(browser_id)
|
|
355
|
+
|
|
285
356
|
retry_count = 0
|
|
286
357
|
while True:
|
|
287
358
|
try:
|
|
@@ -260,20 +260,31 @@ class TimeUtils:
|
|
|
260
260
|
|
|
261
261
|
@staticmethod
|
|
262
262
|
def get_dates_from_first_of_month_to_yesterday() -> List[str]:
|
|
263
|
-
"""获取从本月第一天到昨天的日期列表
|
|
263
|
+
"""获取从本月第一天到昨天的日期列表
|
|
264
|
+
如果今天是本月第一天,则返回上个月的日期列表
|
|
265
|
+
"""
|
|
264
266
|
today = datetime.today()
|
|
265
|
-
first_day_of_month = today.replace(day=1)
|
|
266
267
|
yesterday = today - timedelta(days=1)
|
|
267
268
|
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
# 如果今天是本月第一天,取上个月
|
|
270
|
+
if today.day == 1:
|
|
271
|
+
# 找到上个月最后一天
|
|
272
|
+
last_month_last_day = today - timedelta(days=1)
|
|
273
|
+
# 上个月第一天
|
|
274
|
+
first_day_of_last_month = last_month_last_day.replace(day=1)
|
|
275
|
+
start_date = first_day_of_last_month
|
|
276
|
+
end_date = last_month_last_day
|
|
277
|
+
else:
|
|
278
|
+
start_date = today.replace(day=1)
|
|
279
|
+
end_date = yesterday
|
|
270
280
|
|
|
271
|
-
|
|
281
|
+
date_list = []
|
|
282
|
+
current_date = start_date
|
|
283
|
+
while current_date <= end_date:
|
|
272
284
|
date_list.append(current_date.strftime('%Y-%m-%d'))
|
|
273
285
|
current_date += timedelta(days=1)
|
|
274
286
|
|
|
275
287
|
return date_list
|
|
276
|
-
|
|
277
288
|
# ==================== 月份相关 ====================
|
|
278
289
|
|
|
279
290
|
@staticmethod
|
|
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
|