最可靠方式是Windows下调用shell32.dll的ShellExecuteEx以"print"动词静默打印PDF,需设fMask为SEE_MASK_NOCLOSEPROCESS|SEE_MASK_FLAG_NO_UI、绝对路径,且依赖默认打印机;指定打印机须用comtypes调Office COM接口。

Windows下用shell32.dll调用ShellExecuteEx后台静默打印PDF
直接调用系统API打印PDF最可靠的方式,是绕过Python生态里那些半吊子库(比如win32print不支持PDF、PyPDF2根本不能打),走Windows原生的“用默认PDF阅读器打开并打印”路径。关键在于让ShellExecuteEx带"print"动词执行,且不弹窗。
- 必须用
ShellExecuteEx而非os.startfile:后者无法控制打印参数,也无法抑制窗口 -
fMask要设为SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI,否则可能弹出Acrobat或Edge窗口 - 目标文件路径必须是绝对路径,相对路径在后台服务/计划任务中大概率失败
- 打印机名不传给这个API——它走的是系统默认打印机;想指定打印机?得换方案(见下一条)
import ctypes
from ctypes import wintypes
<p>shell32 = ctypes.windll.shell32
SEE_MASK_NOCLOSEPROCESS = 0x40
SEE_MASK_FLAG_NO_UI = 0x400</p><p>class SHELLEXECUTEINFO(ctypes.Structure):
<em>fields</em> = [
('cbSize', wintypes.DWORD),
('fMask', wintypes.ULONG),
('hwnd', wintypes.HWND),
('lpVerb', wintypes.LPCWSTR),
('lpFile', wintypes.LPCWSTR),
('lpParameters', wintypes.LPCWSTR),
('lpDirectory', wintypes.LPCWSTR),
('nShow', wintypes.INT),
('hInstApp', wintypes.HINSTANCE),
('lpIDList', wintypes.LPCVOID),
('lpClass', wintypes.LPCWSTR),
('hkeyClass', wintypes.HKEY),
('dwHotKey', wintypes.DWORD),
('hIcon', wintypes.HANDLE),
('hProcess', wintypes.HANDLE),
]</p><p>info = SHELLEXECUTEINFO()
info.cbSize = ctypes.sizeof(SHELLEXECUTEINFO)
info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI
info.lpVerb = "print"
info.lpFile = r"C:\report.pdf" # 必须绝对路径
info.nShow = 0
shell32.ShellExecuteExW(ctypes.byref(info))
指定打印机名打印Office文档需用comtypes调Word.Application或Excel.Application
Office套件本身提供稳定COM接口,能精确控制打印机名、份数、页码范围、是否后台、是否等待完成。这是唯一能真正“指定打印机”的自动化路径,但代价是必须本机装对应版本Office(不是仅装Runtime)。
- Word和Excel的
PrintOut方法参数差异大:Word.Application用PrinterName参数,Excel.Application靠ActivePrinter属性预设 - 务必设置
Visible=False和DisplayAlerts=False,否则弹窗阻塞流程 - 调用完别忘了
.Quit()并显式del app,否则后台残留进程吃内存 - 如果遇到
RPC_E_SERVERFAULT错误,大概率是Office被另一个实例占用或权限问题,建议用独立用户上下文运行
import comtypes.client
word = comtypes.client.CreateObject("Word.Application")
word.Visible = False
word.DisplayAlerts = False
doc = word.Documents.Open(r"C:\invoice.docx")
doc.PrintOut(PrinterName="HP LaserJet MFP M428fdw") # Word支持直接传打印机名
doc.Close()
word.Quit()
del word
Linux/macOS上没系统级打印API,只能靠lpr命令+Ghostscript中转PDF
Linux/macOS没有类似ShellExecuteEx的统一打印入口,lpr是事实标准,但它只认PostScript或纯文本。PDF要打,必须先用gs(Ghostscript)转成PS或直接喂给lpr -P。
-
lpr -P "HP-OfficeJet" file.pdf看似可行,实际依赖CUPS配置——很多PDF直打会失败或乱码,必须加-o document-format=application/pdf - 更稳的做法是用
gs先转:gs -dNOPAUSE -dBATCH -sDEVICE=ps2write -sOutputFile=- input.pdf | lpr -P "HP-OfficeJet" - macOS注意:
lpstat -p查到的打印机名常含空格或括号,要用引号包裹,且CUPS默认不启用PDF后端,可能需手动开启cups-pdf或改配置 - 权限坑:脚本若以root运行,
lpr可能找不到用户级CUPS队列,建议始终用目标用户身份运行
后台打印失败常见报错及定位点
所有平台共性问题是“看起来没报错,但纸没出来”。这不是Python的问题,而是系统打印子系统在后台静默吞掉错误。
立即学习“Python免费学习笔记(深入)”;
-
ERROR_SPOOLER_NOT_READY(Windows):打印后台处理程序卡死,net stop spooler && net start spooler可临时恢复 -
lpr: unable to access "xxx.pdf": No such file or directory:路径是相对路径,或Python工作目录和预期不符 - Office COM调用卡住无响应:检查任务管理器是否有残留
WINWORD.EXE/EXCEL.EXE进程,杀掉再试 - PDF打印内容错位/空白:不是代码问题,是PDF本身含JavaScript或加密,Ghostscript或Acrobat引擎拒绝渲染——换用
pdf2image转图再打(但会丢矢量精度)
真正的难点不在调哪个函数,而在于每次换打印机型号、换Office版本、换CUPS配置,底层行为都可能变。写死的打印机名、硬编码的路径、忽略用户环境权限,比语法错误更难调试。











