
1. 批量处理 PDF 页眉页脚的真实痛点与工程化思路批量给 PDF 添加、修改、删除页眉页脚听起来像是个小需求真动手才知道坑有多深。我最近就遇到一个典型场景一批已经盖章的 PDF 文件上传电子版时才发现页码全错了每一页都印着“11”。文件本身内容没问题就是页脚那行字错了重新走一遍盖章流程成本太高只能想办法在电子版上批量修掉。第一反应是找现成工具。问了一圈 AI 助手给的方案要么是桌面软件要么是在线平台。桌面软件大多收费免费版限制页数在线平台倒是能改但一次只能传一个文件、一页一页处理几十个文件下来手都点酸了。更麻烦的是页眉页脚这类操作往往需要“先删旧的、再加新的”很多工具只支持添加不支持精准删除已有内容改完反而叠了两层。这就是批量 PDF 页眉页脚处理的第一个难点操作类型不统一。添加、修改、删除本质上是三种不同的处理逻辑。添加是在页面边缘叠加新内容修改是先定位旧内容再替换删除则是把指定区域的已有文字或图形抹掉。如果用一个脚本硬编码很容易写成“只能加不能删”的半成品。第二个难点是批量与一致性。单文件处理时你手动调一下位置、字号、颜色就行但面对几十上百个文件必须把页眉页脚的参数模板化——边距多少、字体多大、奇偶页是否对称、首页是否跳过这些都要提前定好否则每个文件输出效果都不一样验收时根本没法通过。第三个难点是脚本与外部服务的衔接。很多批量脚本需要调用 OCR、文档解析或 AI 辅助生成页眉内容这就涉及 API Key 的管理。如果每个脚本、每个工具都单独配一套 Key维护起来非常乱。用统一 Key 通道把脚本和自动化流程串起来才能做到“一次配置、多处复用”。所以我的思路是先搭一个清晰的目录结构把输入、输出、模板、日志分开然后写一个可复用的 Python 脚本用参数控制添加/修改/删除三种模式最后通过统一 API 通道接入让脚本可以调用模型能力做内容识别或校验。下面按步骤拆开讲你可以直接跟着操作。2. TaoToken 统一 Key 前置准备让脚本与自动化流程共用一条通道在批量处理 PDF 页眉页脚的流水线里脚本本身负责页面操作但有些环节需要外部能力介入。比如旧页脚是图片形式需要先识别出文字才能精准删除或者你想让模型根据文件名自动生成页眉标题再或者批量任务跑完后需要调用模型做一次输出一致性检查。这些场景都涉及 API 调用。如果每个脚本单独申请 Key、单独配环境变量很快就会乱套。我的做法是用 TaoToken 作为统一 Key 通道把模型对话、脚本调用、自动化任务都接到同一个入口。这样你只需要维护一份 Key换脚本、换工具都不用重新配置。先明确几个地址后面配置会用到官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基础地址https://taotoken.net/api模型对话页https://taotoken.net/api/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewriteAPI Keys 管理https://taotoken.net/api/keys?utm_sourcetaotoken_aicg_blog_endutm_contentkeysutm_campaignrewrite接入文档https://taotoken.net/api/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite操作上分三步。第一步在 API Keys 页面创建一个新 Key命名建议带上用途比如pdf-footer-batch方便后续排查。第二步把 Key 写入环境变量不要硬编码在脚本里。Linux/macOS 下可以这样export TAOTOKEN_API_KEY你的Key export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell$env:TAOTOKEN_API_KEY你的Key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api第三步在脚本里读取环境变量。Python 示例import os API_KEY os.environ.get(TAOTOKEN_API_KEY) BASE_URL os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) if not API_KEY: raise RuntimeError(未找到 TAOTOKEN_API_KEY请先配置环境变量)这里有个细节Base URL 末尾不要带斜杠很多 SDK 会自动拼接/v1/chat/completions之类的路径多一个斜杠容易出 404。另外如果你用的是 OpenAI 兼容的客户端可以把base_url直接指向https://taotoken.net/apiKey 用上面创建的即可。配置完成后建议先跑一个最小请求验证通道是否通。用 curlcurl -s https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY | head -c 500能返回模型列表就说明 Key 和地址都没问题。这一步别跳过后面脚本报错时你能快速判断是通道问题还是业务代码问题。3. 可复制配置目录结构、页眉页脚参数模板与批处理命令这一节是整篇的核心直接给你能复制的配置。先看目录结构我习惯这样组织pdf-header-footer/ ├── input/ # 待处理 PDF │ ├── sample1.pdf │ ├── sample2.pdf │ └── sample3.pdf ├── output/ # 处理结果 ├── templates/ │ └── footer_config.json # 页眉页脚参数模板 ├── logs/ │ └── run.log ├── scripts/ │ ├── process_pdf.py # 主处理脚本 │ └── verify_output.py # 输出一致性校验 └── requirements.txtrequirements.txt内容pypdf4.0.0 reportlab4.0.0 requests2.31.0页眉页脚参数模板templates/footer_config.json是关键它决定了批量输出的一致性。下面这份配置覆盖了添加、修改、删除三种模式{ mode: replace, header: { enabled: true, text: 内部资料 · 请勿外传, font_size: 9, font_color: #666666, margin_top: 20, align: center, skip_first_page: true }, footer: { enabled: true, text_template: 第 {page} 页 / 共 {total} 页, font_size: 9, font_color: #333333, margin_bottom: 20, align: center, skip_first_page: false }, remove_region: { footer_height: 40, header_height: 40 } }字段说明用表格对照更清楚字段作用常用值mode处理模式add / replace / removeheader.enabled是否添加页眉true / falsefooter.text_template页脚文本模板支持 {page}、{total}margin_top / margin_bottom边距点20 约等于 0.7cmskip_first_page首页是否跳过封面通常设 trueremove_region.footer_height删除区域高度根据旧页脚位置调整主处理脚本scripts/process_pdf.py的核心逻辑import json import os from pathlib import Path from pypdf import PdfReader, PdfWriter from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 import io def load_config(path): with open(path, r, encodingutf-8) as f: return json.load(f) def make_overlay(width, height, config, page_num, total): packet io.BytesIO() c canvas.Canvas(packet, pagesize(width, height)) header config.get(header, {}) footer config.get(footer, {}) if header.get(enabled): c.setFont(Helvetica, header.get(font_size, 9)) c.setFillColor(header.get(font_color, #666666)) c.drawCentredString(width / 2, height - header.get(margin_top, 20), header.get(text, )) if footer.get(enabled): text footer.get(text_template, ).format(pagepage_num, totaltotal) c.setFont(Helvetica, footer.get(font_size, 9)) c.setFillColor(footer.get(font_color, #333333)) c.drawCentredString(width / 2, footer.get(margin_bottom, 20), text) c.save() packet.seek(0) return PdfReader(packet) def process_file(src, dst, config): reader PdfReader(src) writer PdfWriter() total len(reader.pages) for i, page in enumerate(reader.pages): if config.get(mode) remove: # 删除模式用空白覆盖指定区域 pass if config.get(header, {}).get(skip_first_page) and i 0: writer.add_page(page) continue overlay make_overlay( float(page.mediabox.width), float(page.mediabox.height), config, i 1, total ) page.merge_page(overlay.pages[0]) writer.add_page(page) with open(dst, wb) as f: writer.write(f) if __name__ __main__: config load_config(templates/footer_config.json) input_dir Path(input) output_dir Path(output) output_dir.mkdir(exist_okTrue) for pdf in input_dir.glob(*.pdf): out output_dir / pdf.name process_file(str(pdf), str(out), config) print(f处理完成: {pdf.name})批处理命令很简单cd pdf-header-footer python scripts/process_pdf.py如果你要处理大量文件可以加一个 shell 循环配合日志for f in input/*.pdf; do echo [$(date)] 开始处理 $f logs/run.log python scripts/process_pdf.py $f logs/run.log 21 done注意删除模式比添加复杂因为 PDF 没有“图层”概念旧页脚如果是文字需要用pypdf的extract_text定位后重建页面或者用空白矩形覆盖。覆盖法简单但会留白适合旧页脚位置固定的场景。如果旧页脚是图片建议先用模型识别出文字内容再决定覆盖区域。4. 验证请求与成功结果用 3 个样例 PDF 检查输出一致性配置写完后别急着上生产。先拿 3 个样例 PDF 跑一遍验证输出一致性。我准备的样例覆盖三种情况sample1 是普通文档、sample2 是带封面的报告、sample3 是旧页脚为图片的扫描件。验证脚本scripts/verify_output.pyfrom pypdf import PdfReader from pathlib import Path def check(pdf_path): reader PdfReader(pdf_path) total len(reader.pages) first_text reader.pages[0].extract_text() or last_text reader.pages[-1].extract_text() or print(f文件: {pdf_path.name}) print(f 页数: {total}) print(f 首页含页眉: {内部资料 in first_text}) print(f 末页含页脚: {共 in last_text and 页 in last_text}) print(---) for pdf in Path(output).glob(*.pdf): check(pdf)预期输出文件: sample1.pdf 页数: 5 首页含页眉: False 末页含页脚: True --- 文件: sample2.pdf 页数: 12 首页含页眉: False 末页含页脚: True --- 文件: sample3.pdf 页数: 3 首页含页眉: False 末页含页脚: True ---首页页眉为 False 是正常的因为配置里skip_first_page设了 true。如果你希望封面也带页眉把它改成 false 即可。末页页脚为 True说明{page}和{total}模板替换成功。除了文本检查还要做视觉抽查。用 PDF 阅读器打开 output 目录下的文件重点看三处页眉是否压到正文、页脚是否超出页面、奇偶页位置是否一致。我踩过的坑是A4 和 Letter 混在一起时边距按 A4 设的Letter 页面底部会偏上。解决办法是在脚本里读取page.mediabox动态计算而不是写死坐标。如果你用统一 Key 通道接了模型做校验可以再加一步把输出 PDF 的首页文本发给模型让它判断页眉页脚是否符合模板要求。请求示例import requests, os resp requests.post( f{os.environ[TAOTOKEN_BASE_URL]}/v1/chat/completions, headers{Authorization: fBearer {os.environ[TAOTOKEN_API_KEY]}}, json{ model: gpt-4o-mini, messages: [ {role: user, content: f检查这段页眉页脚文本是否符合内部资料页码格式{first_text}} ] } ) print(resp.json()[choices][0][message][content])这一步不是必须的但批量任务里加一道模型校验能提前发现模板替换异常比人工翻几十个文件快得多。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth批量脚本跑起来后报错基本集中在通道和解析两类。下面按真实报错对照排查。401 Unauthorized。最常见的原因是 Key 没读到或写错了。先确认环境变量echo $TAOTOKEN_API_KEY如果输出为空说明当前终端没加载。检查是不是在另一个 shell 里 export 的或者写进了.bashrc但没 source。另一个原因是 Key 带了多余空格复制时容易带上换行。建议用echo -n测试或者直接在脚本里strip()。local proxy failed。这个报错通常出现在请求发不出去的时候。先检查网络是否能访问https://taotoken.net/api用 curl 测一下。如果公司网络有出口限制联系运维放行。注意不要配置任何非官方的转发工具直接用官方地址即可。另外有些 Python 环境会读取HTTP_PROXY环境变量如果之前设过先 unsetunset HTTP_PROXY HTTPS_PROXYreading choices。这是解析响应时choices字段不存在导致的。常见于三种情况请求体格式不对、模型名写错、或者返回的是错误信息而不是正常响应。排查时先把原始响应打出来print(resp.status_code) print(resp.text[:500])如果返回{error: ...}按错误信息处理。如果模型名不对换成文档里列出的可用模型。还有一种情况是流式响应没开stream却按流式解析也会读不到choices。OAuth 相关报错。如果你用的是某些 CLI 工具比如 Claude Code 类它可能默认走 OAuth 登录而不是 API Key。这时候需要在配置里显式指定 Base URL 和 Key。以 Claude Code 为例配置文件通常在~/.claude/settings.json或项目级.claude/settings.json写入{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: 你的Key } }如果你用的是 Codex 类工具检查~/.codex/auth.json确保里面是 API Key 而不是 OAuth token。Cline MCP 场景下在 MCP 配置里填全三件套Base URL、Key、Model ID缺一个都会连不上。最后提醒一个业务层面的坑删除页脚时如果旧页脚是矢量图形覆盖法可能盖不干净边缘会留一条线。这时候要么扩大覆盖区域要么用pypdf的页面重建方式。重建方式代码量大但效果最干净适合对输出质量要求高的场景。6. 把脚本接进自动化流程统一 Key 通道的长期用法单次批量处理跑通后下一步是把它接进日常自动化。我的做法是用统一 Key 通道做三件事脚本调用、任务调度、结果校验。脚本调用方面所有需要模型能力的环节都走同一个 Base URL 和 Key。比如页眉内容生成、旧页脚文字识别、输出一致性检查分别写三个小函数共用一份配置。这样换模型、换 Key 只改一处。任务调度方面可以用 cron 或 Windows 计划任务定时跑。Linux 下加一行0 2 * * * cd /path/to/pdf-header-footer /usr/bin/python3 scripts/process_pdf.py logs/cron.log 21结果校验方面跑完后自动发一封汇总邮件或者把校验结果写进日志。如果接了模型校验让模型输出一个 JSON 格式的结论方便程序解析{pass: true, issues: []}长期编码或 Agent 类任务可以考虑用 Coding Plan 把脚本开发、调试、迭代串起来地址是 https://taotoken.net/api/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。如果你只是想先验证模型输出效果用模型对话页更快https://taotoken.net/api/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 。最后说一个实用技巧把templates/footer_config.json纳入版本管理每次调整参数都提交一次。批量任务出问题时回滚配置比回滚代码快得多。另外input 和 output 目录不要提交用.gitignore排除避免仓库里塞满 PDF。整套流程跑下来从单文件脚本到多文件流水线核心就是把参数模板化、把 Key 统一化、把校验自动化。你按上面的目录结构和配置先跑通一个样例再逐步加文件基本不会翻车。