diff --git a/Cigarette/jinye_scraper.py b/Cigarette/jinye_scraper.py index ee3447f..556e4e0 100644 --- a/Cigarette/jinye_scraper.py +++ b/Cigarette/jinye_scraper.py @@ -1,21 +1,10 @@ #!/usr/bin/env python3 """ -卷烟平台数据采集脚本 v4(跨平台 + 数据/图片合并) - -- Cookie 从环境变量 JY_CK 读取 -- 以产品名称为主键,增量更新模板 -- 图片用原始文件名,可并发下载 -- 特定列自动设置数字格式和公式 -- 每 20 条自动保存 -- 跨平台:Windows / Linux (青龙) 均可运行 - -用法: - python jinye_scraper.py # 使用脚本同目录下的模板 - python jinye_scraper.py --template ./模板.xlsx # 指定模板 - python jinye_scraper.py --no-images # 只采集数据,不下图片 - python jinye_scraper.py --images-only # 只下载图片(不处理数据) - python jinye_scraper.py --skip-existing # 跳过已存在产品(加速) - python jinye_scraper.py --delay 0.1 # API 请求间隔 +卷烟平台数据采集脚本 v4(通用格式 + 强制禁用代理) +- 数字列格式设为 'General'(G/通用格式) +- 强制忽略系统代理(HTTP_PROXY/HTTPS_PROXY) +- 修复调货价(第20列)转数字 +- 从第一个空白行开始写入 """ import os, sys, json, time, re, argparse @@ -35,9 +24,9 @@ FUNCODE = "MOHD0418" SAVE_INTERVAL = 20 # 格式列编号 -INT_COLS = {4, 14} # D 小盒条码, N 条装条码 → '0' -DEC_COLS = {6, 7, 8, 9, 16, 17, 19} # 数值列 → '#,##0.##' -DATE_COL = 30 # ^ 批复日期 → 'yyyy-mm-dd' +INT_COLS = {4, 14} # D 小盒条码, N 条装条码 → 整数 +DEC_COLS = {6, 7, 8, 9, 16, 17, 19, 20} # 数值列 → 通用格式 +DATE_COL = 30 # 批复日期 → 'yyyy-mm-dd' FORMULA_COL = 10 # J 烟支分类公式 # 列号 → API 字段路径 @@ -68,7 +57,6 @@ COL_FIELD = { # ── 工具函数 ────────────────────────────────────────── def safe_product_dir(name: str) -> str: - """产品名 → 安全文件夹名""" for ch in r'/\:*?"<>|': name = name.replace(ch, " ") return name @@ -79,7 +67,6 @@ def extract_filename(url: str) -> str: return unquote(raw) if raw else f"img{int(time.time())}.jpg" def _g(detail, path): - """从嵌套字典取值""" if detail is None: return "" val = detail for p in path.split("."): @@ -98,11 +85,17 @@ def parse_cookies(s: str) -> dict: return c def make_client(cookies: dict) -> httpx.Client: - return httpx.Client(cookies=cookies, headers={ - "User-Agent": "Mozilla/5.0", - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", - "Referer": f"{BASE_URL}/ui/pages/list.html?funCode={FUNCODE}" - }, timeout=30) + # 强制禁用代理,避免走 SOCKS/HTTP 代理 + return httpx.Client( + cookies=cookies, + headers={ + "User-Agent": "Mozilla/5.0", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "Referer": f"{BASE_URL}/ui/pages/list.html?funCode={FUNCODE}" + }, + timeout=30, + proxies=None # ← 关键修改:忽略系统代理 + ) # ── API ─────────────────────────────────────────────── @@ -161,7 +154,7 @@ def write_cell(ws, row: int, col: int, value): if s: try: cell.value = int(float(s)) except ValueError: cell.value = s - cell.number_format = '0' + cell.number_format = '0' # 整数格式 return if col in DEC_COLS: @@ -169,7 +162,7 @@ def write_cell(ws, row: int, col: int, value): if s: try: cell.value = float(s) except ValueError: cell.value = s - cell.number_format = '#,##0.##' + cell.number_format = 'General' # G/通用格式 return cell.value = str(value).strip() if value else "" @@ -189,9 +182,7 @@ def download_single(url: str, folder: Path) -> bool: return False def download_images_for_product(pk, name, images_root: Path, cookies: dict): - """为单个产品下载图片(并发单元)""" folder = images_root / safe_product_dir(name) - # 检查是否已有非编号图片 existing = [f for f in os.listdir(str(folder)) if not re.match(r'^\d{2,3}\.jpg$', f)] if folder.exists() else [] if existing: return name, 0, len(existing) @@ -214,7 +205,7 @@ def download_images_for_product(pk, name, images_root: Path, cookies: dict): # ── 主流程 ──────────────────────────────────────────── def main(): - parser = argparse.ArgumentParser(description="卷烟平台数据采集 v4") + parser = argparse.ArgumentParser(description="卷烟平台数据采集 v4(通用格式版)") parser.add_argument("--template", default="", help="模板路径,默认脚本同目录") parser.add_argument("--output", default="", help="输出路径,默认覆盖模板") parser.add_argument("--page-size", type=int, default=20) @@ -225,7 +216,6 @@ def main(): parser.add_argument("--img-workers", type=int, default=5, help="图片下载并发数") args = parser.parse_args() - # 模板路径 script_dir = Path(__file__).parent if not args.template: args.template = str(script_dir / "卷烟产品数据系统_v1.xlsx") @@ -241,15 +231,12 @@ def main(): if not cookie_str: print("错误: JY_CK 未设置"); sys.exit(1) cookies = parse_cookies(cookie_str) - # ── 仅图片模式 ───────────────────────────────────── if args.images_only: run_images_only(template_path, images_root, cookies, args) return - # ── 正常模式:数据 + 图片 ────────────────────────── client = make_client(cookies) - # 清理旧编号图片 if images_root.exists(): for d in os.listdir(str(images_root)): dp = images_root / d @@ -259,11 +246,9 @@ def main(): try: (dp / f).unlink() except: pass - # 打开模板 wb = openpyxl.load_workbook(str(template_path)) ws = wb.active - # 构建已有产品索引 existing = {} for row in range(2, ws.max_row + 1): n = ws.cell(row, 3).value @@ -275,20 +260,21 @@ def main(): existing[name_key] = row print(f"模板已有 {len(existing)} 条数据") - # 获取全量列表 print("获取产品列表...") all_items = fetch_all(client, args.page_size, args.delay) print(f"金叶网共 {len(all_items)} 条产品") new_cnt = upd_cnt = skip_cnt = 0 - next_row = ws.max_row + 1 fmt_cols = INT_COLS | DEC_COLS | {DATE_COL} - img_tasks = [] # 待下载图片的产品 + + # 从第一个空行开始写入 + next_row = 2 + while next_row <= ws.max_row and ws.cell(next_row, 3).value is not None: + next_row += 1 for i, item in enumerate(all_items, 1): pk, name = item["pk_cigarette"], item["vcgtname"] - # ── 已存在产品:补缺失字段 ── if name in existing: if args.skip_existing: continue @@ -318,13 +304,13 @@ def main(): if upd_cnt > 0: wb.save(output_path) continue - # ── 新产品:完整填充 ── detail = fetch_detail(client, pk) if detail is None: print(f"[{i}/{len(all_items)}] {name} 详情失败,跳过") skip_cnt += 1; continue - target_row = next_row; next_row += 1 + target_row = next_row + next_row += 1 existing[name] = target_row new_cnt += 1 @@ -341,10 +327,8 @@ def main(): fills += 1 write_cell(ws, target_row, FORMULA_COL, None) - # C 列:先写产品名,图片下载后再替换为 HYPERLINK ws.cell(target_row, 3).value = name - # 图片:记录任务(或同步下载) img_urls = detail.get("imgPaths") or [] if img_urls and not args.no_images: safe_name = safe_product_dir(name) @@ -374,7 +358,6 @@ def main(): # ── 仅图片模式 ──────────────────────────────────────── def run_images_only(template_path, images_root, cookies, args): - """独立图片下载模式""" import openpyxl as _xl wb = _xl.load_workbook(str(template_path)) ws = wb.active @@ -410,7 +393,6 @@ def run_images_only(template_path, images_root, cookies, args): tasks = [(name_to_pk[n], n) for n in products if n in name_to_pk] print(f"待处理: {len(tasks)} 个产品,{args.img_workers} 线程并发") - # 清理旧编号图片 if images_root.exists(): for d in os.listdir(str(images_root)): dp = images_root / d @@ -431,7 +413,6 @@ def run_images_only(template_path, images_root, cookies, args): print(f"\n图片完成: 下载 {total_dl} | 已有跳过 {skipped}") - # 更新 HYPERLINK print("更新超链接...") wb = _xl.load_workbook(str(template_path)) ws = wb.active @@ -450,4 +431,4 @@ def run_images_only(template_path, images_root, cookies, args): print(f"超链接更新: {updated} 个") if __name__ == "__main__": - main() + main() \ No newline at end of file