如何开发自动提取PDF表单数据写入SQL的后台定时服务
基于PowerShell的无人值守采购订单导入方案
你选PowerShell做全流程实现是非常合适的,整套方案不需要额外开发重型桌面/服务程序,部署维护成本极低,完全可以实现无弹窗静默后台运行,核心实现逻辑如下:
前置依赖准备
所有依赖都可以离线部署,不需要服务器连外网:
- 数据提取:针对带表单域的标准PDF采购单(绝大多数企业用的可填写PDF订单都是AcroForm格式),直接用iText社区版的DLL读取表单域值,不需要OCR,识别准确率100%;如果是扫描版非结构化PDF,搭配Tesseract OCR做固定区域识别即可
- 数据库操作:直接用PowerShell官方的SqlServer模块,支持批量高速写入SQL Server,不需要逐行插入
- 后台托管:不需要自己写循环调度逻辑,用Windows任务计划程序或NSSM即可实现开机自启、崩溃自恢复、无界面静默运行,比自研服务稳定性更高
核心脚本实现
首次环境配置
以管理员身份打开PowerShell,执行以下命令安装SQL模块:
Install-Module -Name SqlServer -Force -AllowClobber -Scope LocalMachine
将iText相关的依赖DLL放到脚本同级的lib目录下,不需要全局注册。
主脚本结构
把配置项统一放在脚本开头,方便后续维护调整:
# -------------------------- 配置项 -------------------------- $pendingPath = "D:\PO_System\Wait_Import" # 待处理PDF存放目录 $archivedPath = "D:\PO_System\Imported" # 已处理PDF归档目录 $errorPath = "D:\PO_System\Parse_Error" # 解析失败文件存放目录 $logPath = "D:\PO_System\Run_Log.log" # 运行日志路径 $sqlConnString = "Server=你的SQL实例地址;Database=采购订单库;Integrated Security=True;Encrypt=False;" # 优先用Windows集成认证,避免硬编码账号密码 $tableName = "dbo.purchase_order" # 采购订单存储表名 # ----------------------------------------------------------- # 加载PDF处理依赖 Add-Type -Path ".\lib\itext.kernel.dll" Add-Type -Path ".\lib\itext.forms.dll" # 日志写入函数 function Write-Log { param([string]$Content) Add-Content -Path $logPath -Value "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Content" } # PDF表单数据提取函数 function Read-PoFormData { param([string]$PdfFullPath) $reader = [iText.Kernel.Pdf.PdfReader]::new($PdfFullPath) $doc = [iText.Kernel.Pdf.PdfDocument]::new($reader) $form = [iText.Forms.PdfAcroForm]::getAcroForm($doc, $true) $fields = $form.getFormFields() # 以下字段名请根据你实际PDF的表单域名称调整,首次运行可以遍历$fields的键名打印所有域确认 $poInfo = [PSCustomObject]@{ po_no = $fields.Get("PO_Number").GetValueAsString().Trim() vendor = $fields.Get("Vendor_FullName").GetValueAsString().Trim() total_amount = [decimal]$fields.Get("PO_Total").GetValueAsString().Trim() po_date = [datetime]$fields.Get("PO_Date").GetValueAsString().Trim() source_file = Split-Path $PdfFullPath -Leaf import_time = Get-Date } $doc.Close() $reader.Close() return $poInfo } # 初始化目录 if (-not (Test-Path $archivedPath)) { New-Item -Path $archivedPath -ItemType Directory | Out-Null } if (-not (Test-Path $errorPath)) { New-Item -Path $errorPath -ItemType Directory | Out-Null } # 遍历待处理PDF $poList = New-Object System.Collections.Generic.List[Object] Get-ChildItem -Path $pendingPath -Filter "*.pdf" -File | Where-Object { $_.Name -notlike "~$*" } | ForEach-Object { try { # 检查文件是否被占用,避免读取正在拷贝/编辑的不完整文件 $fs = $_.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) $fs.Close() # 提取数据 $data = Read-PoFormData -PdfFullPath $_.FullName $poList.Add($data) # 归档已处理文件 Move-Item -Path $_.FullName -Destination (Join-Path $archivedPath $_.Name) -Force Write-Log "文件 $($_.Name) 解析完成" } catch { Write-Log "文件 $($_.Name) 处理失败:$($_.Exception.Message)" Move-Item -Path $_.FullName -Destination (Join-Path $errorPath $_.Name) -Force } } # 批量写入SQL Server if ($poList.Count -gt 0) { try { $bulk = [System.Data.SqlClient.SqlBulkCopy]::new($sqlConnString) $bulk.DestinationTableName = $tableName # 字段映射:左边是PowerShell对象属性名,右边是SQL表列名 $bulk.ColumnMappings.Add("po_no", "po_number") | Out-Null $bulk.ColumnMappings.Add("vendor", "vendor_name") | Out-Null $bulk.ColumnMappings.Add("total_amount", "total_amount") | Out-Null $bulk.ColumnMappings.Add("po_date", "order_date") | Out-Null $bulk.ColumnMappings.Add("source_file", "source_file_name") | Out-Null $bulk.ColumnMappings.Add("import_time", "import_time") | Out-Null # 转DataTable写入 $dataTable = [System.Data.DataTable]::new() $poList | ForEach-Object { $row = $dataTable.NewRow() foreach ($prop in $_.PSObject.Properties) { if (-not $dataTable.Columns.Contains($prop.Name)) { $dataTable.Columns.Add($prop.Name, $prop.Value.GetType()) | Out-Null } $row[$prop.Name] = $prop.Value } $dataTable.Rows.Add($row) } $bulk.WriteToServer($dataTable) $bulk.Close() Write-Log "数据写入完成,共导入 $($poList.Count) 条采购订单" } catch { Write-Log "数据库写入失败:$($_.Exception.Message)" } }
静默后台运行配置
不要在脚本内写无限循环+Start-Sleep做定时调度,这种方式异常退出后无法自动恢复,资源占用也更高,直接用系统原生能力实现服务级运行效果:
- 每日定时跑的场景:用Windows任务计划程序创建任务
- 安全选项选择「不管用户是否登录都要运行」,取消勾选「只有用户登录时才运行」,运行时不会弹出PowerShell窗口
- 触发器设置为你需要的每日执行时间,也可以设置为每10/30分钟执行一次,满足近实时导入需求
- 操作选择启动程序,程序路径填
powershell.exe,参数填-ExecutionPolicy Bypass -WindowStyle Hidden -File "你的脚本全路径.ps1" - 设置任务失败自动重启规则:失败后每5分钟重试,最多重试3次,避免偶发异常导致任务中断
- 7*24小时长驻运行的场景:用NSSM把脚本注册为原生Windows Service,支持开机自启、崩溃自动拉起、资源占用监控,和自研Service程序没有体验差异
落地注意事项
- 首次调试时先把所有表单域打印出来确认名称,避免字段对应错误
- 数据库连接优先用Windows集成认证,不要把数据库账号密码硬编码在脚本里,降低安全风险
- 如果你的采购单是扫描版PDF,把
Read-PoFormData函数替换为Tesseract OCR识别逻辑即可,固定模板下提前预设识别区域,识别准确率可以满足业务需求 - 可以在写入前增加重复校验逻辑,按PO单号+源文件名判断是否已经导入,避免重复插入数据
- 所有操作必须留日志,出问题可以直接定位是文件解析错、文件被占用还是数据库连接问题
内容的提问来源于stack exchange,提问作者Une Jolie fleur
相关产品推荐
相关产品推荐

