
做金融市场研究这些年我电脑里最值钱的东西不是模型权重而是几份自己手工整理并反复打磨的数据集。尤其是这套A股上市公司数字投资数据集2000-2024年连同配套的提取代码算是我压箱底的核心资产之一。它把过去二十多年A股上市公司年报里和数字化、信息化、智能化有关的资本投入统一加工成一张可以直接跑回归、做因子测试的面板表省掉了从上万份PDF里逐页翻附注的痛苦。这个数据集到底在解决什么问题呢企业数字化转型这几年是热门话题但数字化到底投了多少钱在报表上并没有单列科目只能从年报附注、项目明细和董事会报告里去挖。挖出来之后不同公司的口径还不一样有的记在无形资产里有的记在固定资产里还有的挂在在建工程中。这套数据集的价值就在于建立了一套统一的识别规则和清洗管线让这些散落的数据变成可比、可追溯的年度面板。如果你是做公司金融方向的博士生、关注数字经济的策略研究员或者想造一个差异化另类因子的量化开发者这篇内容应该能帮到你。不管你是第一次接触年报文本挖掘还是已经在用类似数据下面会把数据集的口径设计、提取逻辑、完整代码骨架和踩坑记录都摊开来写。1. 数字投资的数据长什么样为什么定这个口径1.1 数字投资不等于数字化转型先厘清一个经常被搞混的问题。很多研究把数字化转型程度做成一个0到1的文本词频指标比如年报里提到数字化多少次这当然有用但拿词频去做回归解释的是嘴上说了多少而不是实际投了多少钱。这套数据集抓的是后者也就是资本开支层面的数字投资。一家上市公司说要数字化转型落到报表上通常只有几条路固定资产里新增的服务器和数控设备无形资产里确认的软件著作权和信息系统在建工程里挂着的数字化车间项目长期待摊费用里摊销的数字平台实施费。这些项目在财务附注里往往都有明细名称而且绝大多数会带着金额。问题是它们分散在几十页PDF中名称五花八门有ERP系统实施费用有智能仓储改造有大数据中心一期工程还有工业互联网平台开发。如果不做统一抽取靠手工翻数千份年报不现实靠粗糙的关键词全文匹配又会混入大量干扰项。所以构建这个数据集的第一步就是把这些附注明细识别出来再归入统一的数字投资口径。提示我建议把数字投资严格限定在资本化支出和软件无形资产确认支出上暂时不把人员薪酬和云服务订阅费纳入。原因很现实——薪酬在报表里合并披露云服务订阅往往走费用化这两部分在公开年报里很难干净地拆分出来硬做只会引入噪声。1.2 数据集表的最终字段结构经过多轮调整最终输出的明细数据长这样字段名类型说明stock_codestr股票代码经过前导零规范化yearint对应年报年度item_namestr年报附注中的原始项目名称accountstr所属报表科目无形资产/固定资产/在建工程/长期待摊费用asset_typestr识别为 hardware/software/project 三类item_valuefloat该条目金额统一换算为元match_keywordstr命中的关键词用于追溯判断source_pageint该条目在原始年报中的页码confidence_scoreint人工校验时的置信度标记我特意保留了 item_name 和 source_page而不是只留一个汇总金额。原因是后面做数据审计时你要能随时回到原始文本验证。任何一套从文本里抽出来的数据如果无法溯源就不具备学术研究层面的可信度。confidence_score 字段用来区分记录质量0表示待核查1表示人工确认2表示机器高置信但未人工复核。正式使用前建议把所有 confidence_score 低于1的记录都过一遍。作为配套产出还会输出一份汇总表核心变量是 stock_code、year、digital_investment_total。汇总逻辑很简单同一个公司同一年度下所有数字投资明细条目金额加总。加总之前要处理好去重、单位统一和字段口径这些细节在后面的章节里会逐个拆开讲。2. 构建流程从年报PDF到面板数据的四步管线2.1 数据来源公开年报里数字投资的藏身之处先说数据源。A股上市公司年报是法定公开披露文件通常在交易所信息披露平台和上市公司官网投资者关系栏目都能找到PDF版本。早期年报可能是扫描图片需要OCR2007年以后大部分是文字版PDF最近几年还出现了XBRL半结构化格式。构建数据集时最省力的方式是先把下载好的年报PDF按股票代码_年份.pdf的格式放到本地目录然后用程序批量处理不要一边下载一边解析避免网络波动导致中间文件残缺。有了原始文件之后第二步是定位。数字投资条目最常出现在财务报表附注里的固定资产无形资产在建工程长期待摊费用这几个注释中。有些年报在董事会报告部分会专门提到数字化转型项目这也可以作为候选来源但要非常小心董事会报告是管理层叙述金额经常是计划投入而非实际投入不能直接当投资额用。我在构建时把附注明细作为主口径董事会报告只作为补充标签不会计入汇总金额。还有一个容易忽略的细节2000年到2024年横跨了多个信息披露规范版本早期年报的附注结构更松散章节标题不统一。这就决定了整套代码不能写死第几页到第几页而必须按文本标题动态定位章节否则换一份年报就失效。2.2 抓取策略附注章节定位与OCR容错直接全文匹配数字化三个字会找到大量不相关内容比如管理层讨论页里的加快数字化发展、风险提示页的数字化经营风险这些都没有金额含义。可行的做法是先按标题切分出附注区域再在小区域内找明细项。年报版式虽然每家略有不同但财务报表附注这个大标题高度统一可以先按这个标题把正文切开再用固定资产无形资产等二级标题缩小范围。如果遇到图片型PDF年报需要先做OCR。我实测下来中文OCR对这种排版密集的财务附注最大的问题不是单个词语识别而是金额数字串被切断比如1,234,567.89中间被识别成1,234, 567.89正则就会失效。所以我的管线里保留了一步数字清洗先把所有逗号和中文顿号统一成半角再过滤出包含元、万元、亿元的句子把句子里的连续数字块提取出来而不是对单个数字做正则。这样即使OCR产生轻微断句也能容忍。具体定位脚本可以参考下面的简化版本。这段代码的目的不是输出最终结果而是把附注里所有和金额相关的行抓出来为后续关键词匹配提供候选集。import pdfplumber def locate_account_sections(pdf_path): sections [] current_account None with pdfplumber.open(pdf_path) as pdf: for page_no, page in enumerate(pdf.pages, start1): text page.extract_text() or for line in text.splitlines(): line line.strip() if line.startswith(固定资产) or line.startswith(无形资产) \ or line.startswith(在建工程) or line.startswith(长期待摊费用): current_account line if current_account and (元 in line or 万元 in line or 亿元 in line): sections.append({ page: page_no, account: current_account, text: line, keywords_matched: [] }) return sections这里有个取舍一行文本可能同时包含本年增加本年减少期末余额等多个概念直接在行级提取后后面还需要针对金额语义再做一轮判断。不要想着一步到位管线的每一步职责越单一后期排错越容易。2.3 关键词识别词典设计与迭代方法关键词词典是整个数据集准确率的关键。我的做法是分两批建立。第一批是显性词比如数字化、信息化、智能化、大数据、云计算、物联网、工业互联网、人工智能、数据中台、数据中心、软件、信息系统、ERP、MES、CRM、OA、SaaS。第二批是根据实际年报文本扩充的行业词比如制造业常用的数控化改造机器人项目医药行业出现的实验室信息管理系统零售企业年报里的全渠道数字化门店。需要提醒的是词典不仅要包括正面命中词还要设计排除词。像汽车智能化虽然提到智能化但很多车企的智能化是产品功能不是企业内部数字投资像数字化服务收入是收入科目不是投资支出。这类干扰项如果全靠人工一条条排除成本太高。可行的办法是加入负面词典凡是明细词中出现收入销售营业收入合同金额的先打上怀疑标签不进汇总只进入待审列表。词典建设没有一步到位的版本。我建议你用自己的研究样本先跑一遍然后把抽出的条目随机抽100条人工标注看看有多少误判再反向补充排除词。迭代两三轮之后精确率一般能稳定在90%以上。这里不需要追求100%因为财务附注本身的颗粒度差异决定了总有边缘个案只要确保误判路径可追溯就不影响整体使用。2.4 金额抽取和单位换算的细节金额抽取里最容易被坑的是单位。年报里不同公司、不同年份的披露习惯完全不同有的用元有的用万元极少数用亿元。同一份年报里固定资产的本期增加可能用万元而在建工程注释里又变成元。如果只做正则提取、不管单位最后汇总出来的数字会乱套。我的做法是每抽到一个金额必须同时抽取它紧邻的单位词然后统一换算成元存入字段。换算规则很简单1亿元等于100000000元1万元等于10000元。判断单位时要注意中文标点很多年报用全角逗号比如1200万元正则要先做标准化。此外附注明细常常会有上年末余额和本年末余额同时出现。数字投资数据集要的是本年度新增投资额所以默认取本年增加额那一列而不是期末余额。如果某条明细没有明确的增加额只有余额变化就要判断是否可比。不清楚的宁可放弃也不能用期末数代替增量数否则后面算数字投资占营收比例时会得到一个方向错误的指标。3. 配套代码框架可复现的Python实现3.1 环境依赖与目录设计整套代码基于Python 3.9以上主要依赖 pdfplumber、pandas、re、jieba可选。pdfplumber 负责从PDF提取文本和坐标信息pandas 负责面板数据的拼接与输出jieba 不是必须的但如果你后续要做更细的分词和语义扩展可以用它做词性过滤。环境安装只需要一行命令pip install pdfplumber pandas jieba选库的时候我做过对比。PyPDF2虽然轻量但对财务附注这种复杂排版的文本还原能力偏弱经常提取出大量空行或断行pdfplumber 在保留页面坐标和表格结构上明显更强解析财报场景更稳。如果你的年报是扫描图片建议直接用 PaddleOCR 或 Tesseract 走OCR管线pdfplumber 的 extract_text 对纯图片页面返回为空需要提前做好分支判断。代码目录按执行顺序组织我强烈建议按编号命名脚本方便复现和回滚digital_investment_dataset/ |-- downloads/ # 年报PDF存放目录 |-- output/ # 中间结果与最终面板 |-- config.py # 关键词词典与路径配置 |-- 01_extract_notes.py # 定位附注并抽取候选行 |-- 02_keyword_match.py # 关键词匹配与排除逻辑 |-- 03_amount_parser.py # 金额提取与单位换算 |-- 04_build_panel.py # 汇总输出面板数据 |-- 05_qc_report.py # 抽样与质量报告这样设计的好处是任何一个环节出问题你只需要回滚到对应脚本不用重跑整条链路。A股年报数据集动辄几千个文件每次都全量重跑非常浪费时间。我实际统计过全量解析3500多份年报大约需要6到8小时如果只改一个关键词就重跑全部一天就过去了。3.2 核心函数一候选条目识别与过滤候选条目识别就是把带金额的文本行和数字投资关键词做匹配。前一步 locate_account_sections 已经返回了所有带金额的行这一步要做的是过滤from config import DIGITAL_KEYWORDS, NEGATIVE_KEYWORDS def filter_digital_items(sections, keywordsDIGITAL_KEYWORDS, negativesNEGATIVE_KEYWORDS): matched [] for sec in sections: text sec.get(text, ) hit_words [kw for kw in keywords if kw in text] bad_words [kw for kw in negatives if kw in text] if hit_words and not bad_words: sec[keywords_matched] hit_words matched.append(sec) return matched这段代码的逻辑很直白只有同时满足含正面关键词和不含负面关键词的行才会进入后续金额解析。你可以在脚本里加一个统计输出把每个关键词的命中频率打印出来作为调整词典的依据。如果某个关键词命中几百次但没有一次进入最终数据说明它要么太宽泛要么总是和负面词绑定出现需要考虑换一种表达方式。这个环节的另一个常见问题是关键词匹配的粒度。直接用if kw in text做子串匹配会把数字化车间和非数字化车间都匹配上。对于这种否定表达最简单的处理是再加一层否定前缀检查看关键词前面两个字符内是否有非未无。虽然朴素但能解决一部分典型误报。3.3 核心函数二金额解析与标准化金额解析是数据集构建中最容易出错的地方。我的实现不是简单匹配所有数字而是先按单位分割文本块再提取数字import re UNIT_RATIO {元: 1, 万元: 10000, 亿元: 100000000} def parse_amounts(text): text text.replace(, ,).replace( , ) amounts [] pattern re.compile(r([\d,](?:\.\d{1,2})?)\s*(亿元|万元|元)) for m in pattern.finditer(text): number float(m.group(1).replace(,, )) unit m.group(2) amounts.append(number * UNIT_RATIO[unit]) return amounts这里有个关键的取舍一段明细行经常既有本期增加又有本期减少甚至还有期末余额。直接提取所有金额会把这几个概念混在一起。我的建议是先看行内是否有增加新增本期购入本期购建这类词有的话只取这些词之后的第一个金额没有的话再退回到取所有金额的最大值。之所以取最大值是因为附注明细里通常期末余额最大但这个兜底逻辑只用于没有明确增加词的条目而且要在置信度字段中标为低置信度等待人工复核。这个兜底逻辑我在测试中发现能救回大约15%的有效样本但代价是引入了不少噪声。所以它必须和置信度标记机制配合使用不能直接混进最终面板。宁可在明细表中多标记一些待核查也不要默默吞掉一整批异常金额。3.4 核心函数三拼接面板与导出最后一步是把所有解析出的条目按股票代码加年份进行分组汇总并输出最终文件import pandas as pd def build_panel(records): df pd.DataFrame(records, columns[ stock_code, year, item_name, account, item_value, match_keyword, asset_type, source_page, confidence_score ]) df df.drop_duplicates( subset[stock_code, year, item_name, item_value] ) df.to_csv(output/digital_investment_detail.csv, indexFalse, encodingutf-8-sig) summary df.groupby([stock_code, year], as_indexFalse).agg( digital_investment_total(item_value, sum), item_count(item_value, count) ) summary.to_csv(output/digital_investment_panel.csv, indexFalse, encodingutf-8-sig) return df, summary我特意加了 drop_duplicates这个看起来不起眼的步骤实际上能干掉大量重复数据。原因是同一份年报的附注在PDF里会因为页眉页脚被重复抽取或者同一明细在固定资产和在建工程两处都有摘要性描述。如果没有去重汇总金额会被翻倍而且这种错误很难通过肉眼发现。此外输出用 utf-8-sig 编码可以直接用Excel打开而不会乱码。这个习惯对后续协作和审阅非常有用算是我在实操里留下来的一个小经验。导出的明细文件和汇总文件建议分开保存因为你做学术答辩或者数据审计时审阅人大概率会想看明细到底是怎么汇总的。4. 数据质量校验与典型问题处理4.1 三类典型脏数据重复、单位混用、误匹配做文本挖掘数据集最终拼的不是抽取速度而是数据质量。我在这套数据集上踩过的坑主要有三类每类都值得单独说一说。第一类是重复条目。最常见的情形是年报的主要会计政策部分描述了某项数字化资产的摊销年限金额又出现在无形资产明细里两段文字都可能包含软件和金额。如果不限制在明细章节内同一个软件项目可能被抽两次。解决办法是增加去重维度股票代码、年度、条目名称、金额四个字段同时一致才判定为重复。第二类是单位混用。同一段文字中会出现其中软件原值3500万元本年摊销110.25万元如果你只提取所有金额会把摊销额和原值混在一起。这里没有银弹只能在解析阶段依赖上下文词来区分紧邻原值的金额记为资产原值摊销减值后的金额不纳入投资额。第三类是误匹配。最典型的是把数字化收入当成数字化投资。销售商品、提供劳务收到的现金里可能带数字经济服务收入字样如果负面词典不完整这一条就会被错误纳入产生一个虚高的异常值。这个问题只能靠完善负面词典加人工抽样审计解决没有捷径。4.2 交叉校验横向和纵向怎么配合数据集构建完不是终点校验至少要做两遍。第一遍是纵向校验看单家公司在时间序列上是否平滑。正常公司的数字投资不会出现从零突然跳到几十亿又回落到零的情况。如果出现这样的突变通常说明年报披露口径调整或者抽取错误需要回到源文件复核。第二遍是横向校验看同年度、同行业、同规模公司的数字投资分布是否符合常识。制造业龙头和科研型小公司的绝对投入当然不同但你可以用数字投资除以总资产这个比例来判断如果某家公司该比例比同行业中位数高出20倍它大概率是混入了非数字化资产比如把整个新厂房建设项目因为名称里带智能化就全部算进来了。我建议在校验阶段生成一张质量报告表让所有问题都有据可查检查项判定规则处理方式单位换算合理性换算后金额落在公司总资产的0.01%-30%区间内区间外标低置信度人工复核同比变化平稳性相邻两年增长率绝对值小于200%超阈值标待核查行业偏离度与同行业同年度中位数比值小于20超过20倍回源核查重复样本标记股票代码年份条目金额完全一致自动去重并记录这份检查表其实比最终代码更有价值因为它把抽象的数据质量变成了可执行的清单。每次跑完一批新数据我都是先跑这些检查再决定下一步怎么修。4.3 常见问题速查我遇到过的十个坑整理一份速查表按踩坑频率排序也是我在维护这套数据集时的实时备忘现象原因解决办法某公司某年金额为0但前后两年都很大年报附注改版明细披露位置变化检查该年财报目录调整章节定位规则所有公司同一年份金额普遍偏低PDF解析丢失部分页面检查该年年报总页数重下缺失文件同一公司数字投资逐年翻倍把期末余额当成了本年增加回归金额解析逻辑锚定本期增加制造业公司混入大量发电设备关键词设备过度匹配增加数字化自动化数控前置限定词软件公司数字投资为0软件公司把研发投入资本化了科目名称不含软件扩展关键词表加入研发支出-资本化个别条目金额等于总资产误取了资产负债总计行排除合计总计开头的行新上市年份缺失该年无年报保留空值不填0避免幸存者偏差名称含数字科技的公司全部命中公司名字自带数字先剔除公司全称字段再跑附注匹配面板数据股票数量远少于3000家有些公司年报为纯图片PDF接入OCR流程后重跑解析与外部统计口径对不上不同统计口径对数字经济定义不同明确说明本数据集口径不强行对齐外部统计这些坑看似琐碎但任何一个都会影响面板数据的可信度。数据集的产出过程本质上是一个抽取-审计-修正-再抽取的循环循环次数越多数据越可靠。不要想着一次性跑出完美结果那在非结构化文本处理里基本不存在。5. 数据集的真实应用场景5.1 学术研究里怎么把数据变成结论在学术研究中这套数据最直接的应用是公司金融和数字经济领域的实证分析。以往没有统一数据时研究者只能用词频代理变量或者手动整理几十家公司的样本。有了这个面板数据集可以做数字化转型投资和企业绩效的面板回归控制行业、规模、年份固定效应之后看数字投资强度对企业总资产收益率、全要素生产率的边际影响。使用时要特别注意两点。第一在论文里要清楚写出数字投资的定义和抽取规则不能只写数据来自某数据集否则审稿人无法判断口径是否合理。把关键词列表作为附录提交是常见的做法。第二做回归前要斟酌滞后结构。当年投资和当年的经营绩效之间存在明显内生性常用的处理方法是使用滞后一期或两期的投资变量或者结合工具变量法。数据集的时间跨度从2000年到2024年完全足够支撑这类多期模型不需要为了缩短样本而牺牲稳健性。5.2 量化策略里怎么构建因子从量化角度看数字投资是一个典型的另类数据因子。你可以构造数字投资强度因子用 digital_investment_total 除以总资产再按行业做中性化处理。逻辑是企业在数字化上扎实投入未来可持续经营能力可能改善这类公司在基本面因子里可能提供额外增量。但不要指望这个因子能单独给出很高的IC。它更合理的用法是作为质量因子或成长因子的组成部分与其他财务指标合成。实际测试时我建议先用数据集构建面板再用排序法或回归法检测IC值并且注意在2020年前后做分段测试因为数字化投入强度和主要行业结构在这个节点前后有明显变化。最后再说一遍任何因子回测都要加入交易成本假设否则结论很难落地。5.3 后续还能往哪些方向扩展第一可以引入语义分析。当前数据集靠关键词命中但智能仓储物流系统这种复合概念可能被拆散如果引入短文本分类模型就能提升识别精度。第二可以把数字投资从企业层扩展到产业链层比如按行业和上下游关系聚合成行业数字化投入指数进而研究一家公司的数字化投入是否受上下游带动。第三可以考虑把数据集和其他公开财务数据合并构建更完整的公司特征库再做聚类分析或者时序预测。我个人的建议是先把当前版本的数字投资数据集用熟再谈扩展。因为很多扩展方向都要以准确的基准数据为前提基准都不稳进阶指标全是空中楼阁。写到这里说一下我实际使用这套数据的体会。做完这个数据集之后我去翻了几份2020年前后的年报做人工复核发现很多公司的数字化投入并非体现在母公司报表层面而是藏在子公司、孙公司的附注里。这是目前版本无法完美解决的遗漏也是文本挖掘类数据集的天然局限。但正因为这样我才越发觉得数据集的价值不在于绝对精确而在于口径透明、可追溯、可复现。你永远可以在它基础上补数据、改规则、加标签这比拿一个黑盒数据要强得多。最后送大家一个最实用的建议无论是谁发布的数据集拿到手之后先抽取十份原始年报做人工核对再决定能不能用在你的研究里。数据这个行当信任是慢慢攒出来的不是声明出来的。