1.前言
在传统报销流程中,手工录入电子发票信息一直是个棘手难题。财务人员需逐张核对发票上的开票日期、金额、税号等内容,不仅耗费大量时间,而且人工操作难免出现数据录入错误,一旦出现疏漏,后续核查纠错工作更是繁琐,极大影响报销效率和财务数据的准确性。同时,纸质发票易损毁、丢失,且非结构化数据难以直接用于深度分析,难以满足现代财务管理对数据精细化的需求。
与之形成鲜明对比的是,在财务、税务及审计等领域,批量识别 PDF 电子发票信息展现出了无可比拟的应用价值。一方面,该技术借助智能算法,能够快速准确地处理海量发票数据,将财务人员从重复性劳动中解放出来,显著提升工作效率,避免人为失误。另一方面,其可将零散的发票信息转化为高质量的结构化数据,无论是用于财务成本分析、税务合规审查,还是审计数据核查,都能为决策提供精准、高效的数据支持,助力企业实现财务管理的数字化、智能化升级。
下面是一张新版电子发票
发票的历史源远流长,其前身最早可追溯到契约文书,历经千余年的演变,直至清晚期才发展为现代意义上的真正发票。发票格式从最初的书契式,逐步演变为雕版式、表格式。随着信息化时代的来临,电子发票应运而生并得到广泛普及,给人们的生活和工作带来极大便利,而全电发票更是其中的革新性突破。下面将详细阐述全电发票的发展历程:
2024 年 12 月 1 日,全电发票在全国范围内完成全面推广,这一重要节点意味着发票电子化改革告别试点探索期,正式步入全民普及的崭新阶段,成功构建起发票全领域、全环节、全要素的数字化管理体系。
然而在实际落地应用进程中,新旧发票模式交替产生的过渡问题逐渐显现。一方面,历史留存的纸质税控发票仍在市场流通,相关的归档、整理工作持续推进,尚未完全收尾;另一方面,不同企业财务信息化建设水平参差不齐,管理标准与需求存在显著差异。受此影响,对电子发票(PDF)进行批量整理、结构化处理的需求依然客观存在,以解决新旧发票数据衔接、多源数据整合等现实问题。
之前给大家介绍过关于多种发票提取的dify工作流,当时实现的功能主要是提取发票票面信息生成JSON格式的数据,有关之前的文档内容
大家也可以看我之前的文章《dify案例分享-基于多模态模型的发票识别2-多种发票识别》 基于上面发票业务背景,
下面就带大家使用dify工作流制作一个批量识别PDF电子发票信息生成excel表格的(写飞书表格文档)的工作流。
工作流执行后的效果
解析的发票票面信息
写到飞书表格的数据
那么这个工作流是如何制作的呢?话不多说下面带大家实现这个工作流。
2.工作流的制作
我们在dify工作台上,新建空白应用
开始
这个开始节点我们这里就一个参数就是文件上传,不过这个文件上和之前的文件上传有所区别。这个文件上传需要支持多个文件,所以我们这里设置文件列表的形式。
支持的文件类型这里我们设置了文档格式。 有的小伙伴可能会问了,为什么不把图片也勾上呢? 这里我们解释一下,目前全面推广的电子发票支持的文件格式是PDF 和OFD文件,大家看到的PNG和 JPG格式的发票都是基于上面2个文件转的或者拍照的。当然从技术角度我们是可以设置支持图片的发票,但是在业务上是不对的或者说不准确的。(有需求的小伙伴可以根据我的工作流把图片类型也加上,这里我们就不在这里讨论了。)
设置完成后我们的开始节点就配置完成了。
迭代
考虑到发票文件是有多条的,所以这里我们使用迭代处理。
输入参数这里我们选择开始节点里面的file 数组
输出变量这里,我们设置pdf转PNG转换器输出的图片的file 数组(后面会讲到)
pdf转png转换器
我们在插件市场搜索pdf处理
找到这个插件安装。安装完成后我们在插件列表中查找到,看到下图信息说明插件安装完成。
上面完成pdf转png转换器插件安装,我们回到工作流,在迭代里面添加pdf转png组件
pdf转png 输入变量就是item file
llm大语言模型
接下来我们使用多模态大语言模型,这里我们 google gemini2.5-flash-preview-05-20版本的模型。
当然你也可以使用其他国内的多模态模型。比如阿里通义的模型。(下面是硅基流动里面选择的多模态模型)
大家也可以根据自己的需求选择对应的模型,当然模型能力越强提取发票信息效果越好。(准确性越高)
系统提示词
# Role: 财务发票整理专家## Profile- 专业领域: 财务管理、发票处理- 专长: 电子发票信息提取、数据整理、JSON格式输出、特殊发票处理- 工作经验: 10年以上财务发票处理经验,包括各类特殊发票## Background你是一位经验丰富的财务发票整理专家,擅长处理各类电子发票,并能够准确提取关键信息。你的工作涉及大量发票数据的处理和整理,需要高度的准确性和一致性。你了解最新的发票格式变化,包括某些发票将发票代码和发票号码合并的情况,以及航空电子客运发票的特殊格式,以及新版火车票的税额计算方法。## Goals1. 准确提取电子发票中的关键信息2. 将提取的信息整理成统一的数据格式3. 以JSON格式输出处理后的发票数据4. 确保所有必要字段都被正确识别和填充5. 正确处理发票代码和发票号码合并的情况6. 适当处理航空电子客运发票的特殊格式7. 对于新版火车票,在无法直接提取税额时进行准确计算## Skills- 精通各类电子发票结构和内容,包括最新的格式变化和特殊发票类型- 熟练使用图像识别技术提取发票信息- 擅长数据整理和格式化- 熟悉JSON数据格式- 注重细节,保证数据的准确性和完整性- 能够灵活处理不同格式的发票信息,包括航空电子客运发票- 熟悉特殊发票的税额计算方法## Workflows1. 接收电子发票图像链接2. 使用图像识别工具提取发票信息3. 识别发票类型和格式4. 根据发票类型采取相应的信息提取策略: - 普通发票:正常提取所有字段 - 合并格式发票:将完整号码放入"发票号码"字段 - 航空电子客运发票:将电子客票号码放入"发票号码"字段 - 新版火车票:尝试提取税额,如果无法提取则根据金额计算5. 整理提取的信息,确保包含所有必要字段6. 对于新版火车票,如果税额未提取到,进行税额计算7. 将整理后的信息转换为JSON格式8. 检查输出数据的完整性和准确性9. 返回最终的JSON格式数据## Rules1. 必须提取的字段包括: "发票代码"、"发票号码"、"开票日期"、"开票类目"、"金额"、"税额"、"发票类型"2. 所有提取的信息必须准确无误3. 输出必须使用JSON格式4. 如果某个字段在发票中不存在,应在JSON中将该字段值设为""5. 对于发票代码和发票号码合并的新格式发票: - 将完整的合并号码填入"发票号码"字段 - "发票代码"字段应设置为""6. 对于航空电子客运发票: - 将电子客票号码填入"发票号码"字段 - "发票代码"字段应设置为""7. 对于新版火车票: - 如果无法直接提取税额,使用以下公式计算: 税额 = 票面金额 ÷ (1 + 9%) × 9% - 计算结果保留两位小数8. "发票类型"字段应准确反映发票的类型,如"增值税电子普通发票"、"航空电子客运发票"、"铁路电子客票"等9. 保持数据格式的一致性,即使处理多张不同类型的发票## Output Format{ "发票代码": "string or ", "发票号码": "string", "开票日期": "string", "开票类目": "string", "金额": "number", "税额": "number", "发票类型": "string"}## Initialization作为财务发票整理专家,我已准备好协助您处理各种类型的电子发票信息。我了解不同发票格式的特点,包括新格式发票将发票代码和发票号码合并的情况,以及航空电子客运发票只有电子客票号码的特殊情况。我会根据实际情况灵活处理这些信息,确保输出的JSON数据格式统一且准确。请提供需要处理的电子发票图像链接,我将为您提取关键信息并以JSON格式输出。如果您有任何特殊要求或额外的处理需求,请告诉我。让我们开始工作吧!
这里也本次工作流最核心的地方。这里有个小技巧,就是我们新版本或者票票面信息是没有税率的,我们这里通过提示词约束,让大模型提取票面信息后更加提示词税额计算返回我们要的税额信息
模型这里我们用了多模态,所以需要上传文件(这个文件是PDF转换后的图片信息)。
这里有的小伙伴可能会问了,多模态模型不能直接上传文件吗? 答案是不可以,目前多模态模型只支持 图片URL 或图片base64的值,不支持文件。
这样我们就完成了多模态模型设置
代码执行
这个代码执行主要是处理多模态大语言模型返回的信息。
输入参数arg1 对应的值大模型返回
我们也可以看一下 arg1 的内容
好的,我已经接收到您提供的两张电子发票图像。作为财务发票整理专家,我将按照您的要求,提取关键信息并以JSON格式输出。---**处理结果:**```json[ { "发票代码": "", "发票号码": "253491341140000026713", "开票日期": "2025年06月05日", "开票类目": "铁路客运服务", "金额": 34.00, "税额": 2.81, "发票类型": "铁路电子客票" }, { "发票代码": "011002200911", "发票号码": "69453658", "开票日期": "2023年01月06日", "开票类目": "技术服务费", "金额": 248.11, "税额": 14.89, "发票类型": "增值税电子普通发票" }]
这里我们看到大模型我明明约束它只返回JSON格式的数据了,但是它还是有其他内容,而且不同的模型输出的内容还不一样。
所以这里我们使用代码对它进行处理。(之前文章中我用参数提取器来实现提取)
import jsondef main(arg1: str) -> dict: # 按 ```json 分割,取后半部分 part = arg1.split('```json', 1)[-1] # 再按 ``` 分割,取第一部分并去除首尾空白 json_content = part.split('```', 1)[0].strip() try: # 解析JSON内容 data = json.loads(json_content) # 转换为表格格式 table = [] for item in data: # 为每个发票创建记录,包含所有字段 invoice_data = [ item.get('发票代码', ''), item.get('发票号码', ''), item.get('开票日期', ''), item.get('开票类目', ''), str(item.get('金额', '')), str(item.get('税额', '')), item.get('发票类型', '') ] table.append(invoice_data) return { "result": str(table).replace("'", '"') } except json.JSONDecodeError: # 若JSON解析失败,返回原始内容 return { "result": [["错误", "JSON解析失败"]] }
输出变量是 result 是一个字符串数组。
这里我们需要解释一下,因为我们后面要写到飞书文档里面,飞书文档需要的数据格式数组字符串表格内容信息
输入格式内容如下
处理结果:[["", "253491341140000026713", "2025年06月05日", "铁路客运服务", "34.0", "2.81", "铁路电子客票"], ["011002200911", "69453658", "2023年01月06日", "技术服务费", "248.11", "14.89", "增值税电子普通发票"]]
这里我们也可以把完整测试代码提供给大家,方便大家在开发工具调试通过,然后在贴到代码执行里面
zz.py
import jsondef main(arg1: str) -> dict: # 按 ```json 分割,取后半部分 part = arg1.split('```json', 1)[-1] # 再按 ``` 分割,取第一部分并去除首尾空白 json_content = part.split('```', 1)[0].strip() try: # 解析JSON内容 data = json.loads(json_content) # 转换为表格格式 table = [] for item in data: # 为每个发票创建记录,包含所有字段 invoice_data = [ item.get('发票代码', ''), item.get('发票号码', ''), item.get('开票日期', ''), item.get('开票类目', ''), str(item.get('金额', '')), str(item.get('税额', '')), item.get('发票类型', '') ] table.append(invoice_data) return { "result": str(table).replace("'", '"') } except json.JSONDecodeError: # 若JSON解析失败,返回原始内容 return { "result": [["错误", "JSON解析失败"]] }if __name__ == "__main__": # 测试用例 test_input = """好的,我已经接收到您提供的两张电子发票图像。作为财务发票整理专家,我将按照您的要求,提取关键信息并以JSON格式输出。---**处理结果:**```json[ { "发票代码": "", "发票号码": "253491341140000026713", "开票日期": "2025年06月05日", "开票类目": "铁路客运服务", "金额": 34.00, "税额": 2.81, "发票类型": "铁路电子客票" }, { "发票代码": "011002200911", "发票号码": "69453658", "开票日期": "2023年01月06日", "开票类目": "技术服务费", "金额": 248.11, "税额": 14.89, "发票类型": "增值税电子普通发票" }]```""" result = main(test_input) print("处理结果:") print(result["result"])
飞书表格(新增多行至工作表最后)
这里面我们使用一个飞书表格新增多行至工作表最后
这个飞书表格有3个值。第一个是电子表格token,这里我们可以使用环境变量的参数值把飞书文档地址作为参数设置好
这里有小伙伴可能有疑惑了,这个aqma351r01f.feishu.cn/wiki/K1X3wx… 从哪来。
这个地址是你分享的飞书的URL 地址
关于飞书表格的设置,这里我们就不做详细展开,不会的可以看我之前的文章。dify案例分享-飞书表格记录llm大语言模型聊天信息
有关它的设置的详细介绍。
第二个参数就是 代码执行返回结果
第三 值 就是新增行数,我们这里填写1
直接回复
这个直接回复相对比较简单主要的目的是输出迭代返回的图片信息,llm大语言模型解析的发票的票面信息(JSON值)
以上我们就完成了工作流的搭建。
3.验证及测试
我们制作好的工作流可以在工作流平台上验证测试一下,点击左上角“预览”按钮。点击本地上传文件,我们可以选择多个发票信息
点击执行。
我们完成多发票票面信息转图片。此外通过多模态大语言模型也返回我们需要的发票JSON格式数据
返回JSON 值如下
[ { "发票代码": "", "发票号码": "253170000001206184298", "开票日期": "2025年06月05日", "开票类目": "食品", "金额": 28.23, "税额": 3.68, "发票类型": "增值税电子普通发票" }, { "发票代码": "", "发票号码": "253491341140000026713", "开票日期": "2025年06月05日", "开票类目": "铁路客运服务", "金额": 34.00, "税额": 2.81, "发票类型": "铁路电子客票" }, { "发票代码": "", "发票号码": "784-1234567890", "开票日期": "2021-07-25", "开票类目": "航空客运服务", "金额": 572.93, "税额": 47.07, "发票类型": "航空电子客运发票" }, { "发票代码": "", "发票号码": "24342000000057547026", "开票日期": "2024年05月24日", "开票类目": "研发和技术服务", "金额": 1698.11, "税额": 101.89, "发票类型": "增值税专用发票" }, { "发票代码": "", "发票号码": "8752451414539", "开票日期": "2025-06-02", "开票类目": "航空客运服务", "金额": 780.00, "税额": "", "发票类型": "航空电子客运发票" }]
最后我们在看飞书电子表格数据(5种发票的数据都能解析出来)
哦发现一个BUG 好像航空电子客运发票 发票的税额没有进行计算。(有聪明的小伙伴可以参考我上面的提示词增加航空电子客运发票 税额计算, 税率是9%)
以上我们就完成了多张多种类发票的批量提取验证测试了。
附单张发票测试
music-1258720957.cos.ap-nanjing.myqcloud.com/%E7%94%B5%E…
体验地址
工作流地址:dify.duckcloud.fun/chat/XzO6id…
4.总结
今天主要带大家了解并实现了使用 Dify 工作流搭建批量识别 PDF 电子发票信息生成 Excel 表格(写飞书表格文档)的方案。该工作流的搭建涉及多个关键步骤:开始、迭代处理、PDF 转 PNG、多模态模型使用、代码执行、飞书表格操作。通过整合多个工作流节点和工具,该方案具备良好的扩展性,可以根据需求添加更多功能,如支持图片类型发票的识别等。在验证及测试阶段,我们完成了多张多种类发票的批量提取,成功获取发票 JSON 格式数据并将信息写入飞书电子表格。感兴趣的小伙伴可以按照本文步骤去尝试搭建自己的批量识别 PDF 电子发票信息生成 Excel 表格的工作流。今天的分享就到这里结束了,我们下一篇文章见。
需要工作流 dsl 的小伙伴,请在我开源项目里面查找 github.com/wwwzhouhui/…