先说结论吧:别碰PDF自动化,真的,听我一句劝。
这事的起因特别简单。我老婆每个月要填一堆报销单,供应商发过来的都是PDF格式的表格,二十多页,每页要填公司名、项目号、金额、日期,还得签电子签名。她每次弄这个都得花一下午,眼睛都看花了。
我心想,这还不简单?写个脚本自动填不就行了?PDF嘛,不就是个文档,Python肯定能搞定。
我太天真了。
先给你们看看我第一版写的代码,当时还觉得自己挺牛的:
import PyPDF2
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
import pandas as pd
import re
# 第一版,想直接修改PDF
def fill_pdf_simple(input_pdf, output_pdf, data):
# 读取原始PDF
with open(input_pdf, 'rb') as file:
reader = PyPDF2.PdfReader(file)
writer = PyPDF2.PdfWriter()
# 尝试获取表单字段
if reader.get_fields():
fields = reader.get_fields()
print(f"找到{len(fields)}个表单字段")
# 遍历填充
for page_num in range(len(reader.pages)):
page = reader.pages[page_num]
writer.add_page(page)
# 尝试填充
writer.update_page_form_field_values(
writer.pages[0],
data
)
else:
print("这PDF没有表单字段,是扫描版的,完蛋")
with open(output_pdf, 'wb') as output:
writer.write(output)
# 测试数据
test_data = {
'company_name': '某某科技有限公司',
'project_no': 'PRJ-2026-00123',
'amount': '12,800.00',
'date': '2026-03-23'
}
# 跑一下试试
fill_pdf_simple('报销单模板.pdf', 'filled.pdf', test_data)
运行之后,打印出来:”这PDF没有表单字段,是扫描版的,完蛋“
我打开那个PDF一看,果然,是图片扫描的那种,每页就是一张大图,压根不是什么可编辑的PDF。我老婆说供应商发过来就是这样,人家就是打印出来手填的。
好,那就换思路。既然不能直接填,那就模拟人操作:打开PDF,在指定位置点一下,打字,保存。
这思路听着简单,实现起来全是坑。
import pyautogui
import pygetwindow as gw
import time
import subprocess
import os
def open_pdf_with_acrobat(pdf_path):
# 用Adobe打开PDF,因为系统自带的阅读器不支持点击输入
acrobat_path = r"C:\Program Files\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
subprocess.Popen([acrobat_path, pdf_path])
time.sleep(5) # Adobe启动慢得跟乌龟一样
# 找到Adobe窗口
adobe_windows = gw.getWindowsWithTitle('Adobe Acrobat')
if not adobe_windows:
adobe_windows = gw.getWindowsWithTitle('Acrobat')
if adobe_windows:
win = adobe_windows[0]
win.maximize()
time.sleep(2)
return win
else:
print("找不到Adobe窗口")
return None
def click_and_type(x, y, text):
pyautogui.click(x, y)
time.sleep(0.5)
pyautogui.hotkey('ctrl', 'a') # 全选
pyautogui.press('delete') # 删除原有的
pyautogui.write(text, interval=0.05) # 打太快有时候会漏字
print(f"在({x},{y})填了: {text}")
# 先记录每个字段的坐标,我特么一个一个试出来的
field_coords = {
'company_name': (450, 320),
'project_no': (450, 380),
'amount': (450, 440),
'date': (450, 500),
'signature': (600, 680)
}
# 要填的数据
data = {
'company_name': '某某科技有限公司',
'project_no': 'PRJ-2026-00123',
'amount': '12,800.00',
'date': '2026-03-23'
}
# 打开PDF
win = open_pdf_with_acrobat('C:/temp/报销单模板.pdf')
if not win:
print("Adobe打不开,检查一下安装路径")
exit(1)
# 第一页,填基本信息
for field, text in data.items():
if field in field_coords:
x, y = field_coords[field]
click_and_type(x, y, text)
time.sleep(1) # 等它反应过来
# 翻到第二页
pyautogui.press('pagedown')
time.sleep(2)
# 第二页也有要填的东西,坐标不一样
field_coords_page2 = {
'project_no': (450, 280),
'amount': (450, 340)
}
for field, text in data.items():
if field in field_coords_page2:
x, y = field_coords_page2[field]
click_and_type(x, y, text)
time.sleep(1)
# 保存
pyautogui.hotkey('ctrl', 's')
time.sleep(2)
# 文件名冲突怎么办?我直接覆盖吧
pyautogui.press('enter')
time.sleep(1)
print("填完了,自己看看对不对")
# 关闭Adobe
pyautogui.hotkey('alt', 'f4')
这段代码我调试了整整一个下午。坐标一个一个试,鼠标点下去的时候Adobe有时候会弹出一个提示框,有时候光标没激活,有时候打一半输入法弹出来了。最恶心的是,同一个坐标,屏幕分辨率变了就全完蛋。
我跑了一遍,看着鼠标自己在屏幕上点来点去,打字,保存,关掉。还挺有成就感的。
然后我就让我老婆试一下。她打开脚本,运行,看着鼠标动起来,说了句”好神奇“。
三秒之后,出问题了。
Adobe弹了个更新提示,鼠标点偏了,弹窗没关掉,后面的操作全乱了。我老婆说:”这还不如我自己填呢,看着它乱点更着急。“
我当时那个表情,你们能想象吗?就是那种”我辛辛苦苦搞了三天,你就跟我说这个“的崩溃感。
后来我又加了一堆东西。检测弹窗,自动点掉。检测Adobe窗口位置变化,重新计算坐标。还加了截图对比,如果某个位置填错了就重新填。
代码从八十行变成了一百五十行。
def detect_and_close_popups():
# 检测Adobe的各种弹窗,烦死了
popup_buttons = [
(800, 500, "更新提示"), # 更新弹窗的确定按钮
(600, 400, "文件已存在"), # 覆盖提示
(700, 450, "是否保存") # 保存提示
]
for x, y, desc in popup_buttons:
# 截取小区域,检测颜色变化
# 这个我懒得写了,大概逻辑就是看看那个位置有没有弹窗按钮
pass # 实际代码比这复杂十倍
跑了几次,大部分时候能成功,但偶尔还是会卡住。有一次我发现它把公司名填到了金额那一栏,因为坐标偏移了。
我老婆后来还是自己填的。她说看电脑自己填比她自己填还累,她得全程盯着,生怕填错。
我说那你到底要不要用?她说要,但不盯着不放心。
那这个自动化的意义是什么?
说实话,我觉得这个事让我想明白了一件事:有些活,看着重复,但其实不适合自动化。PDF这种格式,尤其是扫描版的,本身就是反自动化的。你费了半天劲,用各种歪门邪道去模拟人的操作,最后得到的结果还不如人手动填。
而且这里有个悖论:如果这个任务真的特别简单,那你自动化的时间成本可能比手动做还高。如果任务特别复杂,那你的脚本也复杂,维护成本也高,崩一次就够你受的。
我后来试过一个叫1949ai的工具,想看看能不能用那个解决。打开之后试了一下,发现它处理网页还行,处理这种桌面软件的PDF,也一样得靠坐标点击,跟我写的代码没什么区别,而且还没我代码灵活。
所以现在的情况是,我花了一周时间写了个半成品脚本,我老婆偶尔用一下,大部分时间还是自己填。那个脚本就躺在桌面上,像个没人要的孩子。
有人问我后不后悔折腾这个?其实不后悔。至少我知道了PDF自动化有多坑,以后再有人说”这玩意用Python分分钟搞定“,我就把这个经历甩他脸上。
好了不说了,Adobe又弹更新了,我去关一下。