
说实话第一次接到把系统里的一批报表页面每天自动另存为 PDF 归档这个需求时我第一反应是这有什么难的CtrlP 然后选另存为 PDF不就行了但等我把需求完整听完人傻了。每天几十个页面、凌晨定时跑、还要按日期自动归档命名手动操作根本不可能。于是我掏出 Python Selenium打算把这套网页打印机流程彻底自动化。折腾下来发现Selenium 调用网页打印机存 PDF 这件事一共能走三条完全不同的技术路线。第一条是无对话框直接把页面渲染成 PDF第二条是通过 Chrome DevTools 协议精细控制输出第三条才是真正把打印预览对话框调出来、模拟人工选打印机、最后另存为 PDF。三条路各有各的适用场景也各有各的坑。这篇文章不搞虚的把原理、完整代码、参数说明和排查经验一次讲透适合正在做网页自动化存档、报表导出、页面快照定时归档这类需求的同学参考。1. 从手动另存 PDF到全自动打印先把需求场景说清楚很多人看到Python Selenium 调用网页打印机另存 PDF这个标题下意识觉得这是个小众技巧。实际上这类需求的业务场景比你想象的普遍得多报表归档后台系统每天生成一堆数据报表页面要求保存成 PDF 留档供后续审计或追溯。页面快照一些活动页面、公告页面随时可能下架运营要求在下架之前把所有版本快照成 PDF。单据凭证保存订单详情、结算单、对账单这类页面需要导出 PDF 发给客户或走内部审批流程。定时任务凌晨自动跑一遍把昨天新增的所有页面按日期命名归档到指定目录。这些场景有一个共同点页面是用 JavaScript 动态渲染的直接拿 requests 去抓 HTML 根本没用拿到手的是一堆空壳标签。就算你强行把 HTML 存下来排版也和浏览器里看到的天差地别。有人可能会说那我用截图不行吗行但截图出来的是一张张长图不可检索、不可复制文字、也不能按打印样式自动分页。PDF 才是归档场景下的标准格式因为它自带文本层、支持分页、还能通过打印样式表控制排版。网页打印机这个词其实不是某个硬件的名字它指的是浏览器打印功能里那些能把页面打印成文件的虚拟打印机。Windows 上最典型的就是系统自带的Microsoft Print to PDFChrome 里还有一个内置的另存为 PDF目标打印机。所谓调用网页打印机另存 PDF本质就是让浏览器走一遍打印流程但把输出目标指向文件而不是真的纸张。明白了这个本质你就知道为什么这事儿必须用 Selenium 来做了因为这类页面往往需要登录态、需要先选择一些查询条件、需要等图表渲染完成这些页面操作 Selenium 最擅长。打印只是整个流程的最后一个动作。2. 网页打印的底层逻辑为什么 Selenium 默认拿打印按钮没办法在写代码之前必须先把一个关键认知掰扯清楚。很多人以为 Selenium 能像操作普通网页元素那样去点击浏览器打印对话框里的按钮比如打印、取消、另存为 PDF这些。这是个高频误区。2.1 打印对话框根本不在 DOM 树里按 CtrlP 弹出的那个打印预览窗口在 Chrome 里叫 Print Preview它是浏览器内部的原生界面不是网页 DOM 的一部分。Selenium WebDriver 的工作方式是通过 W3C WebDriver 协议操作的是当前页面的 DOM 树。所以你在 Selenium 里用find_element是永远不可能定位到打印对话框里的任何按钮的协议层面就不支持。这就引出了一个核心结论要把网页内容输出成 PDF要么绕开打印对话框直接让浏览器内核去渲染 PDF要么借助 Selenium 之外的系统自动化工具去操作那个原生对话框。围绕这个结论所有技术方案分成了三条路。2.2 三条能落地的技术路径对比我把三条路径放在一个表里先给你看个全景后面再逐个拆解技术路径实现方式是否出现打印对话框可控参数最适用的场景Selenium 4 官方 print_page()WebDriver 协议内置打印端点否缩放、纸张、边距、背景、页码范围等基础项常规页面快速导出代码最简单CDP Page.printToPDF通过 execute_cdp_cmd 调 Chrome DevTools 协议否几乎全部参数含页眉页脚模板、CSS 页面尺寸需要精细控制排版比如页眉页脚、A4 精准分页真实打印对话框 pyautoguiSelenium 触发打印 系统级键盘鼠标模拟是完全等同人工操作必须走真实打印流程、依赖打印预览场景另外还有一个常被提的偏门方案Chrome 启动参数--print-to-pdf。它可以在无头模式下直接把页面存成 PDF不需要写 Selenium 代码。但它的缺点也很致命无法动态控制登录态、无法在打印前操作页面元素、文件名固定在一个路径上。所以遇到稍微复杂的业务页面这个方案基本用不了后面我也不展开讲重点放在上面三条路上。选哪条路取决于一个核心问题你到底需不需要那个打印对话框。如果不需要print_page() 和 CDP 是首选稳定且速度快如果需要真实弹框比如页面里有打印相关的 JavaScript 逻辑或者你就是想演示给业务方看那才轮到 pyautogui 上场。3. 路径一Selenium 4 原生 print_page()无对话框直接出 PDF这是我最推荐新手先试的一条路。Selenium 4 开始WebDriver 协议增加了一个打印端点Chrome 和 Firefox 都支持。调用driver.print_page()后浏览器会直接把当前页面按打印样式渲染成 PDF返回一段 base64 字符串你只需要解码写文件。3.1 最小可用代码from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.print_page_options import PrintOptions import base64 options Options() driver webdriver.Chrome(optionsoptions) driver.get(https://example.com/report-page) # 配置打印参数 print_options PrintOptions() print_options.orientation portrait # 纵向打印 print_options.scale 1.0 # 缩放比例1.0 为原始大小 print_options.background True # 打印背景色和背景图片 print_options.page_width 21.0 # 页面宽度单位是厘米 print_options.page_height 29.7 # 页面高度对应 A4 纵向 print_options.margin_top 1.0 # 上边距厘米 print_options.margin_bottom 1.0 # 下边距 print_options.margin_left 1.0 # 左边距 print_options.margin_right 1.0 # 右边距 print_options.page_ranges [1-3] # 只打印第 1 到第 3 页 print_options.shrink_to_fit True # 内容过宽时是否自动缩小适配 # 执行打印返回 base64 编码的 PDF pdf_base64 driver.print_page(print_options) with open(report.pdf, wb) as f: f.write(base64.b64decode(pdf_base64)) driver.quit()这段代码跑完report.pdf就在当前目录下了。整个过程没有任何对话框弹出特别适合放在服务器上做无人值守的定时任务。3.2 PrintOptions 参数逐个说有些参数看着简单实际容易踩坑我挑重点讲page_width / page_height单位是厘米。很多从 CDP 方案转过来的人容易在这里栽跟头因为 CDP 的参数单位是英寸。A4 纸在厘米下是 21 x 29.7在英寸下是 8.27 x 11.69。background默认是 False。如果你发现导出的 PDF 里表格的行间隔色、状态标签的底色全没了那就是这个参数没开。CSS 里写的-webkit-print-color-adjust: exact也不会影响这个开关必须在浏览器层面打开。shrink_to_fit默认 True。如果页面内容比纸张宽浏览器会自动缩小内容让它在纸面内完整显示。但有些场景你不希望缩放比如设计稿类的页面想 1:1 输出就把它设成 False同时把 paperWidth 设大一些。page_ranges这里传的是数组比如[1-2, 4]。注意这个参数在不同浏览器里的行为有点差异Chrome 下表现稳定Firefox 下偶尔出现范围不生效的情况遇到就升级浏览器版本。3.3 这套 API 的边界print_page() 最大的问题是参数不够细。它没有暴露页眉页脚模板的控制项也就是说你没法给 PDF 加页码也没法自定义页眉文字。Chrome 的默认行为是不打印页眉页脚所以如果你不在意页码这条路径完全够用。另外它也没法处理page的 CSS 规则。有些页面的 CSS 里用page { size: A4; margin: 2cm; }定义了纸张规格print_page() 会忽略它直接用 PrintOptions 里设的纸张尺寸。这个差异在看设计还原度要求高的页面时会很头疼。还有一个版本问题Selenium 3 没有print_page()必须是 Selenium 4.x并且浏览器驱动版本要配套。装好之后可以用driver.print_page()直接试如果报WebDriverException先检查 Selenium 版本和浏览器驱动版本。4. 路径二通过 CDP 调 Page.printToPDF精细到每一毫米当 print_page() 满足不了你的需求时就该上 CDP 了。CDP 全称 Chrome DevTools Protocol是 Chrome 给开发者工具提供的一套调试协议。Selenium 里可以用driver.execute_cdp_cmd()直接发协议指令绕过 WebDriver 的封装拿到浏览器更底层的能力。Page.printToPDF就是这套协议里控制打印的命令也是 print_page() 在 Chrome 底层的真实实现。4.1 为什么还需要 CDP直接调用 CDP 带来的好处是参数控制级别完全不同。比如可以自定义页眉页脚模板往页脚塞页码、往页眉塞标题。可以设置preferCSSPageSize让浏览器优先遵循页面 CSS 里定义的page尺寸。可以精确控制纸张宽高和边距单位是英寸精度比 print_page 的厘米更细。可以将 PDF 以 base64 或流式两种方式返回方便处理超大 PDF。说白了print_page() 是官方给的快捷方式CDP 是可以直接操作发动机。碰到 print_page 搞不定的事直接看 CDP 的参数列表通常都能找到答案。4.2 参数清单与代码实现from selenium import webdriver import base64 driver webdriver.Chrome() driver.get(https://example.com/report-page) params { landscape: False, # 是否横向 displayHeaderFooter: True, # 是否显示页眉页脚 headerTemplate: span/span, # 页眉模板这里置空去掉默认标题 footerTemplate: div stylefont-size:8px; width:100%; text-align:center;span classpageNumber/span / span classtotalPages/span/div, printBackground: True, # 打印背景 preferCSSPageSize: True, # 优先使用 CSS page 尺寸 paperWidth: 8.27, # 纸张宽度单位是英寸 paperHeight: 11.69, # 纸张高度 marginTop: 0.4, # 上边距英寸 marginBottom: 0.4, marginLeft: 0.4, marginRight: 0.4, scale: 1.0, # 缩放比例 pageRanges: , # 空字符串表示全部页面 transferMode: ReturnAsBase64 # 返回方式base64 或流 } result driver.execute_cdp_cmd(Page.printToPDF, params) pdf_bytes base64.b64decode(result[data]) with open(report_cdp.pdf, wb) as f: f.write(pdf_bytes) driver.quit()4.3 几个关键参数的实际经验页眉页脚模板这块最值得讲。如果你把displayHeaderFooter设为 True但不传模板Chrome 默认会在页眉显示页面标题在页脚显示日期、网址和页码丑到没法看。我的习惯是要么干脆把displayHeaderFooter设成 False要么用空模板span/span占位。模板里允许插入简单的 HTML 和几个内置 classpageNumber是当前页码totalPages是总页数title是页面标题url是当前地址。你可以在页脚模板里拼一个第 X 页 / 共 Y 页的居中样式就像文档打印那样。preferCSSPageSize 这个参数容易被忽略但非常关键。有些报表页面的 CSS 里写了page { size: A4 landscape; margin: 10mm; }这种情况下如果你不打开preferCSSPageSize浏览器会无视这个规则按你传的paperWidth/paperHeight出纸结果就是页面被压缩或拉伸比例不对。打开这个参数后浏览器会以 CSS 定义为准你传的纸张尺寸反而变成兜底。实际项目里我通常先开preferCSSPageSize跑一版看效果如果页面本身没有写page再关掉它手动指定 A4。单位换算要反复确认。CDP 里所有长度参数的单位都是英寸不是厘米也不是像素。A4 纸纵向是 8.27 x 11.69 英寸横向是 11.69 x 8.27。边距 0.4 英寸大约等于 1 厘米。我吃过一次亏把 21 当成纸宽直接传进去出来的 PDF 边缘大量留白。写代码前先算好或者像我一样在注释里标清楚换算结果。4.4 两种无对话框方案怎么选print_page() 和 CDP 都能在不弹对话框的前提下输出 PDF我的选择标准是这样页面没有特殊 CSS 分页需求、也不需要页码时优先 print_page()代码短浏览器跨版本兼容性也更好。需要页码、需要遵循page尺寸、需要打印多份不同页码范围、或者输出结果对排版精度要求很高时直接上 CDP。如果要在 Safari、Firefox 等多浏览器环境跑同一套脚本print_page() 的兼容性更好CDP 虽然在 Firefox 里也有部分实现但参数支持度和 Chrome 有差异。一句话日常导出用 print_page()追求排版控制力用 CDP。这两条都是不动打印机的方案下面这条才是真正把打印对话框调出来的做法。5. 路径三真实调起打印对话框模拟人工完成另存为 PDF说完了不动对话框的方案现在来看看标题字面意思对应的做法把打印对话框真正调出来选打印机然后另存 PDF。这套流程里Selenium 负责调起和页面准备真正操作对话框的是 pyautogui 这个系统级自动化库。5.1 完整思路流程分四步用 Selenium 打开目标页面完成所有前置操作登录、选条件、等图表加载。触发打印快捷键 CtrlP或者执行window.print()让浏览器弹出打印预览。用 pyautogui 操作打印对话框聚焦目标打印机下拉框输入Microsoft Print to PDF回车确认。点击打印按钮后Windows 会弹出保存打印输出为对话框输入文件路径和文件名回车保存。这里注意一点Chrome 自带的另存为 PDF目标打印机走的是浏览器下载逻辑不会弹 Windows 的保存框而是按下载设置直接存到下载目录。如果你要精确控制保存路径和文件名最好选择Microsoft Print to PDF这个 Windows 系统虚拟打印机它一定会弹系统保存对话框命名和路径都由你掌控。5.2 Windows 下的完整代码import time import pyautogui from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys driver webdriver.Chrome() driver.get(https://example.com/report-page) # 触发打印对话框 body driver.find_element(By.TAG_NAME, body) body.send_keys(Keys.CONTROL, p) # 等待打印预览渲染完成页面复杂时适当加大 time.sleep(3) # Chrome 打印预览里聚焦目标打印机下拉框 # 不同版本快捷键有差异下面是最常见的组合 pyautogui.hotkey(ctrl, shift, p) time.sleep(1) # 在打印机下拉框里输入目标打印机名称 pyautogui.typewrite(Microsoft Print to PDF, interval0.05) time.sleep(0.5) pyautogui.press(enter) time.sleep(1) # 按下回车触发打印按钮 pyautogui.press(enter) time.sleep(2) # 等待系统保存打印输出为对话框弹出 # 输入完整路径 文件名 pyautogui.typewrite(rD:\pdf_archive\report_20250101.pdf, interval0.02) time.sleep(0.5) pyautogui.press(enter) # 如果文件已存在Windows 会提示是否替换再按一次回车确认 time.sleep(1) pyautogui.press(enter) driver.quit()5.3 不同系统、不同 Chrome 版本下的差异这条路径最大的敌人是环境差异。Windows Chrome 新版CtrlShiftP 聚焦目标打印机下拉框这个快捷键在 Chrome 120 以上基本还能用但 Chrome 团队一直在改打印预览的 UI不保证永远有效。Windows Chrome 旧版旧版打印预览里CtrlShiftP 的行为可能不变也可能变成别的功能。如果发现按了没反应可以改用 Tab 键在对话框里遍历焦点把焦点移到打印机下拉框再输入。macOSChrome 的打印对话框在 macOS 下不是 Chrome 自己绘制的而是调用系统打印面板按键逻辑完全不同。macOS 下更稳定的做法是用pyautogui.locateOnScreen()根据按钮图片定位点击或者干脆放弃这条路径回到 CDP。Linux取决于桌面环境和 Chrome 版本打印对话框的行为千奇百怪。我在 Ubuntu GNOME 下试过一次最后直接用 X11 的坐标点击才搞定维护成本太高不建议作为长期方案。如果快捷键不可靠可以退一步用图像识别替代键盘导航。先把打印预览对话框截屏用pyautogui.locateOnScreen(printer_dropdown.png)找到下拉框位置点击再用typewrite输入打印机名。缺点是屏幕缩放比例DPI不同时图像匹配会失准需要针对每台机器单独标定。我的经验是能在 Windows 上用键盘方案就别用图像方案维护成本低一个量级。5.4 pyautogui 操作的危险区与兜底策略用 pyautogui 操作真实对话框本质是模拟人在用键盘鼠标这意味着脚本运行期间你不能碰这台机器的鼠标键盘。一旦你手滑动了鼠标点击位置全乱对话框可能被关掉脚本就卡死了。几个必须做好的防护开启 failsafe 紧急停止。pyautogui 自带一个保护机制把鼠标快速甩到屏幕左上角会抛出一个pyautogui.FailSafeException中断脚本。默认是开启的千万别关。所有 sleep 要给足而不是给刚刚好。打印预览渲染、系统对话框弹出这些动作在不同机器上快慢差异很大。我自己跑批处理时每个阶段之间至少留 2 秒。宁可慢一点也不要因为某个环节超时导致整个流程错位。先激活窗口再操作。打印预览有时不会自动成为前台窗口尤其当你从脚本里远程触发时。操作前可以先用 pygetwindow 找到并激活 Chrome 窗口import pygetwindow as gw print_windows gw.getWindowsWithTitle(report-page) if print_windows: print_windows[0].activate() time.sleep(0.5)批处理时给每个任务套 try/except。批量导出几十个页面时任何一个页面卡住都会拖垮整个队列。我会在循环里捕获异常记录日志后继续下一个等全部跑完再单独处理失败的页面。这条路径的优点是所见即所得业务方看到你确实调起了打印预览会觉得这事很真实。缺点是依赖系统环境稳定性比前两条差。我的定位是能不用就不用除非需求方明确要求看到打印预览过程。6. 打印前页面状态准备自定义下拉框、懒加载与等待策略不管走哪条路径有一个步骤是绕不开的打印之前你得保证页面处于正确的状态。很多报表页面都有查询条件需要你选好日期、选好部门页面才刷新出你要打印的内容。而现在的网页组件尤其是中后台管理系统里大量使用div ul li组合的自定义下拉框根本不是原生select元素。6.1 处理 div/ul/li 组合的非原生下拉框原生select元素用 Selenium 的Select类几下就能搞定但自定义下拉框没这么简单。这类组件的 DOM 结构通常是一个看起来像输入框的div作为触发器点击后弹出一个ul列表里面每个选项是一个li。它没有原生的select/option语义只能靠真实的鼠标点击事件驱动。from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def select_from_custom_dropdown(driver, trigger_selector: str, option_text: str): 处理 div/ul/li 组合的自定义下拉框 # 点击触发器展开下拉列表 trigger WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, trigger_selector)) ) trigger.click() # 等待目标选项出现并点击 option_locator (By.XPATH, f//li[contains(text(), {option_text})]) option WebDriverWait(driver, 10).until( EC.element_to_be_clickable(option_locator) ) option.click() # 等待下拉列表收起、遮罩层消失 WebDriverWait(driver, 10).until( EC.invisibility_of_element_located((By.CSS_SELECTOR, .dropdown-mask)) )这里有几个细节值得注意。第一EC.element_to_be_clickable比presence_of_element_located更可靠因为它会额外检查元素是否可见、是否可点击能过滤掉元素在 DOM 里但被遮罩盖住的情况。第二点击选项之后组件通常会有一个收起动画和一层遮罩如果不等待遮罩消失就直接触发打印打印出来的内容里可能会带上灰色的半透明遮罩层。如果你要处理的页面很多、每个页面的下拉框定位方式都不一样我建议把定位器整理成配置数据而不是散落在代码里。用一个字典或者 YAML 文件专门存这些元素的定位策略和选择值脚本运行时按页面标识去取这样更换页面布局时只需要改配置不用动 Python 代码。这个方法在自动化圈子里叫仅存储定位元数据本质上就是把定位和逻辑分离批量项目里能省下大量维护时间。6.2 等待页面真稳定别只靠 time.sleep选好查询条件后页面一般会发请求刷新数据。这时候很多新手喜欢time.sleep(5)硬等但网络波动、接口慢、图表渲染慢都会让固定等待时间失效。等了 5 秒数据没出来打印出来的就是个残缺页面。更稳妥的做法是主动等待关键元素出现。比如这个报表页有一个标题元素数据加载完成后它的文案会变化你可以显式等待这个文案出现WebDriverWait(driver, 30).until( EC.text_to_be_present_in_element( (By.CSS_SELECTOR, .report-title), 2025年1月报表 ) )如果页面有懒加载机制图片和表格是在滚动过程中才逐步渲染的那在等待之外还要手动触发一次滚动driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(1) driver.execute_script(window.scrollTo(0, 0);) time.sleep(1)这个操作的原理是懒加载通常监听滚动事件把页面滚到底部再滚回顶部能触发一整轮加载流程把窗口可视区域外的内容也逼出来。等滚动完成后再检查document.readyState是否为complete以及网络请求是否基本结束这样才敢触发打印动作。7. 我把踩过的坑整理成了一张排查表这三条路径我都跑过生产级批处理任务踩过的坑不少。这里不按路径分按现象分方便你对照排查。现象常见根因处理办法PDF 内容空白或缺少后半部分页面懒加载未完成图表和数据没渲染出来先滚动触发懒加载再等待关键元素和document.readyState完成背景色、隔行色、状态标签底色消失打印背景开关未开启print_page 里设backgroundTrueCDP 里设printBackgroundTrue打印结果被截断或一页被拆成两页没遵循页面 CSS 的page尺寸或纸张太窄、scale 不合适CDP 里开preferCSSPageSize确认纸张宽高与页面设计匹配页眉页脚出现标题、网址、日期displayHeaderFooter默认开启且使用默认模板把页眉页脚模板置空或直接关闭该选项PDF 末尾多出一张空白页页面 body 有隐藏溢出元素滚动条宽度干扰了分页计算检查 body 的overflow设置打印前设置合适的 viewport 尺寸打印对话框没弹出页面 JS 拦截了 window.print或浏览器窗口未聚焦改用真实按键 CtrlP先激活 Chrome 窗口再触发没有弹系统保存框选了 Chrome 自带的另存为 PDF走了下载逻辑改用 Microsoft Print to PDF或在下载设置里开启下载前询问保存位置输入的文件名中文乱码输入法处于中文状态typewrite 输出异常切换英文输入法或用 pyperclip 复制路径后 CtrlV 粘贴pyautogui 点击位置偏移Windows 显示缩放DPI导致图像识别坐标不准优先用键盘操作避免坐标依赖图像方案需按机器标定CDP 返回的 PDF 打不开transferMode 设成了 ReturnAsStream 但没正确处理流直接用 ReturnAsBase64解析返回的 data 字段下面重点展开三个最常见的坑把排查过程写出来比单纯给结论更有用。坑一PDF 是空的或者只有第一页有内容。我第一次跑批处理时就遇到这个。当时以为是打印参数的问题来回调 CDP 参数浪费了一下午。后来把中间产物输出到日志里才发现是打印动作触发得太早页面里的图表用的是 setTimeout 延迟渲染document.readyState早就 complete 了但图表画布还是空的。解决方式是先判断图表容器的子节点数量等它从 0 变成大于 0再进行滚动和打印。这个经验很重要页面说加载完成不等于内容渲染完成尤其是图表类的 canvas 渲染一定要等实际的业务元素出现。坑二打印结果比预期多了一页最后一页几乎全白。这个坑的根源是滚动条。Windows 下 Chrome 的滚动条默认占十几像素的宽度你在页面里看到的内容宽度和打印布局计算时用的内容宽度并不完全一致。当页面内容高度刚好卡在分页边界附近时滚动条宽度引起的换行差异会把内容挤到下一页产生一页几乎空白的纸。我当时的解决方式是打印前用 CDP 把浏览器的 viewport 宽度设大一些给内容留足余量或者把页面 body 的overflow-y临时设为hidden打印完再恢复。坑三文件名重复导致保存对话框一直问是否替换。批处理场景下归档文件名如果设计得不够精细很容易撞名。Windows 的保存对话框一旦弹出替换确认脚本里没处理这一步整个队列就会卡住。我的做法是在文件名里带上精确到秒的时间戳比如report_20250101_023015.pdf从源头避免重名。如果业务上要求固定文件名那就在 pyautogui 流程里多预留一次回车专门处理替换确认框。还有一点关于 CDP 文件返回方式的提醒transferMode有两个取值ReturnAsBase64和ReturnAsStream。后者返回的是result[stream]你需要再通过IO.read这个协议命令分段读取处理起来麻烦很多。除非你的 PDF 大到 base64 编码后内存扛不住否则一律用ReturnAsBase64简单可靠。最后再分享一条生产环境的经验。批处理几十上百个页面时我会在循环里记录每一页的打印结果和耗时并且每处理二十个页面就重启一次浏览器实例。长时间运行的 Chrome 进程内存占用会越来越大页面开太多还会出现偶发的渲染异常定期重启是最省事的规避手段。另外所有临时 PDF 先输出到一个临时目录全部成功后再统一改名迁移到归档目录这样即使中途有页面失败也不会污染已归档的文件。如果你只是自己处理一两个页面print_page() 五分钟就能搞定如果你要跑每天几十页的定时任务认真看一下 CDP 的参数控制只有当你真的需要那个打印预览弹出来的时候再动用 pyautogui。这就是我在这个需求上最终沉淀下来的选择逻辑。