
前阵子整理家里的备考资料翻出一大摞扫描成PDF的A3版试卷。想把这些卷子打印到普通A4纸上练手随手用阅读器的“适合页面”缩放打印结果打出来的字偏小公式的上下标糊成一团做批注时更是无从下笔。折腾一圈之后我干脆给自己写了个A3试卷分割工具免费、完全离线把A3页面沿中线切成两个A4页再统一导出成PDF或直接打印。这篇就把整个思路、脚本和踩过的坑都写清楚对经常处理试卷、教案、宽幅截图和双栏文献的人应该都能直接抄作业。我要先说清楚这里做的事本质上是“版面分割”不是把文件缩小。A3纸的尺寸是420mm×297mmA4是210mm×297mm两者宽度正好是两倍关系。所以一张A3扫描件只要从正中间纵向切一刀就能得到两个尺寸完全标准的A4页面。这样字迹大小保持不变打印效果几乎等同于原卷线下背单词、错题标注、打印给小朋友做练习都舒服得多。1. 为什么不能直接“适合页面”缩小A3转A4必须解决的隐藏逻辑1.1 A3和A4的尺寸关系并非简单等比例缩小很多人会想A3打印到A4不是直接用打印机的“缩放”功能就行吗能缩放但不是好方案。A3的长边是420mmA4的长边是297mmA3的短边是297mmA4的短边是210mm。你会发现A4实际上是A3沿长边对半切开的结果而不是A3等比缩放后的产物。如果硬要“适合页面”缩放比例会被强制压到约70.7%也就是1除以根号2。整个版面的文字、图表全部按这个比例缩小结果就是正文字号从常见的小四或五号变成更小的“绿豆”大小数学公式里的分式、根号、上下标基本没法看英语试卷里的完形填空选项容易连在一起打印出来之后再扫描或拍照二次清晰度损伤非常明显。所以最正确的转换逻辑不是“缩放”而是“裁切”沿中线把A3分成两个A4每个A4保持100%原始尺寸。1.2 扫描试卷文件的典型形态和需要处理的细节试卷扫描件通常有两种来源一种是学校或机构直接用扫描仪扫成PDF分辨率一般在300dpi左右这类文件干净、平整页边距均匀另一种是手机拍照或从网上下载的图片打包而成的PDF这类文件往往存在歪斜、亮度不均、左右页面内容偏移等问题。我自己遇到的试卷来源以第一种居多但也不是没遇到过第二种。处理时必须先认清一个前提扫描件里的一页A3并不一定等于两个完美对称的A4。常见偏差有扫描时纸张没有放正左右两页的内容中线偏移原卷本身左右两栏宽度不一致比如左侧题目多、右侧留白多A3原卷在印刷时没有严格居中左右页边距本身就不同有装订孔或装订边切出来的右页可能带有一条黑色装订阴影。这些情况如果不处理直接机械地“一刀切”切出来的A4页边距会很难看甚至把题目切掉一小部分。所以真正稳定的工具除了提供基础的中线切割还应该允许用户调整切割位置或者在检测到内容偏移时提醒一下。这个后面在脚本部分会专门展开。2. 免费离线方案如何选一次排查后我会配置哪些工具2.1 为什么我最后选了Python加PyMuPDF而不是各种在线转换器面对“A3转A4”这个需求可选的方案其实不少。在线PDF转换网站、手机App、桌面软件都有相应功能。但我是个比较在意效率和隐私的人在线工具用起来总有几个绕不开的痛点文件上传到别人服务器卷子内容可能涉及个人隐私或考试信息几十页的扫描件上传、转换、下载来回花时间网络差一点直接卡死多数在线工具免费版有页数限制超过就要开会员输出清晰度不可控有些网站为了压缩存储会把PDF重新采样成低分辨率图片打印出来明显发虚。离线桌面软件里Adobe Acrobat的“裁剪页面”功能确实能实现类似效果但正版价格不便宜未必人人都有。LibreOffice Draw也能做页面裁剪不过对于动辄几十页的PDF操作那叫一个繁琐做多了心态容易崩。权衡之后我选择了“Python脚本PyMuPDF”这套组合。原因很简单完全离线不依赖任何外部服务免费开源只看本地文件不吃在线流量批处理能力极强一次处理整个文件夹没问题PDF里的文字和矢量图形可以无损保留打印清晰度和原文件一致。另外可选的是ImageMagick和Ghostscript组合适合偏图片型的PDF。但它们在处理矢量文字边缘时有时候会栅格化不如PyMuPDF优雅。所以最终方案是PDF文件走PyMuPDF纯图片文件走Pillow两边都能覆盖。2.2 在完全没有网络的电脑上准备Python依赖包“离线”不只是说脚本运行时不联网还包括安装环境这件事也可以离线完成。很多人一听Python就头疼其实用对方法装环境一点也不难。假设你手上有一台从来没有装过任何Python包的电脑甚至这台电脑不能联网。你可以先在能联网的电脑上做一次“搬运”把依赖包下载成whl文件再拷贝过去。具体流程是这样在联网电脑上新建一个目录比如wheelhouse然后执行mkdir wheelhouse pip download PyMuPDF Pillow img2pdf --platform win_amd64 --python-version 39 --dest wheelhouse注意--platform和--python-version要根据目标电脑的操作系统和Python版本调整。如果你不确定最简单的方式是直接在联网电脑上建一个和目标电脑相同Python版本的虚拟环境然后pip download PyMuPDF Pillow -d wheelhouse把整个wheelhouse文件夹用U盘拷贝到离线电脑然后执行pip install --no-index --find-linkswheelhouse PyMuPDF Pillow这就完成了离线安装。如果确实不想装Python还有一个备选路线用PyInstaller把脚本打包成exe在联网电脑上打包好后直接拷贝到任意Windows电脑运行。不过打包后的exe体积会比较大动辄几十MB新手如果只是处理自己的卷子装Python反而更轻量。2.3 手机App和桌面工具的补充测试做这个工具之前我也试过几款手机App。有些扫描App自带“A3转A4”功能操作也简单但对老试卷的识别并不总理想。手机App主要问题是免费版有广告和页数限制处理高分辨率扫描件时压缩明显页面顺序调整和批量操作不灵活。如果只是偶尔处理一两张手机App确实够用但如果是整理一整本历年真题我强烈建议走脚本方案。一次跑完输出文件和原PDF保持同一份目录结构一目了然。3. 实际脚本与步骤A3 PDF切分为A4并保留清晰度3.1 脚本前的准备我的脚本思路很简单遍历PDF每一页取页面宽度的一半作为切割线把左侧区域和右侧区域分别输出为新PDF的独立页面。在动手之前先把需要处理的文件整理好。我的习惯是建一个input文件夹把所有的A3版PDF放进去再建一个output文件夹输出的A4文档统一放这里。文件命名最好带原始名称加页码后缀方便追踪。比如2025模考数学A3.pdf输出后生成2025模考数学A3_part1.pdf这样的文件或者全部合并成一个2025模考数学A4.pdf。我用后者更普遍因为合并成一个文件打印起来更方便。3.2 核心脚本PyMuPDF切割PDF下面这个脚本是最基础的版本处理标准A3扫描PDF切成两个A4页面并合并到一个输出文件里import fitz import os input_path input/2025模考数学A3.pdf output_path output/2025模考数学A4.pdf src fitz.open(input_path) out fitz.open() for page in src: # 获取原页面尺寸单位是PDF点 rect page.rect width rect.width height rect.height # 中线位置 half width / 2 # 左半部分和右半部分对应的裁剪区域 left_rect fitz.Rect(0, 0, half, height) right_rect fitz.Rect(half, 0, width, height) # 创建A4大小的新页面 a4_rect fitz.Rect(0, 0, 595, 842) left_page out.new_page(width595, height842) left_page.show_pdf_page(a4_rect, src, page.number, clipleft_rect) right_page out.new_page(width595, height842) right_page.show_pdf_page(a4_rect, src, page.number, clipright_rect) out.save(output_path) src.close() out.close() print(完成输出文件, output_path)这段代码有几个关键点需要说明。show_pdf_page是PyMuPDF提供的方法它可以把指定来源PDF的某个区域“展示”到当前页面的指定位置。这里的clip参数就是切割区域a4_rect是目标区域。因为A4页面尺寸是595×842点正好对应210mm×297mm而原A3页面宽度是1190点左右对半切后宽度恰好是595点。所以切割后的内容能正好铺满整个A4页面不会有缩放发生。这一点特别重要意味着输出PDF和原卷的字体大小完全一致。如果你的原始A3页面尺寸不是标准值比如有些扫描件四周有白边那你需要先判断白边的位置。常见做法是通过page.get_text(blocks)先取出所有文字区块然后计算这些区块的整体边界框再按这个边界框的中线切割。这样就不会把大块白边切进A4页面里浪费打印空间。3.3 图片版本输入JPEG/PNG合并输出PDF有时候我们没有原始PDF只有一堆A3扫描图片。这种情况下可以用Pillow先把图片切好再用img2pdf合并成一个PDF文件。脚本如下from PIL import Image import img2pdf import os input_images [scan01.jpg, scan02.jpg, scan03.jpg] output_list [] for idx, img_file in enumerate(input_images): im Image.open(img_file) w, h im.size # 如果图片尺寸是奇数中线像素要小心处理 left_box (0, 0, w // 2, h) right_box (w // 2, 0, w, h) left_img im.crop(left_box) right_img im.crop(right_box) left_path ftemp_left_{idx}.jpg right_path ftemp_right_{idx}.jpg left_img.save(left_path, quality95) right_img.save(right_path, quality95) output_list.append(left_path) output_list.append(right_path) with open(output_images.pdf, wb) as f: f.write(img2pdf.convert(output_list)) print(图片合并完成生成 output_images.pdf)这里有个容易忽略的细节当图像宽度是奇数时w // 2会使左半部分少一列像素。如果扫描图有4961像素宽简单整除会不均匀导致左侧比右侧少1像素。这个差异肉眼几乎看不见但强迫症选手可以这样写left_box (0, 0, w // 2 1, h) right_box (w // 2 1, 0, w, h)把中间分割处的那一列像素划给左侧左右两侧的面积差不多。当然前提是中线本身没有落在重要内容上。img2pdf合并图片时还有一个好处它不重新编码图片只是把JPEG数据包进PDF容器里所以图片质量和原图一致文件体积也不会膨胀。比用Pillow直接save成PDF要高效得多。3.4 输出验证与清晰度检查处理后不要急着打印先花一分钟做输出验证。我的检查步骤固定是打开输出PDF随便翻几页看左右两页是否完整用PDF阅读器把页面缩放显示到100%看文字边缘是否锐利检查页数是否等于原A3页数的两倍打印一页测试页确认实际尺寸是否为A4标准。如果发现输出页面上出现了明显的白边说明原PDF的裁剪区域计算需要调整如果发现个别页被切掉内容多半是原卷内容偏移导致。这种问题我会放到后面第五部分专门处理。3.5 如果你不需要PDF只想直接打印A4怎么办脚本输出PDF之后直接交给打印机即可。但有些朋友可能不大习惯在电脑上操作PDF更希望直接通过打印机驱动完成。其实也可以在打印对话框里设置“海报打印”或“多页打印”但那些方案大多依赖打印机驱动对页面缩放的处理逻辑效果并不稳定。我更推荐先输出PDF再做打印这样你还能在打印前预览、加水印、插空白页。如果家里打印机支持双面打印输出PDF后同样可以直接调用打印机的双面选项不需要额外处理。4. 打印、装订与双面策略从电子裁切到纸张落地4.1 双面打印时页序控制的实用经验试卷分割成A4后最常见的使用场景是打印出来做题或装订成册。这里有个容易踩的坑双面打印。假设原A3卷有10页切出来是20个A4页从左到右的顺序是“第1页左、第1页右、第2页左、第2页右……”。如果你直接按这个顺序双面打印会出现的情况是第一张纸正面是第1页左反面是第1页右。把纸翻过来从背面看第1页右会位于左侧第1页左位于右侧。如果按试卷的阅读顺序这种排列会打乱左右页的对应关系特别是数学试卷中左页和右页经常是一个题目的两个部分或者是一道大题配一张图表。我的经验是打印前先单独挑出奇数页和偶数页。如果你坚持要按物理顺序排列最简单的办法是把左半页全部放在前面右半页全部放在后面先打印所有左半页再打印所有右半页然后用订书机按顺序合并。这样双面打印时纸张正反面顺序才不会乱。在PDF阅读器里可以用打印范围功能分两次打印第一次打印第1、3、5……页第二次打印第2、4、6……页。虽然多了一步操作但可以避免纸张正面和背面出现错位。如果你不在意左右页面的严格对应只是做题那直接顺序打印也行不用纠结。4.2 装订边距和装订线的特别处理办公用试卷打印通常不需要装订但如果想打印成小册子或者原卷本身靠近装订线一侧有内容就要考虑边距问题。A3原卷如果是从书本上扫描的靠近书脊的部分经常会有文字被阴影遮住或弯曲。分割之后靠近中缝的那个边缘右页的左侧、左页的右侧可能会有少量内容因扫描弯度变黑。此时可以在分割前先对扫描件做一次去黑边处理或者在切割时主动把中缝区域向内偏移几毫米宁可牺牲一点点边距也不要让阴影区域印出来黑乎乎一片。具体的偏移做法在PyMuPDF里不用half width / 2而是自定义一个偏移量。比如希望中缝向右移动5个点让左页多包含一点左半内容、右页少包含一点右半内容offset 5 left_rect fitz.Rect(0, 0, half offset, height) right_rect fitz.Rect(half offset, 0, width, height)这个offset不是随意设置的你需要先通过取几个特征页查看中缝阴影的宽度再决定偏移多少。我一般先处理一页导出后用图片查看器放大中缝区域量出阴影宽度再批量应用。4.3 想保留题号排版时的另类解法如果原试卷是那种左右分栏的排版比如“左边题目1-10右边题目11-20”那按中线切割的效果已经很接近原始卷面。但有一种特殊情况原卷印刷时因为页面宽度的原因把一道题拆成了两栏左栏末尾是题面右栏开头是选项或下一小问。这种题如果硬切会把同一道题的完整信息拆到两个A4页面上做题时来回翻页很不方便。针对这种情况我这个工具还有一条“保守模式”切割前先把整页内容转成高分辨率图片通过简单的图像灰度检测找到左右两栏内容之间的空白带如果空白带很宽说明两栏之间本身留出了格线就正常切如果两栏之间几乎没有空白带说明内容连续则把这一页整体保留为A4缩放版不切。这个检测逻辑在脚本里实现起来也不复杂核心就是纵向投影import numpy as np from PIL import Image img Image.open(page.jpg).convert(L) arr np.array(img) # 计算每一列像素的平均灰度 col_mean arr.mean(axis0) # 找出灰度值较高的空白区域判断是否存在明显分隔带但这套检测对纯黑白扫描件效果会变差因为黑白扫描件的背景灰度是255文字灰度接近0分隔带的判断更依赖像素变化。如果你遇到的是这种复杂试卷我建议先跳过自动切割手动查看这一页再单独处理。5. 那些只会在真实试卷上出现的坑排查与应急方案5.1 扫描歪斜过多导致中线偏移这是我踩过的第一个大坑。有些扫描件不是方正坐在扫描仪里的而是带着几度的旋转。这时候页面的宽度和高度其实是一个倾斜四边形的外包矩形。直接按页宽取一半来切割会出现左右两页中线偏移切出的A4页要么一侧带着纸边阴影要么上边和下边不对称。解决办法是先做“纠偏”再切割。如果只是轻微歪斜PyMuPDF自带的page.rect已经是对应旋转后的页面矩形所以切割本身没有太大问题问题主要出在页面内容旋转后会和切割线产生斜交。建议的是用page.get_text(blocks)获取文本块坐标找出内容的整体边框。如果发现文本块的顶部y坐标平均值和底部y坐标平均值有明显差异说明页面是歪的。此时可以用PyMuPDF对页面执行page.set_rotation或先旋转再另存也可以直接用图像方法做检测把PDF某一页渲染成图片用OpenCV的cv2.minAreaRect求文本区域角度。不过这个功能比较复杂实际处理中我会选择最粗暴但有效的方式手动指定一条中线偏移量而不追求全自动。如果你处理的是大量结构类似的扫描件更高效方案是先做一次批量页码旋转预览确定所有页面的倾斜角度是否一致。若一致则在脚本中统一调整。5.2 左右页面内容不均衡另一个经常出现的情况是左右两栏内容宽度明显不一样。比如左栏题目写了满满一页右栏只有两三道题加一个大空白区。这个时候按面积对半切切出来的左页会有一条又宽又白的右边缘右页则左边几乎靠到裁切线视觉上很不平衡。这种情况我的策略是优先保证内容完整不强求两边等宽。也就是以文字区块的实际边界来确定切割线而不是机械用页面物理中线。实现思路是用PyMuPDF获取每个文字块的边界框blocks page.get_text(blocks) left_bound min(b[0] for b in blocks) right_bound max(b[2] for b in blocks) # 以左右内容边界的中点作为切割线 cut_x (left_bound right_bound) / 2这种方法对纯文字试卷很有效但遇到包含大量图片、表格的试卷文字块可能无法覆盖所有内容。更稳妥的做法是同时分析图像块。PyMuPDF里可以用page.get_image_info()获取图片位置把图片边界和文字边界合并成一个总的内容边界再计算中线。5.3 切掉了出界内容必须修剪裁剪框处理扫描件时经常会遇到某些页面的内容略微超出页面矩形。这通常是因为扫描时纸张边缘没有压平或者原卷页面本身有印刷出血。机械切割时如果裁剪区域完全等于页面物理矩形可能会把超出部分的内容截掉。解决方案是在裁剪框外围加一点点“出血扩展”。例如left_rect fitz.Rect(0, 0, half 3, height)向右扩展3个点确保左页右边缘不会因为细微扫描偏差而切断文字。但要注意扩展幅度不能太大否则会把右侧页面的内容带进来。一般2到5个点约0.7到1.7mm足够。另一个出界类型是顶部和底部。如果扫描稿上下边缘压得太紧裁剪框顶端从0开始可能把上标切掉。这时候把上方区域稍微上移比如从-2开始。PyMuPDF的Rect允许负数坐标不影响使用。5.4 完成后如何快速抽查处理完一堆文件后不要直接打印几十页一定要先抽查几页。我的抽查方法是使用PyMuPDF渲染输出PDF的前三页和最后三页为PNG图片打开看内容是否完整检查页面的矩形尺寸是否严格等于595×842随机挑一页用打印预览看整体效果。为了偷懒我可以写一个几行的检查脚本import fitz doc fitz.open(output/2025模考数学A4.pdf) for i in [0, 1, 2, len(doc)-3, len(doc)-2, len(doc)-1]: page doc[i] pix page.get_pixmap(dpi100) pix.save(fpreview_{i}.png) print(fPage {i1}: {page.rect.width:.1f} x {page.rect.height:.1f})看到所有页面的宽高都稳定在595×842附近内容预览里没有奇怪的空白或截断就可以放心打印了。我的最终流程小结现在每次拿到A3试卷扫描件我都会按这个固定流程走一遍把所有A3文件放进input文件夹命名按“年份科目卷名”统一跑一遍PyMuPDF分割脚本输出到output文件夹抽查三页预览图主要看内容和边距如果发现个别页偏移微调一次切割参数重新跑打印测试页确认输出为A4尺寸且文字清晰全部确认无误后再批量打印。这套流程下来哪怕是几十页的历年真题卷从拿到扫描件到打印出来也只要十几分钟。最花时间的反而是最初安装Python和依赖的过程但只要你装好一次后面就能一直复用。如果你手头也有一堆A3试卷、宽幅表格或者双栏文献需要转成A4大可不必去网上找各种限频限页的在线工具。自己准备一个离线分割脚本既能把控清晰度也能保护文件隐私还能批量处理用着顺手得多。接下来你只需要照着上面的脚本跑一遍大概率就能一次性解决掉这个问题。