财报 PDF 转Markdown:六个坑

### 先说结论(TL;DR)

- 你要**数字**,中日韩三个市场都有现成的结构化数据源。
- 你要**正文**——管理层讨论、附注、风险因素、在建工程明细——三个市场的官方源都只给 PDF / XBRL,得自己解析。
- 而正文才是 LLM / RAG 分析真正吃的东西:**数字告诉你"是什么",正文告诉你"为什么"。**
- 我把三个市场的官方披露解析成了 Markdown,做成一个 API,一行 curl 拿全文。下面给可跑的示例,也把踩过的坑都写出来,你自己做的话能少走弯路。

---

### 一、为什么"能取到数字"不等于"能取到财报"

举个最典型的场景:某公司今年营业利润掉了一半,你问大模型为什么。

结构化数据能告诉你的只有那个数字。原因写在**正文**里——「管理层讨论与分析」里的产能和价格说明、「风险因素」里的汇率和原材料、附注里的资产减值明细。这些内容在 XBRL 的数字里不存在,只存在于财报 PDF 的第 30 页到第 150 页。

所以做 A 股研究、日股筛选、韩股尽调,或者做财报问答的 LLM 应用,最后都会卡在同一个问题上:**全文从哪来。**

---

### 二、三个市场的官方源地图

先把地图画出来,不然后面全是空谈。

| 市场 | 官方披露源 | 有官方 API 吗 | 原始格式 | 主要坑 |
|---|---|---|---|---|
| 🇨🇳 中国 A 股 | 巨潮资讯网(证监会指定披露平台) | 无公开 API | PDF | 表格是线条画的、扫描件、单位混用 |
| 🇯🇵 日本 | EDINET(金融厅) | 有(下载 XBRL / PDF) | XBRL + PDF | XBRL 只有数字,正文在 PDF;字体编码坑 |
| 🇰🇷 韩国 | DART(金融监督院) | 有 Open API | PDF / HTML 混杂 | 限速严(2 万次/天)、文档格式不统一 |

补两个容易混淆的点:

- **日本**:J-Quants 是 JPX 官方的结构化 API,做数字很好用,但免费档数据延迟约 12 周、且限个人使用。它给的是**数字**,不给正文——「経営方針」「リスク情報」这些还是得回 EDINET 的 PDF 里拿。
- **韩国**:DART 的 Open API 能拿到文档列表和原文文件,但原文格式不统一,有的年份是 PDF、有的是 HTML,字段口径也不一致。

一句话总结:**三个市场,数字各有各的路,正文都是 PDF/XBRL 苦力活。**

---

### 三、自己动手,要趟的六个坑

这部分是这篇文章的主要价值。以下都是我们实际踩过的(我们做的是东亚三市场的全文解析),你自己做的话一定会遇到。

#### 坑 1:财报表格不是文字,是线条

财报里的三大表(资产负债表、利润表、现金流量表)看起来是表格,但在 PDF 里很多是**用矢量线条画出来的**——文字是按坐标散落的独立片段,没有任何"表格结构"信息。你用常规的文本抽取,拿到的是一坨按坐标乱序拼接的文字。

要做的是:检测横线(`l` 线段原语 + 矩形边框)和竖线,重建行列网格,再把文字块按坐标归位。我们最初的检测器只认某一种线段原语,**有表格的文档命中率只有 47%**;兼容第二种原语 + 补上竖线检测之后做到了 **96%**,另外 4% 是真·扫描件。

#### 坑 2:字体没有 ToUnicode 映射,整篇是乱码

这是最阴的一个。部分东亚 PDF 用 Identity-H 编码嵌入字体,**却不带 ToUnicode 映射表**——意思是 PDF 里存的是字形编号(GID),不是字符。PyMuPDF 这类库只能原样返回编号,抽出来的正文整篇是乱码。

我们实测过某一类文档,**乱码率高达 42%**(正常文档的异常码位占比在 0.5% 以下,所以是能自动判别的)。解决办法是自己建 GID → 汉字的映射表。顺带一个坑:判断"乱码"时**不能把 CJK 兼容区(U+F900–FAFF)当异常**——老 Big5 会把「年/金/行/利」放在那个区,误判一次要误伤几百篇。

#### 坑 3:静默丢数据,比报错可怕得多

采集端最危险的不是失败,是**看起来成功**:

- 限流时接口返回 200,但正文内容是「查询过量,请稍后再试」
- 下载到的 PDF 是个占位页(小于 20KB)
- PDF 被截断,解析库"修复"后返回半篇

这些都会被老实的代码记成"这篇没有正文"。我们曾经因为这个逻辑,把某个市场的整个年份段静默丢空,后来才发现。**所有"空结果"都必须当成异常处理**,而不是当成事实。

#### 坑 4:单位会骗人

「元 / 万元 / 百万元」「円 / 千円 / 百万円」在同一批文档里混着用,而且不同公司、不同市场习惯都不一样。LLM 不会自己判断——它会把「1,234(万元)」当成 1,234 元。

解法:在文档头部把单位抽出来显式标注,让下游不用猜:

```markdown
> 单位:元  币种:人民币
```

#### 坑 5:扫描件必须 OCR 兜底

大约 **4%** 的文档是纯扫描图,没有文本层,任何解析都是白干,必须走 OCR。比例不大,但如果你跳过它,用户一搜就会发现"这家公司的报告怎么是空的"。

#### 坑 6:更正公告和增量

财报会出"更正版",同一报告期可能有多个版本。你的 pipeline 得能处理版本关系和增量更新,否则会出现同一期两份内容打架。

---

### 四、如果不想自己趟这些坑

上面每一坑都是几周的活。我把三国的官方披露都解析成了 Markdown,做成一个 REST API(DataSinking),你直接调:

**中国 A 股(贵州茅台 2023 年报)**

```bash
curl "https://api.datasink.ing/documents?symbol=600519.SS&doc_type=annual&report_period=2023-12-31&with_content=1&size=1&apikey=你的KEY"
```

**日本(丰田 有価証券報告書)**

```bash
curl "https://api.datasink.ing/documents?symbol=7203.T&doc_type=annual&with_content=1&size=1&apikey=你的KEY"
```

**韩国(三星电子 사업보고서)**

```bash
curl "https://api.datasink.ing/documents?symbol=005930.KS&doc_type=annual&with_content=1&size=1&apikey=你的KEY"
```

返回的正文长这样(带 YAML frontmatter,身份/期间/类型都在头部):

```markdown
---
stock_code: "600519"
stock_name: "贵州茅台"
report_period: "2023-12-31"
announcement_date: "2024-04-03"
doc_type: "annual"
title: "贵州茅台2023年年度报告"
---

> 单位:元  币种:人民币

| 项目                     | 本期发生额        |
| 营业总收入               | 150,560,000,000  |
| 归属于母公司股东的净利润  |  74,734,000,000  |
```

**Symbol 沿用 FMP / Yahoo 的写法**:`600519.SS`(上交所)、`000001.SZ`(深交所)、`7203.T`(东证)、`005930.KS`(韩交所)。你现有的 pipeline 不用改格式。

**覆盖量**(2026-09 实测,每天增量更新):

| 市场 | 公司数 | 文档数 | 源 |
|---|---|---|---|
| 中国 A 股 | 5,500+ | 28 万+ | 巨潮资讯网 |
| 日本 | 4,452 | 13.5 万+(2015 年至今) | EDINET |
| 韩国 | — | 7.7 万+ | DART |

---

### 五、给 LLM / RAG 用:按章节取,别塞一整篇

如果你在做财报 RAG,最容易犯的错是把 14 万字的年报整个塞进向量库。每份报告都切好了章节,可以只取你要的那一章:

```bash
# 1) 先列出报告,拿到 id(不带 with_content 则只返回元数据,很快)
curl "https://api.datasink.ing/documents?symbol=600519.SS&doc_type=annual&size=1&apikey=你的KEY"
# → {"items":[{"id":3,"report_period":"2025-12-31", ...}]}

# 2) 列出这篇报告切好的章节
curl "https://api.datasink.ing/documents/3/sections?apikey=你的KEY"
# → {"sections":["重要提示","第一节释义","第二节公司简介和主要财务指标",
#                "第三节管理层讨论与分析", ...]}

# 3) 只取「管理层讨论与分析」这一章
curl "https://api.datasink.ing/documents/3?section=管理层讨论与分析&apikey=你的KEY"
```

同样的 `sections` / `section=` 模式在三个市场都能用——日本的「経営方針」、韩国的「사업의 내용」都是章节级的。

**接入大模型**有两条路:

- **OpenAPI**:把 `https://api.datasink.ing/openapi.json` 丢给 ChatGPT / Claude,它自己读文档、调接口、报数字。
- **MCP**:`pip install "datasinking[mcp]"`,或者在 Claude / ChatGPT 里直接接 `https://api.datasink.ing/mcp`(6 个工具),然后就能问「丰田最新有価証券報告書里的风险因素是什么」。

---

### 六、定价

| 档位 | 价格 | 限制 | 适合 |
|---|---|---|---|
| 免费版 | **$0** | 3 次/秒,共享每日额度 | 评估、轻量使用 |
| 年付版 | **$31 / 年** | 31 次/秒 + 批量下载 | 生产使用 |

免费 key 填个邮箱就能拿:`POST /free-key`,或者直接上 [datasink.ing](https://datasink.ing) 点「Get an API key」。

---

### 七、常见问题

**Q:数据来源可靠吗?**
A:全部来自各市场的官方公开披露平台——中国的巨潮资讯网(证监会指定)、日本的 EDINET(金融厅)、韩国的 DART(金融监督院)。我们做的是**格式转换**:把官方 PDF/XBRL 转成结构化 Markdown,不修改内容。

**Q:多久更新一次?**
A:每天增量采集 + 推送,新披露的文档当天上线。

**Q:正文质量怎么保证?**
A:有自动质检——空正文、乱码(异常码位占比)、截断 PDF 都会被拦下来重采,不会以"没内容"的形式蒙混过关。

**Q:可以商用吗?**
A:可以,付费档不限制用途。具体条款见网站 terms。

**Q:为什么不自己做,非要用 API?**
A:如果只取一两家公司的几篇报告,自己做完全可行(巨潮/EDINET/DART 都能免费下)。但要做全市场、要覆盖二十年、要处理上面那六个坑,这是持续的工程活——增量、更正、质检、限流,一样都不能少。这活我们替你干了。

---

### 一句话

**东亚三个市场的财报全文,从官方披露到干净的 Markdown,中间只差一个 API。**

👉 https://datasink.ing · 文档 https://datasink.ing/zh/docs
posted @ 2026-09-14 13:51  heubme  阅读(2)  评论(0)    收藏  举报