夜雨聆风学习资料网

ARTICLE · 1166338

PDF页面批量旋转,用Python 3行代码搞定(附完整脚本)

PDF页面批量旋转,用Python 3行代码搞定(附完整脚本)

一、先说个扎心的事

你有没有遇到过这种情况:

用扫描仪扫了一本书,500 页,兴冲冲打开一看——全是横的。

或者从某个系统导出的 PDF 报表,页眉跑到了左边,得把脖子转 90 度才能看。

手动旋转?Acrobat 里一页一页转,转到手抽筋,还得掏钱买 Pro。

其实这事儿用 Python 十几行代码就能解决,而且能一次性处理一整个文件夹。


二、准备工作

只需要一个库:

pip install pypdf

注意:是老牌的 PyPDF2 的官方继任者 pypdf(2023 年后 PyPDF2 已停止维护,代码全部合并进 pypdf)。别装错了。

如果你还在用 Python 3.7 以下,先升级一下,pypdf 新版要求 3.8+。


三、核心代码:只有 8 行

先看最小可运行版本:

from pypdf import PdfReader, PdfWriterreader = PdfReader("input.pdf")writer = PdfWriter()for page in reader.pages:    page.rotate(90)        # 顺时针 90 度    writer.add_page(page)with open("output.pdf", "wb") as f:    writer.write(f)

跑一下,整个 PDF 的每一页都顺时针转了 90 度。

page.rotate() 的参数必须是 90 的整数倍,传 45 会直接报错。

  • 顺时针 90°:rotate(90)

  • 倒过来 180°:rotate(180)

  • 逆时针 90°:rotate(270)

(pypdf 3.0 以前的方法名是 rotateClockwise() / rotateCounterClockwise(),老代码看到别懵。)


四、进阶:只转指定页面

实际场景里,往往不是全部都要转。

比如一本扫描书,奇数页正常、偶数页是反的——这时候就要能精确控制页码。

def parse_pages(expr, total):    """把 '1,3,5-8' 解析成 0 基页码集合"""    if not expr:        return set(range(total))    pages = set()    for part in expr.split(","):        part = part.strip()        if not part:            continue        if "-" in part:            start, end = part.split("-", 1)            pages.update(range(int(start) - 1, int(end)))        else:            pages.add(int(part) - 1)    return {p for p in pages if 0 <= p < total}

支持 1,3,5-8 这种写法,跟打印时选页码范围一个手感。


五、完整脚本:支持文件夹批量处理

把下面这段存成 rotate_pdf.py,就是一个命令行工具了。

# rotate_pdf.pyimport argparsefrom pathlib import Pathfrom pypdf import PdfReader, PdfWriterdef parse_pages(expr, total):    if not expr:        return set(range(total))    pages = set()    for part in expr.split(","):        part = part.strip()        if not part:            continue        if "-" in part:            start, end = part.split("-", 1)            pages.update(range(int(start) - 1, int(end)))        else:            pages.add(int(part) - 1)    return {p for p in pages if 0 <= p < total}def rotate_pdf(src, dst, angle=90, page_expr=None):    reader = PdfReader(str(src))    # 处理加密 PDF(空密码)    if reader.is_encrypted:        reader.decrypt("")    writer = PdfWriter()    targets = parse_pages(page_expr, len(reader.pages))    for i, page in enumerate(reader.pages):        if i in targets:            page.rotate(angle)        writer.add_page(page)    with open(dst, "wb") as f:        writer.write(f)    print(f"✅ {src.name} → {dst.name}|旋转 {len(targets)}/{len(reader.pages)} 页")def main():    ap = argparse.ArgumentParser(description="批量旋转 PDF 页面")    ap.add_argument("input", help="PDF 文件或文件夹")    ap.add_argument("-o", "--output", default="output", help="输出文件或文件夹")    ap.add_argument("-a", "--angle", type=int, default=90,                    choices=[90, 180, 270], help="顺时针角度,默认 90")    ap.add_argument("-p", "--pages", default=None,                    help="页码,如 1,3,5-8;不填则全部")    args = ap.parse_args()    src, dst = Path(args.input), Path(args.output)    if src.is_dir():        dst.mkdir(parents=True, exist_ok=True)        pdfs = sorted(src.glob("*.pdf"))        if not pdfs:            print("目录下没有 PDF 文件")            return        for f in pdfs:            rotate_pdf(f, dst / f.name, args.angle, args.pages)        print(f"\n🎉 完成!共 {len(pdfs)} 个文件,输出目录:{dst}")    else:        if dst.is_dir() or not dst.suffix:            dst.mkdir(parents=True, exist_ok=True)            dst = dst / src.name        rotate_pdf(src, dst, args.angle, args.pages)if __name__ == "__main__":    main()

用法示例

# 单个文件,全部页面顺时针转 90 度python rotate_pdf.py book.pdf -o book_fixed.pdf# 只转第 2、4、6 到 10 页,转 180 度python rotate_pdf.py book.pdf -p "2,4,6-10" -a 180# 整个文件夹批量处理(含子目录里的 PDF 都可以丢进来)python rotate_pdf.py ./scans -o ./fixed

处理一个 500 页的 PDF,大概一两秒。


六、几个必须知道的坑

1. 旋转是"打标记",不是重排内容

rotate() 实际是在页面对象上写了一个 /Rotate 属性。绝大多数阅读器(Adobe、Chrome、WPS、macOS 预览)都会认这个标记,但少数老旧或第三方工具可能忽略它,看起来就像"没转"。

如果需要彻底把旋转烧进内容里,得重新渲染一遍:

import fitz  # pip install PyMuPDFdoc = fitz.open("input.pdf")for page in doc:    page.set_rotation(90)doc.save("output.pdf")

PyMuPDF 处理扫描件更稳,代价是文件体积可能变大。

2. 书签和目录会丢

上面用的 add_page() 是逐页复制,原 PDF 的书签、大纲、表单可能丢失。

如果要保留,改成整体复制再改:

writer.append(reader)          # 一次性带过来,保留书签大纲for page in writer.pages:    page.rotate(90)

3. 原文件已经有旋转标记会叠加

如果 PDF 本身已经带了 90° 的 /Rotate,你再转 90°,结果是 180°。处理前最好先用 page.get("/Rotate", 0) 看一眼。

4. 加密 PDF 要解密

有些 PDF 打开不需要密码,但不能编辑。reader.decrypt("") 就是用空密码试一下,能解就解。

5. 别忘了备份

脚本默认输出到新文件,但自己写代码时很容易 open(..., "wb") 直接覆盖原文件。转错方向哭都来不及。


七、还能再自动化一点吗?

如果你要处理的是扫描件,且方向混乱没有规律,手动指定页码就不够用了。

思路是用 OCR 判断文字方向:

import fitzdoc = fitz.open("scan.pdf")for i, page in enumerate(doc):    text = page.get_text()    # 提取文字块的方向信息,判断该页是否需要旋转    blocks = page.get_text("dict")["blocks"]    if blocks:        # 根据 bbox 的宽高比粗略判断        w, h = blocks[0]["bbox"][2] - blocks[0]["bbox"][0], \               blocks[0]["bbox"][3] - blocks[0]["bbox"][1]        if w > h * 2:            page.set_rotation(90)

当然,扫描件通常没有文字层,得先跑一遍 OCR(PaddleOCR / Tesseract),再根据识别结果的置信度判断方向。这套方案复杂度陡增,如果不是几百上千页,手动列个页码范围更快。


八、总结

场景方案
全部页面转同一角度page.rotate(90) 遍历
指定页码加个 parse_pages()
批量文件夹Path.glob("*.pdf") 循环
保留书签大纲writer.append(reader)
扫描件方向混乱PyMuPDF + OCR 检测

核心就一句话:pypdf 读进来,每页 rotate 一下,写出去。

把这 20 行脚本存下来,以后遇到歪脖子 PDF,命令行敲一下,比打开 Acrobat 还快。

相关学习资料