资讯详情

Python脚本实现长图PDF按A4分页转换

📅 2026/10/7 12:08:42 | 华诺云谱 👁 阅读
Python脚本实现长图PDF按A4分页转换
你有没有遇到过这种情况手机上存了一张超长的网页截图或者从软件里导出了一份一页到底的PDF文件——报告、聊天记录、产品详情页、设计稿长图。直接打印它会缩成一页字小到看不清转发给别人对方打开后想看某一页只能疯狂往下拖。这种长图PDF看着省事用起来是真遭罪。我的处理方式很简单把这种一页到底的长图PDF改成规范的、按A4分页的多页PDF文件。今天这篇文章就把我踩过坑之后沉淀下来的完整方案写出来包括为什么我放弃了在线工具和付费软件、分页逻辑怎么算、两种不同场景文字型PDF和图片型PDF的拆分代码、以及一堆实际工作中容易翻车的细节。不管你是做文档处理的办公党还是写脚本的开发者应该都能用得上。1. 方案选型为什么我最后选了脚本解决先别急着找工具我们盘一下市面上常见做法的痛点。1.1 Adobe Acrobat 手动拆分的问题如果你手头恰好有 Adobe Acrobat Pro确实可以用它的裁剪页面功能把每一段内容手动框选出来一页一页切。听起来很直接但遇到真正的长图PDF你就知道多痛苦了。一份十几米长的滚动截图要切成几十页每一页得用鼠标精确对齐切割线眼睛看花了不说页与页之间还容易重复或反过来漏掉内容。更重要的是Acrobat 的裁剪框按百分比或者厘米算你要算好每一刀的位置几乎属于费手费眼的体力活偶尔处理一次还行批量处理想都别想。1.2 在线转换工具的安全隐患很多人的第一反应是搜长图PDF转多页PDF在线工具。我也试过几个排名靠前的网站但后来基本不敢用了。原因有三隐私问题你上传的文件是别人完全没看过的内部截图、聊天记录、合同草稿丢到别人的服务器上等于裸奔。有些免费工具甚至明着在协议里写有权处理你上传的数据。转换质量不可控在线工具为了压缩资源经常会把PDF先栅格化成低分辨率图片转出来文字发虚、线条有锯齿放大打印直接没法看。规则不透明文件大小限制、页数限制、等待队列免费用户经常被卡在莫名其妙的环节。1.3 Python 脚本方案好在哪最后我选择用 Python 脚本解决看起来很程序员但实际上这是最适合一劳永逸的方式可重复性建好一次脚本以后遇到长图PDF拖进去回车输出整整齐齐的多页PDF。改参数比如想分成长度更短的页只动一个变量。完全本地处理文件不出本机隐私安全。有保密要求的环境里这种做法是唯一的安心选择。质量可控可以自由选择保留原始矢量内容或者设定高DPI栅格化甚至控制JPEG压缩比例来控制最终文件大小。批量处理一个文件夹几十个长图PDFfor循环搞定在线工具只能一页一页折腾。这里要说明一下我下面的方案主要基于 Python 的PyMuPDF也就是fitz库配合处理图片切分时用Pillow。这俩库在 PDF 处理场景下都非常成熟安装也简单日常够用。2. 分页逻辑与参数计算先把数学问题想清楚动手写代码之前首先要把切成多大这个问题搞明白。2.1 A4 的标准尺寸与坐标系PDF 的内部度量单位是磅pt而不是像素这一点很多初学者会忽略。A4 纸的标准尺寸是210mm × 297mm换算过来就是宽595.276 pt高841.89 pt在 PyMuPDF 中我们会用page.rect拿到页面的宽高单位也是 pt。一个长图PDF假设它的页面尺寸是1200 pt × 24000 pt那就意味着这是超高的一页需要拆成多页。2.2 核心公式需要拆几页页数计算很简单向上取整即可目标页数 ⌈原页面高度 ÷ 目标页面高度⌉以刚才的尺寸为例⌈24000 ÷ 841.89⌉ ⌈28.51⌉ 29页。但是注意如果无脑按这个比例拆每一页的内容宽度就会超过A4标准宽度。1200pt 的宽比 595.276pt 大了一倍。所以还需要一个处理宽度的策略。2.3 宽度策略的三种情况长图PDF的宽度五花八门处理逻辑不能一概而论情况一原页面宽度 ≈ A4 宽度比如宽度在 560~610pt 之间。这种情况最简单直接沿高度方向等分即可每页保持原宽度高度为 A4 高度。情况二原页面宽度明显大于 A4比如宽度 1200pt如果直接把原页面塞进A4宽度的页面里那么每页要等比缩放。这样每页显示的内容是从原图中按 A4 宽高比裁剪出一块再整体缩放到 A4 页面上。听起来复杂但如果用 PyMuPDF 的裁剪缩放其实只需要一个坐标系转换。情况三原页面宽度明显小于 A4手机竖屏截图导出的PDF宽度往往只有 400~500pt。这种如果直接拉伸到A4宽度内容会变模糊而且比例失真。更好的做法是保持原宽居中排放两侧留白页高还是 A4 的高度。打印出来像原始截图放在白纸上观感舒服得多。2.4 保留边距与顶部偏移还有一个细节如果第一页直接从头开始紧密的内容会贴着页面最上边打印出来不好看。我习惯给每一页留一点上下边距。这个边距可以在计算裁剪区域时做偏移比如每页实际取的高度是目标页高 - 上下边距再把裁剪框平移对应的高度。具体参数建议场景默认建议值A4页面宽595.276 ptA4页面高841.89 pt上边距24 pt约0.85cm下边距24 pt左右边距0~36 pt 视情况而定这段逻辑看起来不难但实际操作中有个最容易翻车的问题裁剪边界刚好把一行文字或一张图片切成两半。这个在第五部分我会细讲避坑方法。3. 实操两种核心拆分方案与完整代码现在进入正题写代码。我准备了两个方案适用场景不同你可以根据文件实际情况二选一。3.1 方案A文本型PDF直接裁剪推荐优先尝试如果这份长图PDF本身就是矢量PDF——比如从设计软件导出的内容或者从网页通过 另存为PDF 生成的文字型PDF——那么直接用 PyMuPDF 的show_pdf_page或clip参数来切分可以做到完全保留矢量信息不重新编码。这个方案的核心思路是打开原始PDF读取第一页通常这种长图只有一个页面的尺寸创建一个新PDF文档设置目标A4页面尺寸在原页面上沿Y轴滑动一个裁剪矩形每次裁剪的高度等于目标页面高度减去边距后把裁剪区域内原页面的内容按缩放系数绘制到新页面上。代码实现在这里import fitz # PyMuPDF import math def split_long_pdf_pymupdf(input_path, output_path, page_width595.276, page_height841.89, margin_top24, margin_bottom24, margin_left0, margin_right0): 使用 PyMuPDF 直接裁剪原 PDF 页面内容保留矢量信息。 适合文字型/矢量型长图PDF。 src_doc fitz.open(input_path) # 长图PDF通常有1页直接取第一页 src_page src_doc[0] src_rect src_page.rect usable_height page_height - margin_top - margin_bottom # 第一步计算缩放比例 # 如果原页面宽度大于目标可用宽度那就按宽度比例缩放 # 如果原页面宽度小于目标宽度保持原大小居中1:1 available_width page_width - margin_left - margin_right if src_rect.width available_width: scale_factor available_width / src_rect.width target_content_height src_rect.height * scale_factor else: scale_factor 1.0 target_content_height src_rect.height # 第二步确定裁剪窗口在原始页面上的移动步长 # 每页展示的内容高度原始坐标系中 usable_height / scale_factor step_height_original usable_height / scale_factor if scale_factor ! 0 else usable_height # 计算总页数 total_pages math.ceil(src_rect.height / step_height_original) # 创建新的空白PDF new_doc fitz.open() for i in range(total_pages): # 新页面 new_page new_doc.new_page(widthpage_width, heightpage_height) # 当前裁剪区域的Y轴起点原始页面坐标系 src_y0 i * step_height_original src_y1 min(src_y0 step_height_original, src_rect.height) # 构建裁剪矩形。宽度可以取原始宽全宽因为缩放是在目标端控制的 clip_rect fitz.Rect(src_rect.x0, src_y0, src_rect.x1, src_y1) # 计算内容在目标页面上的放置区域 # 这里按等比缩放 居中处理 target_content_width src_rect.width * scale_factor target_content_height_target (src_y1 - src_y0) * scale_factor # 水平居中垂直定位在边距起始处 x_offset margin_left (available_width - target_content_width) / 2 y_offset margin_top target_rect fitz.Rect(x_offset, y_offset, x_offset target_content_width, y_offset target_content_height_target) # 核心把原始页面的 clip_rect 区域绘制到新页面的 target_rect new_page.show_pdf_page(target_rect, src_doc, 0, clipclip_rect) new_doc.save(output_path) new_doc.close() src_doc.close() print(f转换完成生成 {total_pages} 页输出文件{output_path}) # 用法示例 # split_long_pdf_pymupdf(input_long.pdf, output_multi.pdf)原理简单说明show_pdf_page本质上是在新页面上放置另一个PDF页面/页面区域的内容。给它一个target_rect和一个clip矩形它就能把原页面中clip指定的区域等比缩放后绘制到target_rect里。因为不经过像素栅格化所以文字和线条依然保持矢量放大打印绝对不会虚。3.2 方案B图片型PDF转图片后切分适合纯截图/扫描件如果长图PDF是从微信聊天截图、微博长图、电商详情页截图导出的那它本质上其实是一张被包在PDF壳里的大图片。这种PDF没有文字层方案A虽然也能裁剪但效果不受影响不过还有一个更稳妥的处理方式——先把PDF渲染成高清图片再用图片处理库按规范切成多等份并导出新PDF。这样做的好处是对于本身就是图片的内容切分边界控制更直观而且可以顺手调节清晰度、压缩输出大小。直接上代码import fitz from PIL import Image import io import math def split_image_pdf(input_path, output_path, dpi200, output_jpg_quality90): 将长图图片型PDF渲染为高清图片按A4比例切分后输出多页PDF。 适合截图/扫描件等无文字层的PDF。 src_doc fitz.open(input_path) src_page src_doc[0] # 渲染整页为图片 # zoom 值控制渲染分辨率dpi200 时 zoom 200/72 ≈ 2.78 zoom dpi / 72 mat fitz.Matrix(zoom, zoom) pix src_page.get_pixmap(matrixmat, alphaFalse) img Image.open(io.BytesIO(pix.tobytes(png))) # 拿到图片的像素宽高 img_width, img_height img.size # A4对应的像素尺寸按同样DPI计算 a4_width_px int(595.276 * zoom) a4_height_px int(841.89 * zoom) # 因为长图的宽高比通常与A4不同按宽度适配等比计算每页高度 if img_width a4_width_px: page_height_px int(img_width * (a4_height_px / a4_width_px)) # 等比例缩放后每页的高度 a4_height_px scale a4_width_px / img_width else: page_height_px a4_height_px scale 1.0 # 宽小于A4不放大居中放置 # 按新高度切分 total_pages math.ceil(img_height / page_height_px) # 创建新的PDF doc fitz.open() for i in range(total_pages): top i * page_height_px bottom min(top page_height_px, img_height) # 裁剪 crop img.crop((0, top, img_width, bottom)) # 如果裁剪出的高度不足加白边补齐 if crop.height page_height_px: padded Image.new(RGB, (img_width, page_height_px), white) padded.paste(crop, (0, 0)) crop padded # 如果宽度大于A4宽度先缩放 if crop.width ! a4_width_px: crop crop.resize((a4_width_px, page_height_px), Image.LANCZOS) # 写入PDF页面居中放置左右留白自动处理 page doc.new_page(width595.276, height841.89) # 把PIL图片转成bytes再插入 img_bytes io.BytesIO() crop.save(img_bytes, formatJPEG, qualityoutput_jpg_quality) page.insert_image(page.rect, streamimg_bytes.getvalue()) doc.save(output_path) doc.close() src_doc.close() print(f转换完成生成 {total_pages} 页输出文件{output_path}) # 用法示例 # split_image_pdf(input_long_screenshot.pdf, output_multi.pdf, dpi200, output_jpg_quality85)关键参数讲解dpi渲染清晰度。屏幕阅读 150 足够如果要打印建议 200~300。DPI 越高图片越大处理越慢自己权衡。output_jpg_quality输出JPEG压缩质量数值 0~100推荐 85 左右既能控制体积又不明显损失细节。page.insert_image(page.rect, ...)把图片拉伸铺满A4页面因为前面已经等比例处理好了所以不会变形。3.3 两种方案的选择原则判断方法适用方案在PDF阅读器中能选中文字、复制文字方案A保留矢量文字只能看不能选字内容是一整张大图方案B无文字层直接切图图纸/设计稿线条要求极高优先方案A矢量无损手机截图长图内容分辨率一般方案B更简单直接有一个小技巧90%的长图PDF其实是长截图导出的PDF也就是方案B的情况。但如果你拿到的是从网页打印生成的长PDF里面文字可以选中方案A会更快而且生成的文件更小。4. 批量处理与使用细节让脚本变成趁手工具单文件的脚本已经能解决日常需求但实际用起来还有不少可以优化的地方。我把踩过的坑和常用的增强功能都整合进这一版。4.1 文件夹批量转换工作里很难只处理一个文件经常是收到一个文件夹里面十几个长图PDF。挨个跑函数太傻直接加一个批处理外壳import os import glob def batch_convert(folder_path, modeauto): 批量将文件夹内的长图PDF转为多页PDF mode: vector 强制用方案A, image 强制用方案B, auto 自动判断 for pdf_path in glob.glob(os.path.join(folder_path, *.pdf)): if _multipage in pdf_path: continue # 跳过已转换的文件避免重复处理 # 自动判断尝试提取文字有文字就用方案A if mode auto: try: doc fitz.open(pdf_path) page doc[0] text page.get_text(text).strip() doc.close() if len(text) 10: print(f[方案A-文字型] {pdf_path}) split_long_pdf_pymupdf(pdf_path, pdf_path.replace(.pdf, _multipage.pdf)) else: print(f[方案B-图片型] {pdf_path}) split_image_pdf(pdf_path, pdf_path.replace(.pdf, _multipage.pdf)) except Exception as e: print(f自动判断失败回退方案B: {pdf_path}, 错误: {e}) split_image_pdf(pdf_path, pdf_path.replace(.pdf, _multipage.pdf)) elif mode vector: split_long_pdf_pymupdf(pdf_path, pdf_path.replace(.pdf, _multipage.pdf)) else: split_image_pdf(pdf_path, pdf_path.replace(.pdf, _multipage.pdf)) # 如果作为命令行脚本执行 if __name__ __main__: batch_convert(./待处理文件夹)自动判断这个思路在实际工作中非常实用。原理很简单PDF如果有文字层page.get_text()能提取到内容如果是一张纯图片提取出来是空的。根据这个特征自动决定走哪条转换路径基本不用人工干预。4.2 输出文件名管理我习惯在原始文件名后面加_multipage后缀这样一眼能分辨转换前和转换后而且不会误覆盖原文件。如果你想要输出到独立的输出文件夹可以在函数参数里加一个输出目录用os.path.basename重新拼路径。4.3 大文件内存优化有一种极端情况长图PDF本身很大比如高达一两百MB的高分辨率扫描长图。方案B渲染整页图片再切内存容易爆。遇到这种建议稍微改一下代码不要一次性把整个页面渲染成完整的大图再切。PyMuPDF允许直接按区域分块渲染也就是用page.get_pixmap(clipclip_rect)只渲染当前需要的区域然后用show_pdf_page方案处理。我是在实际把一个 2.3万像素高的长图转出来的过程中才注意到这个问题的当时用渲染整图的方式直接吃掉了 6GB 内存机器差点卡死。改进后的思路是每页一个clip逐块渲染逐页保存内存峰值大幅下降。4.4 加密PDF的处理如果原PDF有打开密码脚本会报错。需要在打开文档时传入密码doc fitz.open(input_path) if doc.needs_pass: doc.authenticate(你的密码)如果你不想硬编码密码在脚本里可以用getpass在运行时提示输入。这是一个很实际的需求我经常收到客户发来的加密码长图PDF。5. 高频问题与排查这些坑我替你踩过了5.1 拆分后文字在页面交界处被切断这是方案A最常见的问题。因为裁剪线是机械的数学直线如果刚好压在一行文字中间那一行文字会被切成两半分落在两页上非常影响阅读。这个问题在纯代码层面很难100%解决因为你不知道文字的精确Y坐标。我的经验解法是方案A中可以先用page.get_text(dict)解析出所有文字块的边界框找到每个Y坐标上是否有文字然后微调切割线位置避开文字块。但代码会复杂许多。如果要求不高手动设置每页高度稍微缩短一点比如把usable_height减少 20pt这样切割线落在文字中的概率会下降不是绝对但有效。如果是方案B图片型文字其实是图片的一部分没有半行文字的尴尬怎么切都一样。所以音频上对图片方案更放心。我的最终建议对于文字型长PDF如果切割线切到了文字可以用一个小技巧——让相邻页面之间有轻微的上下内容重叠比如重叠 6~8pt然后在打印预览时接受这个微小的重叠。很多人会用这个方案。但如果你要追求完美那就直接转成高清图片再切图片切分不会有这个问题。5.2 页面空白太多有时候页面框按A4高度切完最后一页只有一点点内容大段白边。处理方式可以把step_height_original缩小也就是把目标页高设得比A4标准高度小一截这样页数增加但每页内容排布更均匀——但这不是比例缩放而是调整每页展示的内容高度。或者反过来把上一页的内容吞掉最后一点内容让最后一页留白更少。更科学一点的做法预先计算剩余高度如果剩余高度小于目标高度的 20%就把这部分并到前面几页去。这个逻辑实现也不复杂主要是在切分循环里做一次判断。remaining_height src_rect.height - src_y1 if remaining_height usable_height * 0.2: # 这次剪纸框高度加上剩余高度塞到本页 src_y1 src_rect.height5.3 输出文件太大或太小方案B中quality90转出来的文件可能很大动辄几十MB。这时可以调低到quality75~80或者对输出页面做二次压缩。如果你发现转出来的文件比原文件还大多半是因为原图本身是PNG或无损格式转成JPEG反而增加了体积。解决办法是在插入图片前对画布做适度的缩小采样。对于方案A文件大小基本可控因为矢量PDF本身就是紧凑格式。5.4 原始PDF不是一页而是已经有多页严格来说长图PDF应该是一页长图但有些情况是原文件已经有了多页只是每一页都超长。这时候上面代码只取了第一页src_doc[0]会漏掉后面的页面。建议在代码中加入遍历所有页面的逻辑for src_index in range(src_doc.page_count): src_page src_doc[src_index] if src_page.rect.height src_page.rect.width: # 判断长图特征 # 执行切分逻辑但如果每页是A4正常尺寸不需要转换。加一个判断高度大于 A4 高度即可。5.5 页面里有多余水印和二维码收到的长图PDF经常带着水印或二维码有时是在每一页都会重复出现的固定元素。切分后每一页都会带上水印。如果你的目标是清理掉这些元素那就不是分页能搞定的得先做水印识别与遮盖。这里我提供一个暴力但实用的思路水印通常位于页面的四周固定区域切分后可以直接在新页面的目标区域叠加一个白色矩形遮罩把水印盖掉。代码中用page.draw_rect画一个白色矩形即可。不过要小心如果水印在页面中间覆盖了文字就不能简单遮盖了那涉及更高级的图像修复这里不展开。5.6 高达 300 DPI 的扫描型长图PDF切出来还是虚扫描件本身分辨率不够怎么切都不可能变清晰。渲染时设置dpi300可能带来成倍的内存压力但并不会增加真实细节。遇到这种文件保持dpi200~220就足够节省时间体积也更合理。如果原件质量太差再高的DPI也是原样放大。6. 我实际使用中的一点习惯经常处理这类文件之后我发现真正稳定高效的做法是先用 Acrobat 或浏览器打开原PDF看一眼确认是文字型还是图片型。然后对于图片型的直接扔进方案B脚本对于文字型的先试试方案A如果有半行文字问题再考虑转方案B。反正两个转换函数都是几分钟能跑完的事多换一次不心疼。关于分页高度我实际最常用的是把usable_height设为760pt左右而不是把841.89pt全部用完。这样每页四周都有点呼吸感打印后装订也方便。换来的代价是页数会多一些——但相比阅读体验这完全值得。最后再分享一个小技巧转换完成后用fitz把生成的 PDF 首页截图缩略图拼一个简单预览页出来或者用系统的文件预览工具快速翻两页看看分页效果。我可以负责任地说每一次肉眼检查都能发现一两页边缘位置需要微调尤其是那些带表格和图片密集排布的内容。所以花几秒钟看一下输出结果比批量跑完直接发给别人而不检查的结果要可靠得多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑