
年报季谁翻财报谁知道。打开一份几百页的PDF先在目录里找“合并利润表”翻到对应页码对着表格一格一格地抄数据营业收入、营业成本、净利润……一套十来项指标复制粘贴加手工校验一家公司少说也要十几分钟遇到扫描版PDF还得自己敲数字。要是碰上几十家公司一天基本就耗在这上面了眼睛看花不说稍不留神还能把“归母净利润”和“净利润”填串行。我后来搭了一套自动化抽取方案核心组合是文本解析平台TextIn和AI应用编排平台Coze。简单说用TextIn把财报PDF解析成结构化文本再让Coze编排一个工作流自动抽取指标、计算派生数据、回填到表格里。配置好之后单份财报从上传到生成结果只需要几分钟批量处理时体验更明显。这个方案不需要太深的编程功底财务分析师、投研助理、代码基础薄弱但想提效的人都能上手下面把整个搭建过程和踩过的坑从头到尾讲清楚。1. 核心思路拆解为什么是“解析 编排”这套组合1.1 问题本质财报里的数据难点从来不是“读”而是“结构化”很多人在接到“财报数据抽取”这个需求时第一反应是写Python脚本、用开源解析库。但真正跑过一轮就会明白难点不在“识别文字”而在“把散落的文字还原成有业务含义的字段”。一份财报PDF里有几百个表格、上千个单元格还有合并单元格、跨页断行、科目名称换行、货币单位不统一等各种情况。我最初也试过纯代码路线用解析库把PDF转成文本再靠正则表达式去匹配“营业收入”后面的数字。结果并不理想有些财报的“营业收入”后面紧跟的是“营业总收入”有些表格里“营业收入”下方还有“其中营业收入”一行正则表达式写来写去越来越复杂换个公司、换个报告期就可能匹配失败。这属于典型的“用工程方法解决文本理解问题”维护成本极高。后来我才把思路调整为先用专业的文档解析服务把财报转成结构化数据再用AI大模型去理解和抽取字段最后用低代码工作流把整条链路串起来。这套组合的好处是各干各擅长的事TextIn负责解决“非结构化数据怎么变规整”的问题Coze负责解决“流程怎么自动编排、AI怎么调用”的问题我不需要维护复杂的解析逻辑也不用关心服务器的稳定性。1.2 技术选型给PDF装一只眼睛再给流程配一个调度员TextIn在这套方案里的角色相当于给PDF装了一双“看得懂表格”的眼睛。它能把财报里的表格结构识别出来输出成有序的文本块甚至支持按坐标定位某个单元格。这对于财报这种“位置即语义”的文档特别关键——财报里的数据光看文字本身没法确定含义必须结合它所在的表名、行名、列名来判断是哪个指标。Coze的角色则更像一个总调度员。它负责把“文档解析”“指标抽取”“数值校验”“结果回填”这些步骤串成一条自动流程每一步之间可以传递数据。尤其方便的是它内置了交互式的AI应用节点不需要我写一堆调用大模型的繁琐代码就能自定义提示词让AI从解析结果里提取指定的财务指标并且规定输出为JSON格式。这套组合对非程序员尤其友好。之前一些同事想让我搭个工具帮他们减轻底稿工作量但一提Python环境、依赖安装很多人就直接放弃了。而TextIn和Coze都是可视化配置全程不需要部署任何本地环境浏览器打开就能操作。即使完全不懂代码按照流程照猫画虎也能搭出基础版本。1.3 整体数据流五步完成从PDF到数据表我最终跑通的方案整体链路是固定的五步上传财报PDF到解析平台的对应解析任务中触发文档解析解析平台返回带有页码、坐标、内容的文本块结构Coze工作流接收到结构化文本调用大模型节点按照预设提示词抽取营收、净利润、总资产等核心科目用代码或公式节点计算毛利率、ROE、资产负债率等派生指标将结果写入目标表格Excel、飞书多维表格、数据库均可同时输出人工复核标记。这五步听起来简单实际执行中每一步都有不少细节。尤其是字段设计、提示词写法、异常值判断这三块直接决定抽取结果靠不靠谱。我后面几张表里会详细拆开说。处理环节传统人工方式自动化方案节省时间定位报表页码看目录、手翻页一份5-10分钟解析服务自动定位约80%提取指标数据肉眼核对、手输Excel大模型按提示词抽取约90%计算派生指标手工拉公式工作流内自动计算约100%多份财报汇总逐份复制粘贴批量任务并行处理约90%说“5分钟”其实包含两层意思一层是配置好完整流程大概只需要5分钟左右另一层是单份财报从上传到拿到结构化结果实际运行也就几分钟。两个维度都实打实。2. TextIn端配置把财报PDF变成机器能读懂的文本2.1 创建解析任务与报表类型选择TextIn的使用入口很直接注册后进入控制台先创建一个解析应用。创建时最关键的一步是选择处理类型因为平台内置了针对不同版式的优化模型选错类型会直接影响识别效果。财报PDF属于“表格文字混合”的文档我建议优先选支持阅读顺序输出和表格结构还原的处理模式这样后面的AI节点拿到的是有序文本而不是乱序字符流。创建完成后解析页面上传一份财报PDF做测试。测试目的只有一个——确认平台输出的文本结构是否符合预期。打开返回结果重点看三件事表格中的科目名称和数据有没有被拆到不同文本块“合并利润表”“资产负债表”等表头是否被正确识别跨页表格的上下文有没有粘在一起。如果表格结构还原得好你会看到每一行几乎都保持“科目名称 数字”的顺序这就为后面大模型抽取打下了好基础。相反如果结果里全是戛然而止的碎片文本那就要考虑换一种处理类型或者开启额外的版面分析选项。我在这个环节踩过最大的坑是有些财报PDF本身是电子版有些是扫描版。扫描版的识别速度和处理精度都不一样第一遍测试时最好用一份扫描版财报最差情况能跑通其他情况就都不是问题了。2.2 字段设计先想清楚要抽哪些指标再配置模板很多人拿到解析结果后急着让AI去“读报表”这其实本末倒置了。正确的做法是先想清楚业务上到底需要哪些字段然后让AI只针对这些字段做抽取。字段设计得越明确大模型的输出越可控后期交叉复核也越轻松。以我处理A股财报的经验三个核心报表里必抽的字段大概是这些报表必抽字段备注利润表营业总收入、营业收入、营业成本、销售费用、管理费用、财务费用、归母净利润、扣非净利润注意区分“净利润”和“归母净利润”资产负债表总资产、总负债、所有者权益合计、货币资金、应收账款、存货关注期末数还是期初数建议两个都抽现金流量表经营活动现金流净额、投资活动现金流净额、筹资活动现金流净额注意正负号很多初学者会漏掉负数每股数据与回报基本每股收益、加权净资产收益率(ROE)有些在利润表附注里需要提示模型到补充资料里找字段不是越多越好。字段多一方面容易让大模型产生幻觉把“营业收入”跟“营业总收入”混淆另一方面也增加校验难度。我的习惯是第一版只保留上面这19个核心指标跑通后再按需增加。提示设计字段时一定要明确“取合并报表数据还是母公司报表数据”。A股财报里往往同时存在合并利润表和母公司利润表两者科目相同但数字完全不同。我通常统一取“合并”这一列并在提示词里用一句话锁定。2.3 配置解析结果字段用“同目录填写”的思路处理表格定位TextIn支持基于模板配置结果字段让解析结果直接输出成业务字段名。这一步相当于提前把“大模型抽取”的一部分工作固定下来减少后续AI环节的负担。具体操作逻辑可以理解成一个“同目录填写”的思路先在解析结果里框选“营业收入”这个单元格所在位置告诉平台“我要提取这一行的数据”平台记住这个位置特征后后续文件只要版式相近它就会自动匹配并取出对应数值。当然由于各家财报的科目排序不完全一致做法上不能死板地只靠坐标要结合表头字段去做动态定位。我在模板配置阶段的实用经验有三条第一字段别名要写全。比如“货币资金”在部分银行财报里叫“现金及存放中央银行款项”只设一个名称容易漏建议在同名字段底下配置多个别名。第二灵活使用以“表名科目名”定位的方式。不要只选一个单元格而是把单元格所在的行首科目名也一起包含进来相当于让平台按“某某表里的营业收入那一行”来找跨版本兼容性明显更强。第三对不存在的科目要设置默认值。比如有些公司没有“其他收益”这行解析时就会跳过如果不做处理后续大模型可能把上一个数值错误地带过来。我在模板里给这类字段设置了“空值填充为0”的后处理规则。2.4 处理扫描件、跨页和特殊排版财报PDF有个特点很多重要数据正好在表格的延续位置比如利润表最后一行“净利润”经常被挤到下一页顶部或底部。如果解析平台没有正确处理跨页表格原本应该属于同一张表的上下文会被硬生生切断。针对跨页问题我常用的补救方法是在配置界面把“阅读顺序”调整为“按表格块输出”尽量让一个逻辑表格的连续文本块紧挨在一起同时把大模型的上下文范围放宽让它看到的是整个利润表相关的文本块而不是零散的段落。扫描件的问题更普遍很多早期年报全是扫描图字体模糊、带水印、有折痕。这种文档不能指望一次解析到位我建议开启“图像增强”类处理参数让平台先做倾斜矫正和对比度优化。另外扫描件中“0”和“O”、“1”和“I”经常混淆如果抽取结果里出现明显异常比如总资产突然多一个零一定要回到原始PDF位置做人工确认不能盲目相信解析结果。3. Coze工作流搭建把解析结果变成最终可用的数据3.1 创建工作流节点怎么排数据怎么传TextIn端把文本结构准备好之后真正“自动化”的核心就到了Coze这边。我一般先新建一个“开源插件”或“自定义应用”类型的流程然后在画布上把节点按顺序拖出来。节点布局上我推荐如下顺序开始节点接收文件URL或上传路径文档解析节点调用TextIn能力传入文件地址拿到解析后的JSON文本大模型节点把解析结果整段喂给大模型同时附上提示词和输出约束代码节点对大模型返回的JSON做二次处理计算派生指标、补齐空缺字段数据回填节点把最终结果写入飞书多维表格或Excel在线文档。这里最需要注意的是节点之间的字段传递。Coze工作流里前一个节点的输出会成为后一个节点的输入变量。为了让下游节点不迷路我在大模型节点的输出约束里会明确要求返回固定的JSON字段名比如revenue、net_profit、total_assets这样代码节点里直接用变量名取值即可。一开始我犯过一个错误在大模型节点里把所有抽取结果塞进一个长字符串然后在回填节点里用正则去拆结果经常拆错。后来改成让大模型直接输出严格JSON格式回填节点按Key取值稳定多了。这也提醒大家流程设计上越早让数据变成结构化格式后面的麻烦越少。3.2 大模型节点提示词把“岗位说明书”写得清清楚楚大模型节点是整套流程里最灵活也最容易翻车的环节。它的作用是从解析出的长文本中提炼指定财务指标字段本质上相当于给一个临时分析师布置工作。只不过这个分析师的理解能力很强但耐心有限——你不在提示词里说清楚需求它就会按自己对公司财报的理解“自由发挥”。我目前在用的提示词模板经过多轮迭代后基本稳定核心结构是四段式你是财报数据抽取专家。请从下面给定的财报解析文本中按字段清单提取数据。 字段清单及说明 - revenue营业总收入合并利润表本报告期金额 - net_profit归属于母公司股东的净利润 - total_assets资产总计期末余额 - total_liabilities负债合计期末余额 - equity所有者权益合计期末余额 - ocf经营活动产生的现金流量净额 提取要求 1. 只输出JSON不要输出任何解释。 2. 缺失的字段填null不要猜测。 3. 金额单位统一为元如果原文是“万元”“亿元”先换算成元再输出。 4. 只取合并报表数据。 解析文本内容如下这里有几个关键细节值得多说两句。第一金额单位问题。“单位统一为元”这条约束极其重要否则今天输出的是百万元、明天输出的是万元最终汇总时对不上账。我在代码节点里还加了一道兜底逻辑如果大模型返回的数值明显异常比如总资产小于净利润就自动标记该条记录为“需复核”。第二“只取合并报表数据”必须明确。很多财务新手分不清合并和母公司报表如果提示词不写大模型通常会按文本中出现位置先抽取结果就是一会儿是合并的、一会儿是母公司的数据完全不可比。我在提示词里用一行字锁定后这个问题基本绝迹。第三关于“缺失填null”。以前我不加这条大模型经常自作聪明地拿上一期数据或相近科目来补看起来“填满了”实际上全是错的。后来在提示词里强制“缺失就写null”并且在后续节点里把null统一标记为待人工补录。注意大模型的输出温度建议设置为0也就是最保守的采样模式。抽取任务不是创作任务需要的是稳定性和确定性不要让它“发挥”。3.3 代码节点计算派生指标并处理异常值大模型直接输出的字段能满足基础需求但业务上通常还需要毛利率、ROE这类派生指标。这些指标完全可以在Coze的代码节点里用JavaScript脚本来计算不需要让大模型去“心算”一方面快另一方面可追溯。我的一段代码逻辑大致是const data input.data; // 大模型节点输出的JSON对象 let grossMargin null; if (data.revenue data.operating_cost) { grossMargin (data.revenue - data.operating_cost) / data.revenue * 100; } let roe null; if (data.net_profit data.equity) { roe data.net_profit / data.equity * 100; } let debtRatio null; if (data.total_liabilities data.total_assets) { debtRatio data.total_liabilities / data.total_assets * 100; } // 标记异常值净利润为负但蚀收为正仍保留真实负值 let flag ; if (data.net_profit data.net_profit 0) flag 亏损; output { ...data, gross_margin_pct: grossMargin ? Number(grossMargin.toFixed(2)) : null, roe_pct: roe ? Number(roe.toFixed(2)) : null, debt_ratio_pct: debtRatio ? Number(debtRatio.toFixed(2)) : null, check_flag: flag };这段代码的核心是防止除零和空值计算。实际跑的过程中经常遇到equity为0或total_assets为0的极端情况直接除会返回Infinity落库就成了脏数据。所以每个计算步骤前都要做存在性判断算完还要保留两位小数避免浮点误差。计算完之后我会在输出对象里增加一个check_flag字段。这个字段的作用是给后续人工复核做提示比如净利润为负、总资产比负债还少等异常情况系统不会“自作主张”地修正但会优先标记出来。3.4 数据落库与批量触发多份财报一起跑数据最终要落到一个可查询的地方。我第一版方案是输出CSV后来改成了写飞书多维表格。原因很简单多维表格里可以直接加“复核状态”字段人工确认后打钩整个处理流程就成了半自动闭环。如果团队习惯用ExcelCoze里也有对应的在线表格操作节点原理一样只是目标源不同。批量处理时触发方式有两种选择。一种是手动逐份上传适合零星补充另一种是设定为定时启动或批量文件导入适合处理几十份财报的合集。我建议从第二种开始因为自动化方案的价值在大批量时才会完全释放。把几十份财报PDF压缩包或文件列表传到指定位置工作流自动遍历并逐份解析、抽取、落库中途失败的任务会抛异常并记录日志不会影响其他文件。批量跑起来后还有个“限速”问题需要留意。解析服务的并发调用有限额Coze工作流并行执行时如果触发频率过高容易报429这一类限流错误。我的处理方法是把批量任务切分成小批次比如每次并发3份、间隔几秒再继续下一批稳定性和速度都能接受。4. 常见问题与排错经验实测中踩过的坑4.1 高频问题速查表整个方案我从搭建到稳定运行前前后后折腾了好几周下面这个表格是把最常遇到的问题、原因和解决办法汇总起来的结果可以说是一份“避坑地图”。问题现象根本原因解决办法利润表数据抽成母公司数据提示词没限定“合并报表”在提示词里强制加“只取合并口径”金额单位串味原文含万元、亿元混排提示词统一换算并附单位示例科目缺失时被“瞎补”提示词未要求缺失填null明确“缺失写null禁止猜测”跨页表格字段断连PDF自身表格被强制分页解析时勾选跨页合并或按表格块输出解析结果包含大量乱码扫描件质量太差开启图像增强并做二次OCR校对大模型偶尔返回非JSON格式模型没被明确约束输出说明里给一个严格的JSON样例批量任务中途报错并发触发限量调低并发数分批执行净利润算出来和年报差很多取了母公司而非合并数人工核对一次后锁定提示词口径最值得重视的问题是第一行。我在测试阶段有几次抽取结果“看起来特别合理”营收、净利润都能对上但最后和年报核对时发现全是母公司报表的数据合并报表里的数据明明在下一页。自从在提示词里加上“只取合并口径”后准确率明显提高。4.2 稳定性策略从“能跑通”到“跑得稳”方案跑通后真正决定它能不能长期用下去的是稳定性。财报格式虽然大体统一但每年都有个别公司做创新排版时不时出现“科目名跨两行”“金额前有空格”“负数用括号表示”等变体。这些不会导致流程崩掉但会悄悄把错误数据带进来。我给自己定了几条铁律第一每次解析流程里必须带人工抽查步骤每批至少抽3份与原始PDF人工比对关键指标第二大模型输出的每个数值都做范围校验比如营收增长率超过100%或ROE大于100%就自动标记第三每周复盘一次新增的异常样本把新的“说法”补充到提示词或解析配置里。另外值得一提的是负数处理。旧准则下的财报里部分科目用括号表示负数比如财务费用是“5000”如果不加处理容易被解析成负号。我在代码节点里加了一步预处理如果字段值是字符串且首尾带括号统一转换为负数。这个细节看着小但直接影响现金流量表净额的正负差之毫厘谬以千里。4.3 后续可以扩展的方向这套方案稳定之后我陆续给它加了几个附加能力这里简单提一下供有同样需求的人参考。一是在Coze里加一个“异常波动作警”节点对抽取回填的数据做同比环比计算当某项指标变动幅度超过预设阈值时自动在工作通知群里发一条消息。这等于把一个“只会抄数据”的工具升级成了“会盯着财报异常”的助手。二是把抽取结果接入可视化看板。文章开头提到的传统流程里最容易被反复问的问题是“这个季度营收到底是多少”数据分散在一堆Excel文件里。现在回填结果落到统一数据源后我直接用图表组件做了一张总览看板营收、利润、现金流趋势一图看全。三是支持更多财报类型。目前跑通的是A股定期财报但同样思路可以复用到港股、美股或基金定期报告的抽取。区别主要在于解析模板和提示词里的报表科目名称底层链路完全不用动。根据我个人的实操经验最想给后来者的一句建议是不要一上来就追求“全自动零人工”那在财报这种复杂文档上不现实。先把流程跑起来保留一个灵活的人工复核节点抽样校验几批数据后再逐步扩大自动化范围和批量规模。这样既不用天天提心吊胆担心数据出错又能实打实地把年报季的工作时间从几天压缩到几小时。如果哪天你也被几百页PDF折磨到怀疑人生不妨按这套思路试一试。先把一份财报跑通再慢慢增加字段、增加批量。5分钟搭出一个能用的流程这句话不是夸张是这套方案的实际体验。而你省下来的时间值得花在更重要的分析工作上而不是和PDF斗智斗勇。