Back to skills
extension
Category: Data & AnalyticsNo API key required

港交所披露易下载器

自动从港交所披露易(DION)批量下载权益披露PDF,支持多关键词匹配、日期范围过滤、披露类型过滤。

personAuthor: user_656c2e70hubcommunity

港交所披露易下载

使用 Playwright 浏览器自动化,从港交所披露易(DION)下载权益披露PDF。

前置条件

pip install playwright beautifulsoup4 pypdf
python -m playwright install chromium

Windows 沙箱环境建议用 managed Python venv: C:\Users\cxq\.workbuddy\binaries\python\envs\default\Scripts\python.exe

执行脚本

核心脚本位于 scripts/download_disclosure.py

配置参数

| 参数 | 说明 | 默认值 | |------|------|--------| | --stock | 港股代码 | "09992" (泡泡玛特) | | --start / --end | 披露日期范围 | "2025-01-01" / "2026-06-25" | | --keyword | 股东名称关键词,逗号分隔,任一命中即纳入 | "Duan Yong Ping,H&H" | | --type | 披露编号类型过滤,逗号分隔(如 IS,CS);默认全部 | 空 | | --dir | PDF保存目录 | ~/Desktop/泡泡玛特 | | --headless / --visible | 无头模式 / 显示浏览器 | 启用无头 |

运行

# 推荐:命令行方式(段永平+H&H 关于泡泡玛特的全部披露)
python scripts/download_disclosure.py \
  --stock 09992 \
  --keyword "Duan Yong Ping,H&H" \
  --start 2025-01-01 \
  --end 2026-06-25 \
  --dir ~/Desktop/泡泡玛特

# 只下载个人大股东通知(IS)
python scripts/download_disclosure.py --stock 09992 --keyword "Duan Yong Ping,H&H" --type IS

# 显示浏览器窗口调试
python scripts/download_disclosure.py --visible

工作流程(v2)

[1] 启动 Chromium (Playwright)
[2] 访问港交所披露易搜索页 (NSSrchCorp.aspx),填表提交搜索
[3] 搜索结果页列出4个报告类别入口:
    - 大股東完整名單(仅指定日期在册大股东最新一次披露)
    - 大股東綜合名單
    - 由大股東所提交的披露權益通知一覽表(★ 全部历史披露,本脚本用这个)
    - 董事完整名單
[4] 点击进入"由大股東所提交的披露權益通知一覽表"
[5] 解析列表页,按 KEYWORD(任一命中) + TYPE 过滤,自动翻页
[6] 逐条:context.new_page() + goto(NSFormXPrint.aspx) → page.pdf() 生成干净PDF

关键实现细节(v2 核心修复)

1. 报告类别入口选择(最关键)

港交所披露易搜索结果页有4个报告类别入口。**必须选"由大股東所提交的披露權益通知一覽表"**才能拿到日期范围内的全部历史披露通知。

  • ❌ "大股東完整名單":只显示指定日期当天的在册大股东最新一次披露,会漏掉历史建仓过程中的多次披露。v1 误用此入口,导致段永平8份披露只拿到2份。
  • ✅ "由大股東所提交的披露權益通知一覽表":列出日期范围内大股东提交的每一份披露通知。

2. PDF 生成方式:context.new_page() + goto

v1 用 source_page.evaluate('window.open(url, "_blank")') + expect_page 打开详情/Print页。但在 headless Chromium 下,无真实用户手势的 window.open 会被静默拦截,新标签页始终不出现,expect_page 超时。

v2 改用:

ppage = context.new_page()
ppage.goto(print_url, wait_until="domcontentloaded")
time.sleep(4)  # 等JS渲染表格
ppage.pdf(path=..., format="A4", print_background=True, margin={...})
ppage.close()

Print 页面是纯展示页,URL 自带 fn/sid/corpn 参数,goto 不依赖 ASP.NET session,稳定可靠。

3. Print URL 通用构造

披露详情页有 NSForm1.aspx(个人 IS)、NSForm2.aspx(法团 CS)等多种。Print 页面 URL 构造用通用正则:

re.sub(r'NSForm(\d+)\.aspx', r'NSForm\1Print.aspx', detail_url)
  • NSForm1.aspx → NSForm1Print.aspx(个人大股东通知)
  • NSForm2.aspx → NSForm2Print.aspx(法团大股东通知)

v1 只硬编码替换 NSForm1,导致 CS 法团披露的 Print URL 替换失效,回退到详情页 page.pdf(),生成带导航栏遮挡的版本。

4. 多关键词匹配

段永平通过 H&H International Investment, LLC(法团)和个人双名义披露,同一权益事件会同时生成 IS + CS 两份通知。--keyword 支持逗号分隔,任一命中即纳入:

--keyword "Duan Yong Ping,H&H"

5. PDF 完整性校验

生成后用 pypdf 检查首页文本是否含导航关键词("上市公司文件"/"股權披露"/"披露權益")。若含则判定为带遮挡的详情页版本,触发重试或兜底。

6. Windows 控制台编码处理

PowerShell stdout 默认 GBK,无法输出 ✓✗★ 等 Unicode 符号会触发 UnicodeEncodeError 导致脚本崩溃。脚本顶部强制:

sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")

同时关键日志写入 scripts/_run.log(UTF-8),便于乱码时排查。

记录类型说明

披露编号前缀:

  • IS = Individual Substantial Shareholder(个人大股东通知,NSForm1)
  • CS = Corporate Substantial Shareholder(法团大股东通知,NSForm2)
  • DA = Director Acquisition(董事权益通知)

脚本默认全部下载,可用 --type IS,CS 过滤。

常见问题

| 问题 | 原因 | 解决 | |------|------|------| | 只下载到少量记录 | 误入"大股東完整名單"(仅最新) | 改入"由大股東所提交的披露權益通知一覽表"(v2已修复) | | PDF 含导航栏遮挡 | Print URL 替换只处理 NSForm1,CS 法团用 NSForm2 失效 | 用通用正则 NSForm(\d+)\.aspx 替换(v2已修复) | | PDF 生成失败/新标签不打开 | headless 下 window.open 被弹窗拦截 | 改用 context.new_page() + goto(v2已修复) | | UnicodeEncodeError 崩溃 | PowerShell stdout 是 GBK,无法输出 ✓ 等符号 | 脚本顶部 sys.stdout.reconfigure(utf-8)(v2已修复) | | 文件生成成功但脚本判失败 | MIN_PDF_SIZE(字节) 与 size_kb(KB) 单位混淆 | 用 fsize(字节) 比较,勿用 size_kb(v2已修复) | | 搜索按钮点击超时 | click() 默认等待导航 | click(timeout=10000) + sleep(8),不依赖导航等待 | | Print页面加载超时 | 用了 networkidle | 改用 domcontentloaded + sleep(4) | | 详情页 .pdf 链接全是指南 | 港交所不提供独立PDF下载,内容在HTML表格 | 用 Print 页面 page.pdf() 生成 |

踩坑记录(避坑指南)

坑1:报告类别入口选错(v1→v2 最重大修复)

  • 现象:段永平关于泡泡玛特应有8份披露,v1只下载到2份
  • 根因:v1 点击"大股東完整名單",该入口只返回指定日期当天在册大股东的最新一次披露(5条:王宁/GWF/UBS/段永平IS/H&H的CS),把历史建仓过程的多次披露折叠了
  • 正确做法:点击"由大股東所提交的披露權益通知一覽表",该入口列出日期范围内的每一份披露通知
  • 验证:v2 在该入口下找到8份(3次事件×2~4份通知),与用户预期一致

坑2:headless 下 window.open 被拦截

  • 现象source_page.evaluate('window.open(url, "_blank")') 执行不报错,但新标签页始终不出现,expect_page 超时
  • 根因:headless Chromium 对无用户手势的 window.open 静默拦截。v1 之所以成功,是因为点击"大股东完整名单"链接是手势动作,后续在那个上下文里 window.open 偶然可行;v2 报告列表页是"当前页跳转"返回,后续 evaluate 的 window.open 无手势上下文
  • 解决:改用 context.new_page() + page.goto(print_url),完全不依赖 window.open

坑3:Print URL 只替换 NSForm1

  • 现象:CS 法团披露(NSForm2.aspx)的 Print URL 替换失效,回退到详情页 page.pdf(),生成带导航栏遮挡版本
  • 根因:v1 硬编码 url.replace("NSForm1.aspx", "NSForm1Print.aspx"),对 NSForm2.aspx 无效
  • 解决re.sub(r'NSForm(\d+)\.aspx', r'NSForm\1Print.aspx', url) 通用替换

坑4:Windows PowerShell GBK 编码崩溃

  • 现象:脚本输出 时触发 UnicodeEncodeError: 'gbk' codec can't encode character '\u2713'
  • 根因:PowerShell stdout 默认 GBK 编码,无法输出 Unicode 符号;且异常处理分支里打印符号会导致二次崩溃,掩盖真实错误
  • 解决:脚本顶部 sys.stdout.reconfigure(encoding="utf-8");关键日志同时写 scripts/_run.log(UTF-8)

坑5:PDF 大小单位混淆

  • 现象:154KB 的干净 PDF 被误判为失败,错误触发兜底(详情页,有遮挡)覆盖了干净文件
  • 根因MIN_PDF_SIZE=5000(字节)与 size_kb(KB)直接比较,相当于要求 5000KB=5MB
  • 解决:用 fsize(字节)与 MIN_PDF_SIZE(字节)比较:return fsize > MIN_PDF_SIZE, ...

坑6:详情页 .pdf 链接全是导航指南

  • 现象:NSForm1.aspx 详情页中 href*=".pdf" 匹配到的都是 PredefinedSearchGuide_c.pdf 等操作指南,非披露文件
  • 根因:港交所披露内容直接渲染在 HTML 表格,不提供独立 PDF 下载
  • 结论:绝对不要通过 .pdf 后缀匹配披露文件,必须用 Print 页面 page.pdf() 生成