打包流程:新增一键打包脚本(不跑自检),打包时复制当前 Chrome 登录态到 dist;spec 内置 Playwright 驱动与 pinterest_scraper;冻结态会话目录指向 exe 同目录
This commit is contained in:
+9
-2
@@ -13,9 +13,16 @@ a = Analysis(
|
||||
],
|
||||
datas=[('config.yaml', '.'), ('configs', 'configs'), ('prompts', 'prompts'),
|
||||
('templates', 'templates'), ('basemap', 'basemap'),
|
||||
('material_library', 'material_library')],
|
||||
('material_library', 'material_library'),
|
||||
# Playwright 驱动(Node.js driver):Pinterest 爬取用 playwright 控制系统 Chrome。
|
||||
# 冻结后 _driver.py 按 inspect.getfile(playwright)/driver 定位,必须放 playwright/driver。
|
||||
('C:\\Users\\Admin\\Desktop\\test模版\\design_agent\\pod_trend_agent\\.venv\\Lib\\site-packages\\playwright\\driver', 'playwright/driver')],
|
||||
# 注意:db/ 不打进 exe —— 运行时常读 exe 旁 db/spu_sku.db(用户可随时替换取最新)
|
||||
hiddenimports=['pytrends', 'pytrends.request', 'PIL', 'openpyxl'],
|
||||
hiddenimports=['pytrends', 'pytrends.request', 'PIL', 'openpyxl',
|
||||
# Pinterest 参考模式:scraper 包 + playwright + 异步下载依赖
|
||||
'pinterest_scraper', 'pinterest_scraper.scraper',
|
||||
'pinterest_scraper.pinterest_image_capture',
|
||||
'playwright', 'playwright.async_api', 'aiohttp', 'aiofiles'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
|
||||
@@ -4,6 +4,7 @@ import mimetypes
|
||||
import os
|
||||
import random
|
||||
import socket
|
||||
import sys
|
||||
import urllib.request
|
||||
from urllib.parse import urlparse, quote
|
||||
|
||||
@@ -144,11 +145,18 @@ def get_system_proxy() -> str | None:
|
||||
|
||||
|
||||
def _session_user_data_dir() -> str:
|
||||
"""返回项目内持久化的 Chrome 用户数据目录。
|
||||
"""返回持久化的 Chrome 用户数据目录。
|
||||
|
||||
该目录会一直保留(已加入 .gitignore),首次手动登录 Pinterest 后,
|
||||
登录态(cookies 等)自动缓存在这里,后续运行直接复用,不再需要登录。
|
||||
源码运行时:pinterest_scraper/.chrome_session(项目内);
|
||||
打包 exe 运行时:exe 同目录 pinterest_scraper/.chrome_session(持久,
|
||||
打包时把当前登录态复制过去即可复用,不随临时解压目录消失)。
|
||||
"""
|
||||
if getattr(sys, "frozen", False):
|
||||
base = os.path.dirname(os.path.abspath(sys.executable))
|
||||
ud = os.path.join(base, "pinterest_scraper", ".chrome_session", "User Data")
|
||||
else:
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
ud = os.path.join(base, ".chrome_session", "User Data")
|
||||
os.makedirs(ud, exist_ok=True)
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""一键打包脚本:PyInstaller 构建(不跑自检)→ 同步数据目录 → 复制 Chrome 登录态 → 生成 dist_vXX。
|
||||
|
||||
用法(在项目根目录执行):
|
||||
python scripts/package.py # 自动取下一个版本号
|
||||
python scripts/package.py 71 # 指定版本号
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
DIST = os.path.join(ROOT, "dist")
|
||||
|
||||
# 优先使用项目 .venv 的 Python(含 PyInstaller),避免系统 python 无该模块
|
||||
VENV_PY = os.path.join(ROOT, ".venv", "Scripts", "python.exe")
|
||||
PYTHON = VENV_PY if os.path.isfile(VENV_PY) else sys.executable
|
||||
|
||||
# 从源码同步到 dist 的目录/文件
|
||||
SYNC_ITEMS = [
|
||||
"config.yaml",
|
||||
"configs",
|
||||
"prompts",
|
||||
"templates",
|
||||
"basemap",
|
||||
"material_library",
|
||||
"db",
|
||||
"output",
|
||||
"logs",
|
||||
]
|
||||
|
||||
# 登录态目录:打包时把当前 Chrome 登录态复制到 exe 同目录,供冻结后复用
|
||||
CHROME_SESSION_SRC = os.path.join(ROOT, "pinterest_scraper", ".chrome_session")
|
||||
CHROME_SESSION_DST = os.path.join(DIST, "pinterest_scraper", ".chrome_session")
|
||||
|
||||
|
||||
def find_prev_dist() -> str | None:
|
||||
"""找到上一个 dist_vXX(用于同步 .cache,避免新 exe 首次运行重新采集)。"""
|
||||
versions = []
|
||||
for name in os.listdir(ROOT):
|
||||
m = re.fullmatch(r"dist_v(\d+)", name)
|
||||
if m and os.path.isdir(os.path.join(ROOT, name)):
|
||||
versions.append(int(m.group(1)))
|
||||
if not versions:
|
||||
return None
|
||||
return os.path.join(ROOT, f"dist_v{max(versions)}")
|
||||
|
||||
|
||||
def next_version() -> int:
|
||||
versions = []
|
||||
for name in os.listdir(ROOT):
|
||||
m = re.fullmatch(r"dist_v(\d+)", name)
|
||||
if m and os.path.isdir(os.path.join(ROOT, name)):
|
||||
versions.append(int(m.group(1)))
|
||||
return (max(versions) + 1) if versions else 71
|
||||
|
||||
|
||||
def sync_dir(src: str, dst: str) -> None:
|
||||
if not os.path.isdir(src):
|
||||
print(f"[sync] 跳过(源不存在): {src}")
|
||||
return
|
||||
os.makedirs(dst, exist_ok=True)
|
||||
for name in os.listdir(src):
|
||||
s = os.path.join(src, name)
|
||||
d = os.path.join(dst, name)
|
||||
if os.path.isdir(s):
|
||||
if os.path.isdir(d):
|
||||
shutil.rmtree(d)
|
||||
shutil.copytree(s, d)
|
||||
else:
|
||||
shutil.copy2(s, d)
|
||||
print(f"[sync] 目录同步完成: {src} -> {dst}")
|
||||
|
||||
|
||||
def sync_file(src: str, dst: str) -> None:
|
||||
if not os.path.isfile(src):
|
||||
print(f"[sync] 跳过(源不存在): {src}")
|
||||
return
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
shutil.copy2(src, dst)
|
||||
print(f"[sync] 文件同步完成: {src} -> {dst}")
|
||||
|
||||
|
||||
def build_exe() -> None:
|
||||
print("===== 1/4 PyInstaller 构建(不跑自检) =====")
|
||||
if os.path.isdir(DIST):
|
||||
shutil.rmtree(DIST)
|
||||
os.makedirs(DIST, exist_ok=True)
|
||||
subprocess.run(
|
||||
[PYTHON, "-m", "PyInstaller", "--noconfirm", "--clean", "PODTrendAgent.spec"],
|
||||
cwd=ROOT, check=True,
|
||||
)
|
||||
exe = os.path.join(DIST, "PODTrendAgent.exe")
|
||||
if not os.path.isfile(exe):
|
||||
raise SystemExit(f"[error] 构建失败:未找到 {exe}")
|
||||
print(f"[build] 构建完成: {exe} ({os.path.getsize(exe) / 1024 / 1024:.1f} MB)")
|
||||
|
||||
|
||||
def sync_data(prev_dist: str | None) -> None:
|
||||
print("===== 2/4 同步数据目录 =====")
|
||||
for item in SYNC_ITEMS:
|
||||
sync_file(os.path.join(ROOT, item), os.path.join(DIST, item)) \
|
||||
if os.path.isfile(os.path.join(ROOT, item)) \
|
||||
else sync_dir(os.path.join(ROOT, item), os.path.join(DIST, item))
|
||||
# .cache 从上一个 dist_vXX 同步(避免新 exe 首次运行重新采集)
|
||||
if prev_dist:
|
||||
prev_cache = os.path.join(prev_dist, ".cache")
|
||||
if os.path.isdir(prev_cache):
|
||||
sync_dir(prev_cache, os.path.join(DIST, ".cache"))
|
||||
else:
|
||||
print(f"[sync] 上一个版本无 .cache,跳过: {prev_cache}")
|
||||
else:
|
||||
print("[sync] 未找到上一个 dist_vXX,跳过 .cache 同步")
|
||||
|
||||
|
||||
def copy_chrome_session() -> None:
|
||||
print("===== 3/4 复制当前 Chrome 登录态 =====")
|
||||
if os.path.isdir(CHROME_SESSION_SRC):
|
||||
sync_dir(CHROME_SESSION_SRC, CHROME_SESSION_DST)
|
||||
else:
|
||||
print(f"[warn] 未找到 Chrome 登录态目录,跳过: {CHROME_SESSION_SRC}")
|
||||
|
||||
|
||||
def rename_dist(version: int) -> str:
|
||||
print("===== 4/4 生成 dist_vXX =====")
|
||||
target = os.path.join(ROOT, f"dist_v{version}")
|
||||
if os.path.isdir(target):
|
||||
shutil.rmtree(target)
|
||||
shutil.move(DIST, target)
|
||||
print(f"[done] 发布目录: {target}")
|
||||
return target
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="PODTrendAgent 一键打包")
|
||||
parser.add_argument("version", nargs="?", type=int, default=None, help="版本号(默认自动取下一个)")
|
||||
args = parser.parse_args()
|
||||
|
||||
version = args.version or next_version()
|
||||
prev_dist = find_prev_dist()
|
||||
print(f"目标版本: dist_v{version},上一个版本: {os.path.basename(prev_dist) if prev_dist else '无'}")
|
||||
|
||||
build_exe()
|
||||
sync_data(prev_dist)
|
||||
copy_chrome_session()
|
||||
target = rename_dist(version)
|
||||
print(f"\n打包完成:{target}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user