财报批量下载与归置(A 股 + 港股)
把一家上市公司的全部定期报告 PDF 抓到本地、按 sha256 内容去重、剔除摘要/附件、按「公司:YYYY年X报告.pdf」统一命名并归置到 公司/ 子文件夹。
目标目录默认 ~/财报(用户主目录,跨平台),可用环境变量 REPORT_DIR 覆盖(例如 REPORT_DIR="E:\投资\财报" python ...)。
本 skill 的三个关键特性
- 位置 / 自动发现:把本 skill 文件夹直接放到
~/.workbuddy/skills/financial-report-downloader\即可被 WorkBuddy 自动发现(重启会话生效)。 - 零手动映射:
- 深交所
orgId自动从szse_stock.json按代码解析; - 港股
stockId自动从港交所公开活跃股票列表activestock_sehk_e.json按代码查i字段(覆盖全部港股,无需任何手写映射表)。
- 深交所
- 增量下载(已下的不重复下):以「文件是否在盘上」为唯一真相——已落地的绝不重发请求;即使
_done.txt记过某 URL,只要对应文件丢失就会自动重新下载补齐。重跑同一家公司时,只拉取新发布的报告。要强制全部重下,删掉REPORT_DIR/.state/<代码>_done.txt即可(港股还可删_hkex_<代码>.json缓存)。
何时使用
- 用户要下载某只股票的全部业绩/定期报告 PDF。
- 用户要把持仓公司的财报批量归档到本地目录。
- 关键词:「财报」「定期报告」「年报」「半年报」「季报」「业绩报告」「下载公告」。
判定交易所(决定用哪个下载器)
| 代码特征 | 交易所 | 下载器 | 关键标识符 |
|---|---|---|---|
| 60xxxx(600/601/603/605)、688xxx(科创板) | 上交所 SSE | _download_stock.py | 6 位代码即可 |
| 000xxx / 001xxx / 002xxx(主板+中小板)、30xxxx(创业板) | 深交所 SZSE | _download_cninfo.py | orgId(按代码自动解析) |
| 0xxxx(5 位,如 00700) | 港股 HKEX | _download_hkex.py | stockId(按代码自动解析) |
央企/银行很多在 SSE,白酒/医药/传媒很多在 SZSE;不确定时按代码前缀判断,必要时先问用户。
运行环境(Python)
- 脚本仅需 Python 3.10+ 标准库(除可选
pypdf外无第三方依赖),任意 Python 3.10+ 均可运行。 - 推荐用 WorkBuddy 提供的受管运行时(隔离、干净):路径形如
- Windows:
C:\Users\<你>\.workbuddy\binaries\python\versions\<版本>\python.exe - 在 WorkBuddy 设置/终端里可查到确切路径;下文示例中的
PY="<你的Python路径>"即指它。
- Windows:
- 若用系统 Python,建议先建虚拟环境再装 pypdf(见下),避免污染全局环境。
可选的 pypdf(仅 organize_rename.py 用到)
pypdf 只用于「文件名无年份时读 PDF 首页年份」的兜底;缺失时脚本自动降级,其余功能不受影响。需要该功能时安装:
# 方式 A:用 WorkBuddy 受管运行时建 venv 并装(推荐,隔离)
<你的Python路径> -m venv <你的Python路径同级>/envs/default
<你的Python路径同级>/envs/default/Scripts/pip install -r requirements.txt
# 方式 B:用系统 python
python -m venv .venv && .venv/Scripts/pip install -r requirements.txt
若未安装,运行 organize_rename.py 时仅会跳过 PDF 年份兜底,并在 stderr 提示安装命令。
工作流程(每家公司)
Step 1 — 下载(增量,已下的自动跳过)
清单文件放哪:统一传绝对路径
REPORT_DIR/.state/_<拼音简称>_manifest.txt(如<REPORT_DIR>/.state/_maotai_manifest.txt)。不要放在REPORT_DIR根目录——根目录只应有公司子文件夹,混入 txt 会干扰后续核查。
上交所(SSE)
# PY 用任意 Python 3.10+ 即可;推荐用 WorkBuddy 受管运行时(路径见上方「运行环境」)。
PY="<你的Python路径>"
"$PY" scripts/_download_stock.py <6位代码> <公司名> "<标题前缀|可多个用|分隔>" <清单路径.txt>
# 例:
"$PY" scripts/_download_stock.py 600519 贵州茅台 "贵州茅台" _maotai_manifest.txt
"$PY" scripts/_download_stock.py 601677 明泰铝业 "明泰铝业" _mingtai_manifest.txt
"$PY" scripts/_download_stock.py 600690 海尔智家 "海尔智家股份有限公司|海尔智家" _haier_manifest.txt
- 标题前缀参数保留兼容(旧命令仍可用),但现已不参与命名:报告类型与年份直接从标题+披露日推导为规范名
公司:YYYY年X报告.pdf,因此「海尔智家股份有限公司2023年年度报告」之类旧全称也能正确归并。多个候选用|分隔,可留空或仍填公司名。 - 文件直接落到
REPORT_DIR/<公司>/子文件夹,命名即为规范名,下载时按内容 sha256 去重。 - 已下载过的会自动跳过(见 SUMMARY 的 already_skipped 计数);done 记录过但文件丢失也会自动重新下载补齐。
深交所(SZSE,经巨潮资讯 cninfo)
"$PY" scripts/_download_cninfo.py <6位代码> <公司名> <清单路径.txt>
# 例:
"$PY" scripts/_download_cninfo.py 000596 古井贡酒 _gujing_manifest.txt
"$PY" scripts/_download_cninfo.py 002027 分众传媒 _fenzhong_manifest.txt
orgId脚本自动从http://www.cninfo.com.cn/new/data/szse_stock.json按代码解析(覆盖主板/中小板/创业板),无需手动传。
港股(HKEXnews)
"$PY" scripts/_download_hkex.py <5位代码> <公司名> <清单路径.txt>
# 例:
"$PY" scripts/_download_hkex.py 00700 腾讯控股 _tencent_manifest.txt
stockId脚本自动解析:从https://www1.hkexnews.hk/ncms/script/eds/activestock_sehk_e.json按代码查i字段(首次会缓存到REPORT_DIR/.state/activestock_sehk.json)。无需任何手动映射。- 港股下载时直接归置到
REPORT_DIR/<公司>/子文件夹,并统一命名为<公司>:<年份>年<类型>.pdf。
Step 2 — 归置 + 统一命名 + 去重(上交所/深交所必须;港股可选)
把根目录里 <公司>:*.pdf 移入 REPORT_DIR/<公司>/,回收摘要文件,按文件名/PDF 正文年份重命名为统一格式,按内容去重并送回收站。
"$PY" scripts/organize_rename.py <公司名>
# 例:
"$PY" scripts/organize_rename.py 贵州茅台
- 依赖
pypdf(仅用于「文件名无年份时读 PDF 首页年份」的兜底)。若缺失,脚本自动跳过 PDF 年份兜底、不影响其余重命名;安装方式见上方「运行环境 → 可选的 pypdf」。 - 命名规则:
<公司>:YYYY年(半年度报告|年度报告|第一季度报告|第三季度报告)[(修订)|(正文)|(全文)].pdf。 - 年报:文件名年份 = 披露年 − 1(因年报在次年披露);半年报/季报 = 同年。脚本已处理。
- 旧称兼容:「中期报告」→ 半年度报告;中文数字年份「二〇〇六」可识别。
Step 3(可选)— 清理残留附件
若根目录还混有非定期报告(英文版、ESG、审计报告、更正公告等),送回收站:
"$PY" scripts/cleanup_annex.py <公司名>
移植到其他机器
本 skill 代码零机器绑定:无硬编码绝对路径(默认目录 ~/财报 可用 REPORT_DIR 环境变量覆盖),除可选 pypdf 外无第三方依赖,路径一律 os.path.join、中文文件名正常(UTF-8)。拷贝即用。
- 拷 skill 本体:把
financial-report-downloader/文件夹整体复制到目标机器的~/.workbuddy/skills/下,重启 WorkBuddy 即被发现。 - (可选)拷已有归档:把本地归档目录(含其内的
.state\)一并拷过去可保留增量状态,实现零重下;若目标路径不同,用REPORT_DIR环境变量指向新路径即可(如REPORT_DIR="D:\财报" <PY> scripts/_download_stock.py ...)。只拷 skill 不拷归档也行,只是首跑会全量重新下载(无害,仅耗时)。 - Python 环境:见「运行环境」一节。核心下载无需任何安装;若要
organize_rename的 PDF 年份兜底,按该节装pypdf。 - 网络:脚本直连上交所 / 巨潮资讯 / 港交所接口,目标机器需能访问这些站点(通常需大陆网络环境)。
- 跨平台:Windows / macOS / Linux 均可运行;公司名、文件名中的中文均正常。
输出
- 每家公司一个子文件夹:
REPORT_DIR/<公司>/<公司>:YYYY年X报告.pdf - 一份
<清单>.txt:每行文件名 \t 大小 \t 披露日 \t 原标题,含FAIL/dup(...)/already标记,便于核查漏网。 - 终端打印
SUMMARY:新增/已下跳过/去重/跳过非定期/失败 计数。
关键坑(务必遵守,否则会下错/命名错)
-
上交所 WAF:
static.sse.com.cn直链会返回acw_sc__v2JS 挑战页(不是 PDF)。脚本已内置逆向算法(固定 mask + 40 项 posList 置换异或),无需人工干预。 -
年报披露年陷阱:SSE 早期报告文件名常带「披露日」后缀(如
年报_2015-03-25.pdf实为 2014 年报),文件名年份比报告年份大 1。归置时以文件名YYYY年或 PDF 正文年份为准,不要信披露日后缀。 -
只留定期报告:下载器已用
PERIODIC_RE/EXCLUDE_RE过滤,剔除英文版、ESG、审计、专项说明、独董述职、更正公告等附件。若仍有漏网,用 Step 3 清理。 -
借壳/更名:深交所借壳上市的公司,上市前的公告属于原壳公司。如分众传媒(002027)2015 年借壳七喜控股,2015 三季报及以前约 59 份实为七喜控股,应送回收站。
-
cninfo 过滤:必须同时传
stockJSON 和secid=orgId双参数,否则会漏掉部分公告。 -
港股业绩公布 ≠ 定期报告:腾讯等港股有「中期/全年/九个月/三个月业绩公布」,与中期报告/年报重复,脚本已剔除重复项,只保留年报、中期报告、一/三季度业绩公告。
-
港股年报命名不只「年報」二字:部分港股(如阿里巴巴 09988、京东等)年报标题用财年命名「2020財政年度報告」「2023財務年度報告」,不含「年報」。脚本 classify() 已同时识别「年報 / 財政年度報告 / 財務年度報告」(且先判「中期報告」,避免「2024財務年度中期報告」被误判为年报)。实测 2026-08-20:阿里 7 份年报(2020~2026 财年)补齐,含 28~40MB 大文件,下载耗时较长(单份 30s+),Bash 超时误报 Exit 1 时检查 .pdf 是否已落盘即可。此外,阿里式季报标题用「YYYY年M月底止季度業績公告」(如「2026年六月底止季度業績公告」即 FY2027 Q1),classify() 已支持「業績公告」+「月底止季度」格式(同时兼容腾讯式「X個月業績公佈」),并按阿里财年(止3/31)做年度映射(3月底→当年FY;6/9/12月底→次年FY),保留一/三季度业绩公告、跳过与中报/年报重叠的二/四季度。实测 2026-08-21:阿里 FY2027 Q1 等 13 份季报补齐。
-
增量以「文件是否在盘上」为唯一真相:脚本每轮先扫
REPORT_DIR根目录 +REPORT_DIR/<公司名>/下的同名 PDF;盘上已有的绝对不重发请求。REPORT_DIR/.state/<代码>_done.txt仅作已处理 URL 的辅助记录——即使 done 里有某 URL,只要对应文件丢失/被删,下一轮就会自动重新下载补齐(实测 2026-08-20:茅台 2025 年报文件丢失,done 记着却补不回来,已修复为文件在盘才跳过)。需要强制全量重下时,删该 done 文件(港股再删_hkex_<代码>.json)即可。 -
别改公司文件夹名:改名会让扫盘(第②道闸)失效;若同时
_done.txt也被删,就会全量重下一遍(实测 2026-08-20:茅台因此重下 88 份)。下载前先确认子文件夹名与传入的<公司名>完全一致;发现重复目录时先做内容 sha256 比对,再决定处置,不要直接删。 -
SSE 窗口已覆盖 2000 年起:查询窗口从
2000-01-01开始(每段 ≤3 年),上市初期(2001~2004)的年报/季报/半年报在 SSE 电子档案里都能查到。早期公告标题常无年份(如「贵州茅台年报」),报告年份按披露日推断(年报披露年 − 1;如 2002-04-17 披露 → 2001 年报)。实测 2026-08-20:为茅台补下 2001~2004 年报及 2005 年一/三季报、半年报共 16 份,增量机制正确跳过已有 88 份。 -
命名保留版本标记(修订/正文/全文):同一报告在 SSE 可能有多个版本(如「贵州茅台年报(修订版)」「...季度报告正文」),下载器
canon_qual会把版本保留到文件名((修订)/(正文)/(全文)),避免同一年份同类型退化成(1)(2)序号。实测 2026-08-20:茅台 2007/2010 年报(修订版)、2020 一/三季报与 2021 一季报(正文版)均正确区分。另:脚本会自动创建<公司>/子文件夹,REPORT_DIR指向全新目录也可直接全量下载。 -
cninfo 抓取缓存(真实已实现):
_download_cninfo.py首次抓全量公告后写REPORT_DIR/.state/_cninfo_<代码>.json,重跑直接读缓存、跳过网络抓取。实测五粮液 1594 条:首跑 ~53s → 缓存命中 ~11s(提速 ~5×)。删该 json 即强制全量刷新(与港股_hkex_<代码>.json同理)。 -
更新前/更新后 命名归一(真实已实现):
organize_rename.py对成对报告——「更新后/更正后」= 规范名(权威/最新版,无后缀),「更新前/修订前」标(修订前);仅一份时不加后缀;不再退化成(2)。五粮液 2025 年半年度/一/三季度报告三对已按此归置(报告.pdf+报告(修订前).pdf)。 -
归置幂等性(真实已实现):
compute_target识别并保留文件已有的(n)后缀(如 2022 年同目标两份不同内容 →规范名+(2)),重跑不再(2)→(3)递增;(修订前)/(修订)/(正文)/(全文)均作为稳定后缀识别,重命名完全幂等(五粮液连续两次重跑均renamed=0)。
详见 references/identifiers.md。
Scan to join WeChat group