
简介文档以503页篇幅系统阐述DeepSeek证券研究报告自动摘要方案围绕DeepSeek-VL2多模态模型覆盖文档预处理、文本与表格结构化解析、图片信息提取、多模态融合、数据标注与增强、分布式训练及微调优化等环节。面向金融AI、NLP与深度学习方向的研究者及工程人员重点解决研报关键信息分散、格式多样、投资观点难以自动生成的痛点。PDF单文件约15.1MB共51个大章节支持目录跳转及阅读器书签大纲定位内容分模块讲解字符级语义对齐、金融术语向量映射、多任务损失函数设计、余弦退火学习率调优等关键方法并给出数据标注体系与训练环境搭建的工程化细节。前20章已覆盖从预处理、建模到微调的全流程。目前已有131人学习适合作为智能投研系统研发、金融文档自动化处理项目的技术参考。1. 研报越读越厚、摘要越写越慢DeepSeek 自动摘要方案在解决什么问题卖方研究员小周周五晚上十点还在和一份 503 页的深度报告搏斗翻到第 300 页才找到盈利预测表目标价藏在第 412 页的脚注里等她把这些数字抄进 Excel 已经过去两个多小时。这个标题里的方案解决的就是这件事——把文档关键信息提取、投资观点自动生成串成一条流水线让 DeepSeek 先把长 PDF 拆成结构化字段再基于字段生成一页能直接进日报的摘要。它适合手里常年堆着招股书、深度报告、行业专题的卖方研究员、买方投研也适合被合规要求压着、想把研报处理效率提上去的券商 IT 团队。它不是把 PDF 丢给大模型“总结一下”而是把研报处理拆成解析、抽取、生成、校验四段每一段都有明确的技术选型和翻车点。2. 从 503 页 PDF 到可喂给模型的高质量文本解析管线怎么选、怎么搭自动摘要效果差八成不是模型不行而是喂进去的文本太脏。PDF 解析这一步直接决定下游抽取和生成的准确率值得先花整章讲透。2.1 先分清文字版和扫描版PDF 解析工具选型决定的不是速度而是准确率券商研报在流通时有两种形态处理方式完全不同。文字版 PDF 是原始排版文件文字层可以直接提取解析成本低、准确率高扫描版 PDF 是图片堆出来的必须走 OCR识别误差会一路传导到最后的观点生成。我一般拿到 PDF 先做分类用 PyMuPDF 打开文件逐页调用page.get_text(text)统计字符数页均少于 50 个有效字符的页面判定为扫描页。文字版用 PyMuPDF 提取正文、pdfplumber 提取表格扫描版再上 PaddleOCR。这个分类动作看起来简单实际能省掉后面大量排错时间——很多团队一开始统一走 OCR结果文字版 PDF 反而被 OCR 识别出一堆错别字数字变得不可信。工具选型的逻辑是这样的PyMuPDF 速度快503 页 PDF 几秒就能跑完但它拿到的表格是线性文本流行列关系会丢失pdfplumber 能按坐标还原表格结构适合研报里常见的财务三张表缺点是对无线表格无能为力PaddleOCR 对中文版面识别效果稳定但单页推理耗时在秒级503 页全量 OCR 可能跑上十几分钟。我一般把它们组合使用而不是迷信某一个工具。2.2 最小解析管线PyMuPDF 抽文本、pdfplumber 抽表格先用一个最简单的脚本把 503 页 PDF 拆成“页级 JSONL”每一行代表一页保留页码、文本、表格三个字段。这样后续无论是做关键信息提取还是给 DeepSeek 切片投喂都有据可循。import fitz # PyMuPDF import pdfplumber import json pdf_path industry_report_503p.pdf output_path pages.jsonl # 打开 PDF按页处理 doc fitz.open(pdf_path) for page_num in range(len(doc)): page doc[page_num] # 1. 提取正文文本保留换行和空格 text page.get_text(text) # 2. 用 pdfplumber 单独处理当前页的表格 tables [] with pdfplumber.open(pdf_path) as pdf: current pdf.pages[page_num] for t in current.extract_tables(): tables.append(t) # 3. 写出 JSONL一页一行 record { page: page_num 1, text: text, tables: tables, } with open(output_path, a, encodingutf-8) as f: f.write(json.dumps(record, ensure_asciiFalse) \n)这段代码的逻辑按页切分天然保留了页码后面的引用溯源直接依赖这个字段。page.get_text(text)拿到的文本会带排版换行我通常不做多余的清洗因为换行符对模型来说无害反而能帮助它理解表格顺序。pdfplumber的extract_tables()返回的是一个二维数组每个元素是单元格文本但要注意它会把整页所有表格一起返回需要自己按坐标或内容关键字去筛哪张是利润表、哪张是资产负债表。常见做法是先跑完这个脚本再人工抽查 10 页确认文本顺序没有乱、表格没有串行再进入下一步。2.3 扫描版研报的 OCR 参数PaddleOCR 的 3 个调参要点扫描版研报分两类一类是整册都是扫描图片另一类是正文是文字层、插图或附录是图片。对后者我倾向只对检测出的扫描页做 OCR而不是全文重跑。PaddleOCR 的默认参数能处理大多数页面但研报有它的特殊性——表格多、小字号数字多、表格线经常被压暗。我常用的三组参数调整如下。# 使用 PaddleOCR 命令行对扫描页做 OCR按页输出文本 paddleocr --image_dir scan_pages/ --lang ch --use_angle_cls True \ --det_db_thresh 0.3 --rec_batch_num 6 --output json--lang ch指定中文模型研报里的英文缩写EPS、ROE、PE仍能识别不必切到英文模型。--use_angle_cls True开启方向分类扫描版页面常有轻微旋转不开启会导致整页文字识别错位。--det_db_thresh 0.3是文本检测阈值调低到 0.3 能多召回一些浅色表格线附近的数字但不要低于 0.2否则页脚水印会被当作文字读出来。--rec_batch_num 6控制每批识别行数显存小就降到 2不影响准确率。OCR 输出是带坐标的文本块需要用脚本按坐标排序还原阅读顺序这一步不做的话模型拿到的是乱序文本抽取出来的财务指标顺序会错。OCR 之后建议把识别文本和原文页码绑定否则后面想找到“第几页写上调评级”就成了黑匣子。3. 把“投资观点”的原料做扎实关键信息抽取的字段设计与 DeepSeek 调用解析层解决了“文本是什么”这一章解决“哪些文本能支撑观点”。研报摘要最怕的不是信息少而是模型不知道该把注意力放在哪。粗看 503 页全是信息但真正能支撑投资决策的字段其实很集中。3.1 研报里真正值得抽的四类信息指标、事件、评级与目标价我做了十几份研报抽取后把字段收敛为四类。第一类是财务指标营业收入、归母净利润、EPS、毛利率、ROE以及它们的同比变化注意研报经常同时披露当年预测值和前一年实际值抽的时候要带年份后缀。第二类是公司事件并购重组、股权变动、定增、业绩预告、行业政策发布事件必须带日期否则没有时效性。第三类是评级与目标价买入、增持、中性、减持目标价可能是单一数字也可能是区间区间要拆成上下界。第四类是核心逻辑研报通常在开头给出 1-3 条推荐逻辑这属于自由文本不需要严格结构化但要完整保留因为这是“投资观点自动生成”的直接素材。这四类字段的设计逻辑是把信息密度最高的内容先抽出来压缩成大模型能快速对齐的 JSON。直接让 DeepSeek 读整段原文再写摘要它能读懂但很容易被 503 页里的大量噪声带偏有了结构化字段生成阶段可以只说“基于以下事实写摘要”模型输出的确定性会明显上升。3.2 用 DeepSeek 的 chat 接口做结构化抽取一次请求返回 JSON 的写法“DeepSeek API 如何调用”这个问题的标准答案是用 OpenAI SDK换 base_url 和 model 名。下面这段代码把单页文本批量送进 DeepSeek要求它按给定 schema 抽取字段。import os import json from openai import OpenAI client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com, ) schema_prompt 你是研报信息抽取助手。从给定研报文本中抽取如下字段只输出 JSON - financials: 数组元素含{year, revenue, net_profit, eps, roe, gross_margin} - events: 数组元素含{date, event_type, description} - ratings: 数组元素含{org, rating, target_price_low, target_price_high} - core_logic: 字符串数组每条不超过50字 注意数字保留原单位不要换算不确定的字段置为null。 page_text 第3页文本内容…… resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: schema_prompt}, {role: user, content: page_text}, ], temperature0.1, response_format{type: json_object}, max_tokens2000, ) data json.loads(resp.choices[0].message.content) print(data[financials])这段代码有几个参数值得单独说。temperature0.1是抽取任务的关键设置温度越低输出越稳定不会出现同一指标这次抽到、下次漏掉的情况如果做观点生成温度再调高但抽取必须低。response_format{type: json_object}强制模型返回合法 JSON省去解析时的异常处理DeepSeek 官方 API 支持这个参数本地部署的 vLLM 也兼容。max_tokens2000要留足余量研报文本抽出来的数组可能很长如果设成 500输出可能在中间被截断JSON 直接解析失败。抽取是逐页还是整章送取决于单段文本长度后面第五章会讲分段策略。只要 API 返回了 JSON就可以断言这一步成功这是结构化抽取比自由文本摘要好落地的地方。3.3 数字与单位校验模型提取结果必须过的 3 道检查模型抽取出的数字不能直接进摘要必须过校验。研报里的数字坑是最多的我把校验拆成三道。第一道是原文回查抽出的每个数值正则去掉千分位后必须在原文对应页码的文本里能找到找不到就标记为“疑似幻觉”人工复核。第二道是单位一致性有的研报用“净利润 12.5 亿元”有的用“净利润 125,000 万元”模型容易原样保留但下游做同比比较时必须统一成亿元我通常在 prompt 里要求保留原单位校验脚本再做归一化。第三道是时点区分盈利预测表中的 2025E、2025F、2025 实际值含义不同模型经常漏掉后缀导致观点生成时把预测值当实际值用。这三道检查不写进 prompt而是放到后处理脚本里因为模型在生成时很难自我纠错校验必须由代码完成。校验通过的字段才进入下一步观点生成不合格的标黄留待人工处理。4. 从“信息罗列”到“像研报摘要”投资观点生成的三个层次与多阶段写法字段抽得再全如果生成阶段只是把 JSON 拼成一段话那叫数据转写不叫自动摘要。这一章的难点是怎么让模型输出“有观点”的摘要而不是复述原文。4.1 事实层、观点层、风险层为什么要三层而非一段话我习惯把最终摘要拆成三层每层给模型不同的约束。事实层负责陈述公司去年营收多少、增速多少、评级上调到多少这一层必须全部来自抽取结果不允许任何补充观点层负责解释驱动因素为什么利润增速快过营收是因为产品结构升级还是费用率下降这一层允许模型根据常识补逻辑链但必须引用事实层的数据风险层负责反向审视原材料涨价、行业竞争加剧、商誉减值等这一层必须有来源不能凭空写。三层分开生成的好处是可控事实层可以机器校验观点层可以人工快速审阅风险层单独过滤敏感表述。如果合成一段话模型很容易把所有句子都写成“向好”语气风险被稀释成一句套话。分层结构也方便下游使用事实层直接填 Excel 摘要观点层贴进日报风险层进风控清单。4.2 Few-shot 与温度参数让模型从复述原文走向给出判断生成阶段的 prompt 我一般给一个极简示例而不是长篇 instructions。示例的作用是告诉模型“输出长什么样”而不是“该说什么”。resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是券商研报摘要作者。输入是抽取出的结构化字段输出三段 Markdown 【事实】只能使用输入字段中的数字和结论。 【观点】解释驱动因素每个观点必须引用事实中的一个数据。 【风险】最多列3条每条需说明不利影响的路径。 }, {role: user, content: 【输入】 financials: [{year: 2024E, revenue: 12.5亿元, net_profit: 2.1亿元, eps: 0.35元}] ratings: [{org: 某证券, rating: 买入, target_price_high: 8.5, target_price_low: 7.2}] events: [{date: 2024-03-15, event_type: 股权激励, description: 授予核心研发团队}] 【输出】 【事实】2024 年预计实现营业收入 12.5 亿元、归母净利润 2.1 亿元EPS 为 0.35 元某证券给予买入评级目标价区间 7.2 至 8.5 元。 【观点】盈利增长主要来自研发激励落地后新产品的放量股权激励绑定核心团队是短期催化剂。 【风险】目标价上行空间依赖新品毛利率维持当前水平若原材料价格上涨则存在下修风险。 }, ], temperature0.3, top_p0.8, max_tokens1500, )这段示例的价值在于同时约束了格式和语感。temperature0.3是观点生成任务里我踩出来的安全区低于 0.2 输出像新闻稿句子结构过于固定高于 0.5 开始出现“值得重点关注”这类没有信息量的话术甚至把利空写成利好。top_p0.8配合低温度能让词语选择有一定的多样性又不至于跑偏。如果模型输出里出现了输入字段以外的数字那基本可以判定为阶段失败需要回退到抽取层检查是不是字段漏抽了。4.3 溯源到页码摘要里每个数字都能回原文的引用机制观点生成之后最后一步是给摘要里的每个数字挂页码。做法是在生成摘要前把抽取字段对应的页码一起传给模型要求它在每个关键数字后面用括号标注页码例如“目标价区间 7.2 至 8.5 元第 412 页”。模型输出的页码不一定准确后处理脚本需要再校验一遍拿数字去目标页码的文本里搜索搜不到就自动搜索前一页和后一页还搜不到就把这条标记为“溯源失败”人工处理。这个机制看着笨但它是摘要能否进正式投研流程的关键。很多团队做到观点生成就停了没有回溯能力结果摘要里的数字错了都找不到源头合规一查就出问题。有了页码引用人工复核只需要抽查带引用的条目而不是通读全篇。5. 批量处理 503 页研报的工程化落地4 个高频翻车点与排查清单单页跑通容易整册跑批才是分水岭。503 页全量投喂会遇到一堆“只在实际跑批时才会出现”的问题这一章是把常见的翻车现场和排查思路整理出来。5.1 长文本截断与上下文溢出分段策略和重叠窗口怎么定现象请求 DeepSeek 时报错提示文本过长或者输出到一半戛然而止返回的 JSON 不完整。原因把 503 页全文一次性塞进上下文超过了模型的最大上下文长度。解决按章节切分而不是按固定字符数硬切。硬切会在句子中间断掉破坏语义。研报天然有章节层次我一般用目录正则定位“一、二、三”或“第一章、第二章”等标记把 PDF 按章节切成若干块。每个块再按字符数二次切分单块不超过 8000 字符相邻块重叠 200 字符。重叠窗口的作用是防止关键指标正好落在切分点被截掉。import re def split_by_chapter(pages_jsonl, max_chars8000, overlap200): docs [] current_doc start_page None chapter_pattern re.compile(r第[一二三四五六七八九十百][章节]) for line in pages_jsonl: page json.loads(line) if start_page is None: start_page page[page] text page[text] # 检测到新章节时先把当前 chunk 独立存出 if chapter_pattern.search(text) and current_doc: docs.append({ start_page: start_page, end_page: page[page] - 1, text: current_doc[-overlap:], # 保留尾部重叠加上下文 }) current_doc start_page page[page] current_doc text if len(current_doc) max_chars: docs.append({ start_page: start_page, end_page: page[page], text: current_doc, }) current_doc start_page None return docs这个切分函数把页码作为切分元数据保留后面提取引用时可以直接定位到章节范围。要注意重叠窗口加在章节边界时会带来重复内容模型对重复内容通常能容忍但如果发现抽取结果里同一指标出现两次不同数值优先怀疑是重叠窗口导致的。排查办法是拿重复字段去两个相邻 chunk 里分别搜索看是不是都命中。5.2 表格错位与数字缺失PDF 表格转换后的兜底清洗规则现象抽取结果里营业收入和归母净利润对不上毛利率出现 20% 和 0.20 两种格式。原因pdfplumber 对无线表格识别差跨页表格会把表头丢失模型看到前几列有数字、后几列是空只能靠猜。解决在解析层对表格做两道兜底。第一道如果一行里除了第一列其他列均为空把这一行丢弃第二道检测到“单位亿元”这样的标注在表格上方时把单位信息拼进表头字段避免模型把 0.35 误读成 0.35 亿元还是 0.35 元。这两道规则要写在解析脚本里不要指望靠 prompt 让模型自己推断——模型对窄表上下文往往无能为力。5.3 并发限流与重试批处理脚本要有的指数退避现象批量跑到 300 个请求后返回 429 限流错误重跑后前面的结果全丢。原因默认并发数太高或者没有设置重试机制。解决把并发数压到 4 到 8出错时用指数退避重试 3 次每次等待时间翻倍第一次 1 秒第二次 2 秒第三次 4 秒。不要无限重试503 页拆成 20 个 chunk重试只针对失败的 chunk而不是整册重跑。批量脚本的断点续跑也建议加上每个 chunk 的抽取结果单独写一个 JSON 文件文件名带页码范围跑完一批再合并。这样即使中途断了只需要从断点处继续不用返工。5.4 数据安全不允许外部调 API 时的替代本地部署 DeepSeek 与 vLLM 加速很多券商客户的实际约束是研报不出内网外部 API 调用这条路直接被合规砍掉。常见做法是用本地部署 DeepSeek 模型走 vLLM 做推理加速。vLLM 对硬件有要求显存不够时先选小一点规模的蒸馏模型跑通流程再换大模型。# 本地启动 vLLM 推理服务端口 8000 vllm serve /models/deepseek-chat-7b \ --port 8000 \ --max-model-len 32768 \ --gpu-memory-utilization 0.9启动之后前面的调用代码只需要把base_url改成http://localhost:8000/v1模型名改成部署的名字即可。--max-model-len 32768是上下文长度配置本地部署的上下文通常比 API 版本短所以第五章第一节的分段策略要更保守单块字符数从 8000 降到 4000。--gpu-memory-utilization 0.9表示允许 vLLM 使用 90% 的显存留出一点余量给操作系统。本地部署的坑在于模型的版本可能比 API 旧同样的 prompt 输出质量会有差别建议在切换环境后重新跑一遍校验集不要直接豁免测试。6. 用回归指标和输出模板把摘要方案固化进日常流程方案跑通后最怕“每次输出都不一样”时间一长没人敢信。我习惯建一个最小验证集来锚定质量找 5 篇已经有人工摘要的研报跑完管线后逐篇对比用 ROUGE-L 算事实层的字面覆盖率再用人工抽检看观点层的逻辑是否成立。这个验证集规模不大但能挡住大多数回归问题。另一个建议是把摘要输出固定成统一模板字段顺序固定为“公司简称、证券代码、评级、目标价区间、核心营收利润数据、核心逻辑、风险”方便直接贴进 Excel 或日报系统。模板里每行带页码引用和生成时间这样即使摘要被转了几手也能追溯来源。我自己的一个血泪教训是上线第一批时把温度调到 0.7观点倒是丰富但有两天把“盈利下修”写成了正面观点后来统一固定到 0.2 并加了“不得使用积极评价词描述下修数据”的约束这类问题再没出现过。每次改动 prompt 或模型参数都拿最小验证集重跑一遍再放量比事后排查成本低得多。这套方案走到这一步已经能稳定处理 500 页量级的研报能不能进一步提速就看你们把校验和人工复核放在哪个环节了。希望帮到你。本文还有配套的精品资源点击获取