Python CLI 工具
Python CLI 工具
标题为“Python CLI 工具”的章节nextpdf 命令用于在终端执行 PDF 提取。将它指向一个 NextPDF Connect endpoint 并传入 PDF 后,你就能获得结构化输出:带引用的文本、表格、完整的语义抽象语法树(AST),或元数据摘要;这些结果可以输出到标准输出(stdout),也可以写入文件。
命令结构
标题为“命令结构”的章节nextpdf 命令是一个 Click 命令组。连接和会话选项——--base-url、--api-key、--log-level、--output/-o,以及 --strict——定义在组级别,因此必须放在子命令之前。子命令及其自身选项(例如 --format 或 --page)则放在之后:
nextpdf [GROUP OPTIONS] COMMAND [SUBCOMMAND] PDF_PATH [COMMAND OPTIONS]如果把组选项放在子命令之后,命令会失败。例如,nextpdf info document.pdf --base-url ... 会报出 Error: No such option: --base-url 并以退出码 2 退出,因为 Click 解析到 --base-url 时已经进入 info 子命令,而该子命令没有定义这个选项。
避免这个顺序陷阱最直接的方法,是通过环境变量提供凭据(见每个 shell 设置一次一节)。下面的示例会先显式写出标志,让正确顺序一目了然。
快速参考
标题为“快速参考”的章节提取 JSON 格式的文本:
nextpdf --base-url http://localhost:8080 --api-key "$NEXTPDF_API_KEY" extract text document.pdf提取逗号分隔值(CSV)格式的表格:
nextpdf --base-url http://localhost:8080 --api-key "$NEXTPDF_API_KEY" extract tables invoice.pdf --format csv查看文件元数据和结构:
nextpdf --base-url http://localhost:8080 --api-key "$NEXTPDF_API_KEY" info document.pdf获取完整语义 AST:
nextpdf --base-url http://localhost:8080 --api-key "$NEXTPDF_API_KEY" ast document.pdf打印已安装的 SDK 版本,无需连接服务器:
nextpdf versionversion 命令是唯一既不需要 --base-url、也不需要 --api-key 的命令。其他所有命令都会连接服务器,并且两者都必须提供。
每个示例都从 NEXTPDF_API_KEY 环境变量读取 API 密钥,而不是直接写在命令行中。请将密钥视为机密。直接写在命令行中的密钥会进入 shell 历史记录,也可能出现在同一主机上其他用户可见的进程列表(ps)中。
命令与选项
标题为“命令与选项”的章节组选项
标题为“组选项”的章节这些选项必须放在子命令之前。每个连接选项也可以从环境变量读取;变量已设置时,可以省略对应标志。
| 选项 | 环境变量 | 默认值 | 用途 |
|---|---|---|---|
--base-url | NEXTPDF_BASE_URL | (必填) | NextPDF Connect 服务器 URL。 |
--api-key | NEXTPDF_API_KEY | (必填) | 用于 bearer 身份验证的 API 密钥。 |
--log-level | — | warning | 日志详细级别:debug、info、warning 或 error。日志会输出到标准错误(stderr)。 |
--output、-o | — | 标准输出(stdout) | 将命令输出写入文件,而非 stdout。 |
--strict | — | 关闭 | 保留供未来使用。这个标志目前可以解析,但不会改变任何行为。 |
--help、-h | — | — | 显示说明并退出。 |
除了 version 之外,每个命令都必须提供 --base-url 与 --api-key 的值。如果任一项缺失——既没有标志也没有环境变量——命令会打印错误并以退出码 1 退出。
nextpdf extract text
标题为“nextpdf extract text”的章节提取带引用的文本块。每个块都包含一个引用锚点(节点标识符、页面 Index(索引)、边界框和置信度分数)。
nextpdf [GROUP OPTIONS] extract text PDF_PATH [--format FORMAT] [--page N] [--headings-only]| 选项 | 值 | 默认值 | 用途 |
|---|---|---|---|
--format | json、markdown、plain | json | 输出格式。 |
--page | 整数 | 所有页面 | 只提取这个以 0 为基准的页面索引。 |
--headings-only | 标志 | 关闭 | 只提取标题节点。 |
PDF_PATH 可以是文件路径,也可以是 -,表示从 stdin 读取 PDF 字节。
nextpdf extract tables
标题为“nextpdf extract tables”的章节提取每张表格,并附带引用锚点和单元格级结构。
nextpdf [GROUP OPTIONS] extract tables PDF_PATH [--format FORMAT] [--page-start N] [--page-end N]| 选项 | 值 | 默认值 | 用途 |
|---|---|---|---|
--format | json、csv | json | 输出格式。 |
--page-start | 整数 | 第一页 | 起始页面索引(以 0 为基准)。 |
--page-end | 整数 | 最后一页 | 结束页面索引(以 0 为基准)。 |
PDF_PATH 可以是文件路径,也可以用 - 表示从 stdin 读取。
nextpdf ast
标题为“nextpdf ast”的章节以 JSON 返回完整的语义 AST:这是一棵由多种节点(标题、正文段落、表格、列表、图表)组成的层级树结构,包含边界框和文本内容。
nextpdf [GROUP OPTIONS] ast PDF_PATH [--page-start N] [--page-end N] [--token-budget N]| 选项 | 值 | 默认值 | 用途 |
|---|---|---|---|
--page-start | 整数 | 第一页 | 起始页面索引(以 0 为基准)。 |
--page-end | 整数 | 最后一页 | 结束页面索引(以 0 为基准)。 |
--token-budget | 整数 | 无上限 | 返回 AST 的大致 token 上限。 |
PDF_PATH 可以是文件路径,也可以用 - 表示从 stdin 读取。ast 命令会生成单个文件的树;它不会比较两份 PDF。关于结构化比对,请见 示例:比对两个 PDF 版本一节。
nextpdf info
标题为“nextpdf info”的章节打印单个文件的精简 JSON 摘要:schema 版本、来源哈希、页数、估算 token 数、根节点类型,以及根级子项数量。
nextpdf [GROUP OPTIONS] info PDF_PATHPDF_PATH 可以是文件路径,也可以用 - 表示从 stdin 读取。
nextpdf version
标题为“nextpdf version”的章节打印已安装的 SDK 版本(例如 nextpdf 1.1.0)并退出。这个命令不连接任何服务器,也不需要任何凭据。
nextpdf version每个 shell 设置一次
标题为“每个 shell 设置一次”的章节将连接选项设置为环境变量后,就可以省略重复的标志。这种方式也能完全避开选项顺序陷阱,因为凭据不会出现在命令行中。
export NEXTPDF_BASE_URL=http://localhost:8080export NEXTPDF_API_KEY=your-keynextpdf extract text document.pdf在 Windows PowerShell 上:
$env:NEXTPDF_BASE_URL = "http://localhost:8080"$env:NEXTPDF_API_KEY = "your-key"nextpdf extract text document.pdf建议从机密存储,或从已排除在版本控制之外的 .env 文件加载密钥。不要把生产环境密钥粘贴到共享终端会话中,也不要写进将要提交的脚本。
输出格式
标题为“输出格式”的章节你可以用 --format 为每个命令选择输出格式。text 和 tables 命令支持多种格式;ast 与 info 则一律输出 JSON。
| 命令 | 格式 | 默认值 |
|---|---|---|
extract text | json、markdown、plain | json |
extract tables | json、csv | json |
ast | json | json |
info | json | json |
当下游程序需要页面索引、置信度分数或节点标识符时,选择 JSON。需要由电子表格或表格处理管道读取表格时,选择 CSV。结果要给人阅读或交给纯文本工具处理时,选择 plain 或 markdown 文本。
解析 JSON 输出
标题为“解析 JSON 输出”的章节text 命令会输出一个由带引用块组成的 JSON 数组。每个块都包含 text、一个 citation 对象(node_id、page_index、bbox、confidence),以及一个可选的 node_type。用 --output(或重定向 stdout)把输出发送到文件,然后再解析。
这个 shell 示例使用 jq 只保留第 0 页的标题:
nextpdf --output blocks.json extract text report.pdf --format jsonjq '[.[] | select(.citation.page_index == 0 and .node_type == "heading") | .text]' blocks.json同样的数据也可以在 Python 中直接解析。CLI 会写出一个 JSON 数组,因此可以用标准库加载它,再读取其中有类型的字段:
"""Parse cited text blocks emitted by `nextpdf extract text --format json`."""
import jsonfrom pathlib import Path
def load_headings(blocks_path: Path) -> list[str]: """Return the text of every heading block on page 0.
Args: blocks_path: Path to the JSON file written by `nextpdf extract text`.
Returns: The text of each heading-type block whose citation is on page 0. """ raw = blocks_path.read_text(encoding="utf-8") blocks: list[dict[str, object]] = json.loads(raw) headings: list[str] = [] for block in blocks: citation = block["citation"] if block.get("node_type") == "heading" and citation["page_index"] == 0: headings.append(str(block["text"])) return headings
if __name__ == "__main__": for heading in load_headings(Path("blocks.json")): print(heading)当你需要经过验证且有类型的模型,而不是原始字典时,请直接调用 SDK,而不要解析 CLI 输出。请见 Python 总览一节,以了解 NextPDF 客户端及其 CitedTextBlock 返回类型。
解析 CSV 输出
标题为“解析 CSV 输出”的章节加上 --format csv 时,tables 命令会为每张表格写出一个 CSV 块。每张表格前都有一行注释 # Table N (pM),标示其以 1 为基准的表格编号与以 0 为基准的页面索引。连续表格之间以一行空行分隔。CLI 会用 Python 的 csv 模块对单元格值加引号并转义,所以包含逗号、引号或换行的值都能安全往返处理。
nextpdf --output tables.csv extract tables statement.pdf --format csv由于这个文件包含多个 CSV 块,在把每个块作为独立表格解析之前,请先按注释行切分:
"""Split multi-table CSV output from `nextpdf extract tables --format csv`."""
import csvimport iofrom pathlib import Path
def read_tables(csv_path: Path) -> list[list[list[str]]]: """Parse the multi-block CSV file into a list of tables.
Each table is a list of rows; each row is a list of cell strings. The leading `# Table N (pM)` comment row is dropped from every table.
Args: csv_path: Path to the file written by `nextpdf extract tables`.
Returns: One parsed table per `# Table` block in the file. """ text = csv_path.read_text(encoding="utf-8") tables: list[list[list[str]]] = [] current: list[str] = [] for line in text.splitlines(keepends=True): if line.startswith("# Table ") and current: tables.append(_parse_block(current)) current = [] current.append(line) if current: tables.append(_parse_block(current)) return tables
def _parse_block(lines: list[str]) -> list[list[str]]: """Parse one CSV block, discarding its leading comment row.""" reader = csv.reader(io.StringIO("".join(lines))) rows = [row for row in reader if row] return rows[1:] if rows and rows[0] and rows[0][0].startswith("# Table ") else rows
if __name__ == "__main__": for index, table in enumerate(read_tables(Path("tables.csv")), start=1): print(f"table {index}: {len(table)} rows")退出码与错误检测
标题为“退出码与错误检测”的章节CLI 使用三种退出码。在 shell 脚本中检查 $?(或在 PowerShell 中检查 $LASTEXITCODE),根据成功或失败走不同分支,并从 stderr 读取诊断消息;stderr 会与 stdout 中的数据保持分离。
| 退出码 | 意义 | 示例 |
|---|---|---|
0 | 成功。 | 命令已完成;version 已打印。 |
1 | 执行阶段错误。CLI 会把 Error: <message> 打印到 stderr。 | 找不到输入文件或它不是普通文件、stdin 为空、缺失或无效的 --base-url/--api-key、任何服务器端错误(需要授权、超出配额、未标记的 PDF、构建超时,或其他 API 失败)。 |
2 | 用法错误,由 Click 报告。 | 未知的命令或选项(包括放在子命令之后的组选项)、缺失必要参数(例如 PDF_PATH)。 |
每个服务器端失败都会以退出码 1 呈现,并在 stderr 上附带一条人类可读的消息。SDK 会抛出有类型的异常——NextPDFLicenseError(HTTP 402)、QuotaExceededError(HTTP 429)、AstNoStructTreeError(HTTP 422,未标记的 PDF)、AstBuildTimeoutError(HTTP 504),或基类 NextPDFAPIError。CLI 会在它们共同的 NextPDFError 基类之下捕获所有这些异常、打印消息,并以 1 退出。CLI 不会为每种失败类型提供不同的退出码。例如,若要在脚本中区分配额错误与授权错误,请查看 stderr 上的消息文字,或直接调用 SDK(异常类型请见 Python 总览一节)。
下面是一种将数据与诊断消息分离的脚本模式:
#!/usr/bin/env bashset -euo pipefail
# Credentials come from the environment, not the command line.: "${NEXTPDF_BASE_URL:?set NEXTPDF_BASE_URL}": "${NEXTPDF_API_KEY:?set NEXTPDF_API_KEY}"
if nextpdf --output contract.ast.json ast contract.pdf; then echo "AST written to contract.ast.json"else status=$? echo "nextpdf failed with exit code ${status}" >&2 exit "${status}"fi请注意,--output 会把数据写入指定文件,并且只把确认行 Written to <path> 打印到 stderr,因此 stdout 会保持空白。不加 --output 时,数据会发送到 stdout,你可以重定向它。
下面每个示例都只使用经过验证的命令和标志。所有示例中的凭据都来自环境变量。
示例:把发票表格提取成 CSV
标题为“示例:把发票表格提取成 CSV”的章节将整个文件夹中的发票转换为每份 PDF 对应一个 CSV 文件,供电子表格或会计管道使用:
#!/usr/bin/env bashset -euo pipefail
: "${NEXTPDF_BASE_URL:?set NEXTPDF_BASE_URL}": "${NEXTPDF_API_KEY:?set NEXTPDF_API_KEY}"
mkdir -p outfor pdf in invoices/*.pdf; do name="$(basename "${pdf}" .pdf)" nextpdf --output "out/${name}.csv" extract tables "${pdf}" --format csvdone每个 out/<name>.csv 都会为每张检测到的表格保存一个 CSV 块,且每个块前都有一行 # Table N (pM) 标题行。使用上面示范的 CSV 读取器解析这些块。
示例:构建文件大纲
标题为“示例:构建文件大纲”的章节将 --headings-only 与 markdown 格式结合使用,生成一份可以阅读或粘贴到笔记中的快速大纲:
nextpdf --output outline.md extract text whitepaper.pdf --headings-only --format markdown示例:比对两个 PDF 版本
标题为“示例:比对两个 PDF 版本”的章节CLI 的 ast 命令会返回单个文件的树;它没有 diff 子命令。结构化比对由 SDK 中的 client.ast.get_ast_diff(...),以及 Model Context Protocol(MCP)服务器中的 nextpdf_diff 工具提供。通过 SDK 执行这一比对:
"""Compare two PDF revisions structurally with the NextPDF SDK.
The API key is read from the environment, never hard-coded."""
import osfrom pathlib import Path
from nextpdf import NextPDF
def diff_revisions(original: Path, modified: Path) -> None: """Print a structural diff summary between two PDF revisions.
Args: original: Path to the earlier PDF revision. modified: Path to the later PDF revision. """ base_url = os.environ["NEXTPDF_BASE_URL"] api_key = os.environ["NEXTPDF_API_KEY"]
client = NextPDF(base_url=base_url, api_key=api_key) result = client.ast.get_ast_diff( original.read_bytes(), modified.read_bytes(), )
summary = result.summary print(f"added: {summary.added_node_count}") print(f"removed: {summary.removed_node_count}") print(f"changed: {summary.changed_node_count}") for entry in result.diff: preview = entry.text_preview or "" print(f" {entry.type:<8} {entry.node_type:<12} p{entry.page_index} {preview}")
if __name__ == "__main__": diff_revisions(Path("contract-v1.pdf"), Path("contract-v2.pdf"))若要从 AI Agent(代理)而不是脚本执行同样的比对,请注册 MCP 服务器并调用 nextpdf_diff 工具。请见 Python MCP 服务器页面。
示例:从另一个工具流式传入 PDF
标题为“示例:从另一个工具流式传入 PDF”的章节使用 - 从 stdin 读取 PDF 字节,就可以把 nextpdf 接在另一个会向自身 stdout 输出 PDF 的工具之后:
curl --silent https://example.com/report.pdf | nextpdf info -- 参数会告诉命令从 stdin 读取文件。如果没有传入任何字节,命令会报错并以 1 退出。
效能说明
标题为“效能说明”的章节CLI 会先在内存中构建每个响应,再一次性写出,因此重定向或管道输出都很直接;但输出并非渐进生成。在第一个字节到达 stdout 或 --output 文件之前,庞大的 AST 或表格集合会先完整缓冲。请按整份文件的响应来规划内存与延迟,而不要以流式为前提。
每次调用 nextpdf 都会建立全新的客户端和 HTTP 连接,因此循环处理大量文件时,会逐个文件开启并关闭一条连接。相较于服务器端的提取时间,这个连接成本通常很小,但在大规模场景下确实会成为额外开销。
- 重复使用同一个 endpoint。 把每次调用都指向同一个 NextPDF Connect 部署,让服务器能够复用已预热的缓存和连接池。除非你有意做负载均衡,否则应避免把一个批次分散到多个 endpoint。
- 限制每次调用的工作量。 用
--page、--page-start/--page-end,或--token-budget只处理你需要的页面。较小的页面范围能同时减少服务器处理时间和响应大小;--token-budget则会限制你的 agent 必须读取的 AST 量。 - 大型工作就在单一进程中批次处理。 对于大批量处理,请优先使用 Python SDK,而不是重复调用 CLI。单一长生命周期的
NextPDF(或AsyncNextPDF)客户端会在每份文件之间复用同一条池化的 HTTP 连接,省去 CLI 循环每次都要付出的进程启动和连接建立成本。Python 总览展示了客户端的生命周期,而AsyncNextPDF则支持跨多份 PDF 的并发提取。 - 让日志远离数据路径。 批次执行时,把
--log-level保持默认值。诊断日志会输出到 stderr,不会污染 stdout 数据,但把级别提高到debug会增加噪声和少许额外开销。