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

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

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

本文以发票助手获取发票信息为例,详细介绍如何使用 .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 文件。希望这篇文章能帮助你更好地理解和实现发票信息的提取和处理。如果你有任何问题或建议,欢迎在评论区留言

相关推荐

python创建文件夹,轻松搞定,喝咖啡去了

最近经常在录视频课程,一个课程下面往往有许多小课,需要分多个文件夹来放视频、PPT和案例,这下可好了,一个一个手工创建,手酸了都做不完。别急,来段PYTHON代码,轻松搞定,喝咖啡去了!import...

如何编写第一个Python程序_pycharm写第一个python程序

一、第一个python程序[掌握]python:python解释器,将python代码解释成计算机认识的语言pycharm:IDE(集成开发环境),写代码的一个软件,集成了写代码,...

Python文件怎么打包为exe程序?_python3.8打包成exe文件

PyInstaller是一个Python应用程序打包工具,它可以将Python程序打包为单个独立可执行文件。要使用PyInstaller打包Python程序,需要在命令行中使用py...

官方的Python环境_python环境版本

Python是一种解释型编程开发语言,根据Python语法编写出来的程序,需要经过Python解释器来进行执行。打开Python官网(https://www.python.org),找到下载页面,选择...

[编程基础] Python配置文件读取库ConfigParser总结

PythonConfigParser教程显示了如何使用ConfigParser在Python中使用配置文件。文章目录1介绍1.1PythonConfigParser读取文件1.2Python...

Python打包exe软件,用这个库真的很容易

初学Python的人会觉得开发一个exe软件非常复杂,其实不然,从.py到.exe文件的过程很简单。你甚至可以在一天之内用Python开发一个能正常运行的exe软件,因为Python有专门exe打包库...

2025 PyInstaller 打包说明(中文指南),python 打包成exe 都在这里

点赞标记,明天就能用上这几个技巧!linux运维、shell、python、网络爬虫、数据采集等定定做,请私信。。。PyInstaller打包说明(中文指南)下面按准备→基本使用→常用...

Python自动化办公应用学习笔记40—文件路径2

4.特殊路径操作用户主目录·获取当前用户的主目录路径非常常用:frompathlibimportPathhome_dir=Path.home()#返回当前用户主目录的Path对象...

Python内置tempfile模块: 生成临时文件和目录详解

1.引言在Python开发中,临时文件和目录的创建和管理是一个常见的需求。Python提供了内置模块tempfile,用于生成临时文件和目录。本文将详细介绍tempfile模块的使用方法、原理及相关...

python代码实现读取文件并生成韦恩图

00、背景今天战略解码,有同学用韦恩图展示各个产品线的占比,效果不错。韦恩图(Venndiagram),是在集合论数学分支中,在不太严格的意义下用以表示集合的一种图解。它们用于展示在不同的事物群组之...

Python技术解放双手,一键搞定海量文件重命名,一周工作量秒搞定

摘要:想象一下,周五傍晚,办公室的同事们纷纷准备享受周末,而你,面对着堆积如山的文件,需要将它们的文件名从美国日期格式改为欧洲日期格式,这似乎注定了你将与加班为伍。但别担心,Python自动化办公来...

Python路径操作的一些基础方法_python路径文件

带你走进@机器人时代Discover点击上面蓝色文字,关注我们Python自动化操作文件避开不了路径操作方法,今天我们来学习一下路径操作的一些基础。Pathlib库模块提供的路径操作包括路径的...

Python爬取下载m3u8加密视频,原来这么简单

1.前言爬取视频的时候发现,现在的视频都是经过加密(m3u8),不再是mp4或者avi链接直接在网页显示,都是经过加密形成ts文件分段进行播放。今天就教大家如果通过python爬取下载m3u8加密视频...

探秘 shutil:Python 高级文件操作的得力助手

在Python的标准库中,shutil模块犹如一位技艺精湛的工匠,为我们处理文件和目录提供了一系列高级操作功能。无论是文件的复制、移动、删除,还是归档与解压缩,shutil都能以简洁高效的方式完成...

怎么把 Python + Flet 开发的程序,打包为 exe ?这个方法很简单!

前面用Python+Flet开发的“我的计算器v3”,怎么打包为exe文件呢?这样才能分发给他人,直接“双击”运行使用啊!今天我给大家分享一个简单的、可用的,把Flet开发的程序打包为...