百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

发票助手:使用.NET处理PDF文件和二维码解析

itomcoil 2025-03-20 15:42 14 浏览

本文以发票助手获取发票信息为例,详细介绍如何使用 .NET 技术处理 PDF 文件并进行二维码解析。文章介绍的相关代码已开源在GitHub,欢迎查看和收藏。

1. 背景

在日常工作中,我们经常需要处理发票信息,比如日常报销和出差等等场景。如果发票比较多,能有一款工具可以方便的帮助我们处理 PDF 发票就非常棒了,当然我们其实有很多的选则,比如微信的卡包,还有QQ邮箱、WPS的发票工具等等,但是这些工具都有一些局限性。因此,我们可以自己开发一款发票助手,通过读取 PDF 文件中的发票二维码并解析其内容,实现发票信息的提取和处理。

2. 发票的变化

电子发票越来越普及,我们可以通过扫码获取电子发票,也可以通过邮件或者网站下载电子发票。电子发票的好处是方便快捷,不需要纸质发票,可以随时随地查看和打印。

不知道大家有没有发现,最近的发票都已经换成了新的数电发票,增值税专用发票也使用了电子化,省去了邮寄的环节,减少了开票时间和开票成本。数电发票增加XML的数据电文格式便利交付,更加方便了信息的提取,同时也保留PDF/OFD格式。


数电发票


3. 发票中的二维码

无论是之前的电子发票还是新的数电发票,在发票的左上角一般都会有一个二维码,里面通常包含了发票的关键信息,如发票代码、发票号码、金额、日期等。我们可以通过解析二维码来获取这些信息,从而实现发票信息的提取和处理。当然,这些信息我们也可以直接通过微信扫码来获取和测试。

以下是一个发票二维码的扫码获取的信息的示例,部分数字信息已经做了模糊处理,使用x代替了数字:

01,10,011002400xxx,35602xxx,1058.34,20240xxx,016258xxx15879380xxx,Fxxx,01,31,,24117000000xxx133xxx,476.60,20241xxx,,7xxx01,32,,24117000000xxx771xxx,1472.24,20241xxx,,6xxx

上面的第一个发票是今年早些时候的老款电子发票,第二个和第三个是新款数电发票。下面,我们重点分析一下属性对应的内容及含义,他们的信息以逗号为分隔符:

这里第一个固定属性值 01,第二个属性值 10 则是发票类型,具体含义如下:

属性值
发票类型
01
增值税专用发票
04
增值税普通发票
10
增值税普通发票(电子)
08
增值税专用发票(电子)
31
数电专票
32
数电普票

后面几个分别是发票的代码、号码、金额、日期和校验码等信息。

4. 使用 .NET 处理 PDF 文件和二维码解析

了解了发票中二维码的信息后,我们可以使用 .NET 技术来处理 PDF 文件并进行二维码解析。在这里,我们将使用 UglyToad.PdfPig 库来读取 PDF 文件,使用 ZXing 库来解析二维码。

4.1. 准备工作

在开始之前,请确保你已经安装了以下 NuGet 包:

oUglyToad.PdfPigoZXing.Net

你可以通过以下命令安装这些包:

dotnet add package PdfPigdotnet add package ZXing.Net

4.2. 获取PDF文件中的二维码图片并解析

首先,我们需要读取 PDF 文件中的第一个页面,并获取其中的第一个图像。然后,我们将该图像转换为 Bitmap 对象,并使用 ZXing 库的 BarcodeReader 对象解析二维码。最后,我们将解析出的发票信息添加到 DataGridView 控件中。

using (PdfDocument document = PdfDocument.Open(fullname)){ Page firstPage = document.GetPages().FirstOrDefault(); if (firstPage != ) { var firstImage = firstPage.GetImages().FirstOrDefault(); if (firstImage != ) { var bitmap = ConvertPdfImageToBitmap(firstImage); var result = reader.Decode(bitmap); if (result != ) { string[] values = result.Text.Split(','); if (values.Length < 8) break; dgvPdfFiles.Rows.Add(file.FullName, file.Name, values[3], values[5], values[4]); } } }}

当然,实际情况可能更加复杂,你可能需要多个图像的问题,并不一定所有的PDF文件第一个图片就是二维码,你可能需要根据具体的情况来处理。比如可以通过判断图片的大小来确定是否是二维码。

var images = firstPage.GetImages().Where(i => i.HeightInSamples == i.WidthInSamples && i.WidthInSamples > 100 && i.HeightInSamples > 100);var firstImage = images.FirstOrDefault();

4.3. 发票其他信息提取

除了二维码中的信息,我们还可以通过读取 PDF 文件的文本内容来提取发票的其他信息,比如项目明细或是在非数电发票的情况下,我们需要通过文本内容来提取发票信息的含税金额信息。因为数电发票是含税的,之前的发票是不含税的,所以我们需要根据具体的情况来处理。这里我们可以使用 UglyToad.PdfPig 库的 Page.Text 属性来获取页面的文本内容。

以下是相关的正则表达式,用于匹配发票的日期、号码、类目和金额等信息:

/// /// 正则匹配年月日/// 开票日期[::]\s*\d{4}年\d{2}月\d{2}日/// </summary>private static readonly Regex dateRegex = new Regex(@"\d{4}年\d{2}月\d{2}日", RegexOptions.Compiled);
/// /// 匹配发票号码/// </summary>private static readonly Regex noRegex = new Regex(@"发票号码[::]\s*(\d+)", RegexOptions.Compiled);
/// /// 匹配类目/// 匹配到第一个,然后去除两边的*号/// </summary>private static readonly Regex typeRegex = new Regex(@"\*.*?\*", RegexOptions.Compiled);
/// /// 匹配金额/// </summary>private static readonly Regex amountRegex2 = new Regex(@"[yen¥]\s*([0-9]+[.][0-9]{2})", RegexOptions.Compiled );

这里简单介绍一下类目和发票号码的匹配,其他的匹配可以根据具体的情况来处理:

// 处理类目var typeMatch = typeRegex.Match(text);if (typeMatch.Success){ var type = typeMatch.Value.Trim('*'); dgvPdfFiles.Rows[dgvPdfFiles.Rows.Count - 1].Cells["InvoiceType"].Value = type;}// 处理发票号码if(dgvPdfFiles.Rows[dgvPdfFiles.Rows.Count - 1].Cells["InvoiceNo"].Value.ToString() == "?"){ var noMatch = noRegex.Match(text); if (noMatch.Success) { var no = noMatch.Groups[1].Value; dgvPdfFiles.Rows[dgvPdfFiles.Rows.Count - 1].Cells["InvoiceNo"].Value = no; }}

当然,实际的情况可能更加复杂,比如之前的发票可能存在密码区,会造成文本内容的提取不准确等。不过,后面的发票都是数电发票,不存在这个问题了。而且出了XML的数据电文格式,更加方便了信息的提取,没必要这么麻烦了。

4.4. 发票信息表

将提取的发票信息添加到 DataGridView 控件中,除了方便我们查看和管理外。这里我们也可以通过 DataGridView 导出 Excel 表格,以下代码展示了如何将发票信息导出为 CSV 文件:

/// /// 导出/// /// <param name="sender"></param>/// <param name="e"></param>private void btnExport_Click(object sender, EventArgs e){ // 将列表导出CSV文件
using (SaveFileDialog sfd = new SaveFileDialog() { FileName = "发票数据.csv", Filter = "CSV文件|*.csv", Title = "保存CSV文件" }) { if (sfd.ShowDialog() == DialogResult.OK) { string outputFilePath = sfd.FileName;
using (StreamWriter sw = new StreamWriter(outputFilePath, false, Encoding.UTF8)) { // 带 BOM 的 UTF-8 文件头 sw.WriteLine("\uFEFF文件名,发票号码,开票日期,开票类目,金额");
foreach (DataGridViewRow row in dgvPdfFiles.Rows) { string fileName = row.Cells["FileName"].Value.ToString(); string invoiceNo = row.Cells["InvoiceNo"].Value.ToString(); string invoiceDate = row.Cells["InvoiceDate"].Value.ToString(); string invoiceType = row.Cells["InvoiceType"].Value.ToString(); string invoiceAmount = row.Cells["InvoiceAmount"].Value.ToString();
sw.WriteLine($"{fileName},{invoiceNo},{invoiceDate},{invoiceType},{invoiceAmount}"); } }
// 询问是否打开文件 if (MessageBox.Show("CSV文件导出完成,是否打开?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { System.Diagnostics.Process.Start(outputFilePath); }
txtStatus.Text = "CSV文件导出完成"; } }}

在我们提取了类目之后,我们也可以通过类目来统计发票的总额,这样可以方便我们进行发票管理和统计。

/// /// 导出发票类目及金额信息/// /// <param name="sender"></param>/// <param name="e"></param>private void btnExportType_Click(object sender, EventArgs e){ // 将列表导出CSV文件 using (SaveFileDialog sfd = new SaveFileDialog() { FileName = "发票类目金额.csv", Filter = "CSV文件|*.csv", Title = "保存CSV文件" }) { if (sfd.ShowDialog() == DialogResult.OK) { string outputFilePath = sfd.FileName; using (StreamWriter sw = new StreamWriter(outputFilePath, false, Encoding.UTF8)) { // 带 BOM 的 UTF-8 文件头 sw.WriteLine("\uFEFF开票类目,金额"); var query = dgvPdfFiles.Rows.Cast().GroupBy(r => r.Cells["InvoiceType"].Value.ToString()) .Select(g => new { InvoiceType = g.Key, Amount = g.Sum(r => Convert.ToDecimal(r.Cells["InvoiceAmount"].Value)) }); foreach (var item in query) { sw.WriteLine($"{item.InvoiceType},{item.Amount}"); } }
// 询问是否打开文件 if (MessageBox.Show("CSV文件导出完成,是否打开?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { System.Diagnostics.Process.Start(outputFilePath); }
txtStatus.Text = "CSV文件导出完成"; } }
}


发票信息


5. PDF合并和打印

除了提取发票信息,我们还可以使用 .NET 技术来实现 PDF 文件的合并和打印。比如,我们可以将多个发票 PDF 文件合并成一个 PDF 文件,或者直接打印发票 PDF 文件。这样可以方便我们进行发票管理和归档。

将PDF文件合并成一个PDF文件可以方便我们进行打印,这样在打印的时候可以方便调整每张纸打印的页数,比如可以打印两张或者四张等等。

// 合并PDF文件private void MergePdfFiles(string[] pdfFiles, string outputFilePath){ PdfDocumentBuilder builder = new PdfDocumentBuilder();
foreach (string pdfFile in pdfFiles) { using (PdfDocument inputDocument = PdfDocument.Open(pdfFile)) { for (var i = 0; i < inputDocument.NumberOfPages; i++) { builder.AddPage(inputDocument, i + 1); } } }
//保存PDF文件 var documentBytes = builder.Build(); File.WriteAllBytes(outputFilePath, documentBytes);}

其实打印PDF文件也很简单,当然这个只是最简单的实现方式,调用系统打开PDF文件,然后发送打印指令,这样就可以打印PDF文件了。

/// /// 打印指定文件/// </summary>/// <param name="tempPdfFile"></param>private async void PrintPdfFile(string tempPdfFile){ System.Diagnostics.Process.Start("explorer", tempPdfFile); await Task.Delay(1000); // 发送 Ctrl + P SendKeys.SendWait("^(p)");}

6. 总结

通过以上代码,我们展示了如何使用 .NET 结合 UglyToad.PdfPigZXing 库从 PDF 文件获取图片,并解析二维码信息,同时介绍了如何提取发票的其他信息,如日期、号码、类目和金额等。最后,我们还展示了如何将提取的发票信息导出为 CSV 文件,以及如何合并和打印 PDF 文件。希望这篇文章能帮助你更好地理解和实现发票信息的提取和处理。如果你有任何问题或建议,欢迎在评论区留言

相关推荐

selenium(WEB自动化工具)

定义解释Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7,8,9,10,11),MozillaF...

开发利器丨如何使用ELK设计微服务中的日志收集方案?

【摘要】微服务各个组件的相关实践会涉及到工具,本文将会介绍微服务日常开发的一些利器,这些工具帮助我们构建更加健壮的微服务系统,并帮助排查解决微服务系统中的问题与性能瓶颈等。我们将重点介绍微服务架构中...

高并发系统设计:应对每秒数万QPS的架构策略

当面试官问及"如何应对每秒几万QPS(QueriesPerSecond)"时,大概率是想知道你对高并发系统设计的理解有多少。本文将深入探讨从基础设施到应用层面的解决方案。01、理解...

2025 年每个 JavaScript 开发者都应该了解的功能

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.Iteratorhelpers开发者...

JavaScript Array 对象

Array对象Array对象用于在变量中存储多个值:varcars=["Saab","Volvo","BMW"];第一个数组元素的索引值为0,第二个索引值为1,以此类推。更多有...

Gemini 2.5编程全球霸榜,谷歌重回AI王座,神秘模型曝光,奥特曼迎战

刚刚,Gemini2.5Pro编程登顶,6美元性价比碾压Claude3.7Sonnet。不仅如此,谷歌还暗藏着更强的编程模型Dragontail,这次是要彻底翻盘了。谷歌,彻底打了一场漂亮的翻...

动力节点最新JavaScript教程(高级篇),深入学习JavaScript

JavaScript是一种运行在浏览器中的解释型编程语言,它的解释器被称为JavaScript引擎,是浏览器的一部分,JavaScript广泛用于浏览器客户端编程,通常JavaScript脚本是通过嵌...

一文看懂Kiro,其 Spec工作流秒杀Cursor,可移植至Claude Code

当Cursor的“即兴编程”开始拖累项目质量,AWS新晋IDEKiro以Spec工作流打出“先规范后编码”的系统工程思维:需求-设计-任务三件套一次生成,文档与代码同步落地,复杂项目不...

「晚安·好梦」努力只能及格,拼命才能优秀

欢迎光临,浏览之前点击上面的音乐放松一下心情吧!喜欢的话给小编一个关注呀!Effortscanonlypass,anddesperatelycanbeexcellent.努力只能及格...

JavaScript 中 some 与 every 方法的区别是什么?

大家好,很高兴又见面了,我是姜茶的编程笔记,我们一起学习前端相关领域技术,共同进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力在JavaScript中,Array.protot...

10个高效的Python爬虫框架,你用过几个?

小型爬虫需求,requests库+bs4库就能解决;大型爬虫数据,尤其涉及异步抓取、内容管理及后续扩展等功能时,就需要用到爬虫框架了。下面介绍了10个爬虫框架,大家可以学习使用!1.Scrapysc...

12个高效的Python爬虫框架,你用过几个?

实现爬虫技术的编程环境有很多种,Java、Python、C++等都可以用来爬虫。但很多人选择Python来写爬虫,为什么呢?因为Python确实很适合做爬虫,丰富的第三方库十分强大,简单几行代码便可实...

pip3 install pyspider报错问题解决

运行如下命令报错:>>>pip3installpyspider观察上面的报错问题,需要安装pycurl。是到这个网址:http://www.lfd.uci.edu/~gohlke...

PySpider框架的使用

PysiderPysider是一个国人用Python编写的、带有强大的WebUI的网络爬虫系统,它支持多种数据库、任务监控、项目管理、结果查看、URL去重等强大的功能。安装pip3inst...

「机器学习」神经网络的激活函数、并通过python实现激活函数

神经网络的激活函数、并通过python实现whatis激活函数感知机的网络结构如下:左图中,偏置b没有被画出来,如果要表示出b,可以像右图那样做。用数学式来表示感知机:上面这个数学式子可以被改写:...