charley f982af721d fix(scp_core): 处理下架拍品页面404死循环问题 1 semana atrás
..
README.md f982af721d fix(scp_core): 处理下架拍品页面404死循环问题 1 semana atrás
YamlLoader.py dc51f25eba feat(mysql): 新增MySQL连接池及配置支持 1 semana atrás
application.yml dc51f25eba feat(mysql): 新增MySQL连接池及配置支持 1 semana atrás
mysql_pool.py dc51f25eba feat(mysql): 新增MySQL连接池及配置支持 1 semana atrás
requirements.txt e187d77fb8 feat(mysql): 添加 MySQL 连接池及基础数据库操作封装 1 semana atrás
scp_core.py f982af721d fix(scp_core): 处理下架拍品页面404死循环问题 1 semana atrás
scp_history.py dc51f25eba feat(mysql): 新增MySQL连接池及配置支持 1 semana atrás
scp_spider.py dc51f25eba feat(mysql): 新增MySQL连接池及配置支持 1 semana atrás

README.md

SCP Auctions 拍卖数据爬虫

抓取 SCP Auctionscatalogs.scpauctions.com)历史拍卖会的已售/流拍拍品数据,含拍卖会信息、拍品列表与拍品详情。提供 历史全量每周新增 两个任务。


1. 目标数据

层级 来源页 抓取字段
拍卖会 /auctions/past 标题、类型、状态、开始/结束时间、主页链接、图录链接、封面图、event_id(去重)
拍品列表 图录页 catalog Lot 号、标题、详情链接、缩略图、出价数、成交状态、成交价、item_id(去重)
拍品详情 /online-auctions/... 标题、成交价、成交状态、出价数、Collection、Sport、Type、Athlete、Team、多图

2. 站点分析结论(为什么这么抓)

用户要求「优先从接口抓取」。经浏览器抓包确认:本站为 Bidsquare 纯 SSR 页面,数据全部内嵌在 HTML 中,无独立数据 API(XHR 仅为 GA/埋点)。因此采用 curl_cffi 直接 GET 页面 + parsel 解析,无 Cloudflare 挑战,响应 200 纯 HTML。

  • 去重键
    • 拍卖会:event_id(拍卖会卡片 data-event_id,如 21706,也出现在 URL 尾部)
    • 拍品:item_id(lot 卡片 data-item_id,如 9317607
  • 分页
    • 拍卖会列表:/auctions/past?page=N,每页 24 场,翻到空页为止
    • 图录(lot 列表):{catalog_url}?page=N&limit=120limit 上限 120,翻到空页为止
  • 详情页属性区字段数不固定.item-attributes li<label>键</label><span>值</span>)可能是 2~5 个,Athlete/Team 常缺失 → 先建 label→value 字典,再按需取值,缺失置 None
  • 多图归一化:缩略图 s1.img.bidsquare.com/item/s/... 与大图 /item/l/... 并存,统一替换成 /item/l/(large)后去重,逗号拼接入库。
  • 成交状态:列表卡片底部 9 BidsSold for$90,000 / 24 BidsUnsold;详情页 .bidding-price 有金额为 sold,为空为 unsold

3. 文件结构

2026-07-15(scp_spider)/
├── application.yml      # MySQL 配置(mysql_pool 从运行目录读取)
├── create_table.sql     # 建表:scp_auction(场次)+ scp_lot(拍品)
├── scp_core.py          # 公用模块:HTTP/代理、列表解析、分页抓取、详情解析、入库
├── scp_history.py       # 任务一:历史全量(一次性)
├── scp_spider.py        # 任务二:每周新增(周调度常驻)
└── logs/                # 运行日志(loguru 按天切分,保留 7 天)

数据表

  • scp_auction:一条 = 一场拍卖会,event_id 唯一索引。
  • scp_lot:一条 = 一个拍品,item_id 唯一索引;字段分两阶段写入。

两阶段设计:

  • 阶段一(列表):抓拍卖会 + lot 列表入库,scp_lot.state=0(含成交价/状态/出价,列表页即有)。
  • 阶段二(详情):扫库 state != 1 的 lot,逐条进详情页补 Collection/Sport/Type/Athlete/Team/多图,成功置 state=1,失败置 2

4. 使用方式

4.1 前置

  1. 安装依赖:curl_cffiparsellogurutenacityschedule,以及全局公共库 charley-utils(提供 mysql_pool)。
  2. 建表:执行 create_table.sql(DDL 写操作,需人工确认执行)。
  3. 配置 application.yml 中的 MySQL 连接。
  4. 代理:本地开了 VPN,从大陆直连站点不通,请求必须走代理。见下方「代理配置」。

4.2 运行

# 任务一:历史全量(初始化数据库时跑一次)
python scp_history.py

# 任务二:每周新增(常驻,先立即跑一次,之后每周一 05:00)
python scp_spider.py
  • 每周新增逻辑:GET /auctions/past 首页 → 与库中 event_id 求差集 → 仅新增拍卖会才入库查询;随后补跑阶段二,兜底上一轮失败/中断的详情。

5. 代理配置

代理集中在 scp_core.pyget_proxys()(带重试)。默认使用住宅代理(北美出口,与 SCP 面向的美区一致):

http_proxy = "http://账号:密码@proxy.123proxy.cn:36927"
# 若改用本地 VPN 客户端 HTTP 端口(如 Clash),改成:
# http_proxy = https_proxy = "http://127.0.0.1:7890"

切换本地 VPN 端口时,把 get_proxys() 内两行替换为对应地址即可,其余代码无需改动。


6. 反爬与稳健性

  • 无 Cloudflare / 无 JS 挑战curl_cffi 随机浏览器指纹(impersonate)+ 通用请求头即可,未写死 UA(避免与 TLS 指纹矛盾)。
  • 重试:页面 GET、代理获取均由 tenacity 包裹重试;周调度主函数失败按小时重试。
  • 幂等insert_many(ignore=True) 依赖唯一索引去重,重复跑不会产生脏数据。
  • 限频:站点为静态页,当前未加显式 sleep;如遇风控可在翻页/详情循环处补 time.sleep

7. 踩坑记录

  • 优先接口落空:抓包确认本站无数据接口,全部 SSR,最终走 HTML 解析。
  • 详情属性字段数量不一致(2~5 个、可能缺 Athlete/Team):改为按 label 建字典按需取,避免按固定顺序取值错位。
  • 多图有 small/large 两套:统一归一化到 large 再去重,避免同图重复入库。
  • 时间带时区名EDT/EST):解析时剥离时区名,保留东部墙钟时间入 datetime 字段,原文另存 *_raw 字段。
  • 控制台中文/en-dash 乱码:Windows GBK 终端显示问题,实际数据 UTF-8 正确,入 utf8mb4 库无碍。
  • 下架 lot 的 404 死循环(2026/07/28 修复):个别拍品会被撤下,详情页返回 HTTP 404(页面标题「Nothing Found」)。原先当普通错误重试 5×3 次、置 state=2,而补抓查询 where state != 1 会把 state=2 每轮重新选中 → 该 404 永久循环、阻塞其他待抓 lot。修复:新增 LotNotFound 异常,_get_selector 遇 404/410/软404 直接抛出且 retry_if_not_exception_type 不重试;置终态 state=3;补抓查询改为 where state in (0, 2),跳过已成功(1)与永久不存在(3)。

8. 举一反三

  • 本套「拍卖会 → 图录列表 → 拍品详情」三级 + 两阶段(列表/详情)+ event_id/item_id 去重 + 首页差集增量,是 Bidsquare 系托管拍卖站的通用范式,换其他 Bidsquare 站点只需调整域名与少量选择器。
  • 若某站改为前端渲染(数据走 XHR/JSON),优先复用「浏览器抓包定位接口 → 直接请求接口」路径,比解析 HTML 更稳。