那個條款
資料夾裡有 200 份 PDF。今早從法務部門過來的。裡面某個地方藏著一個條款,決定你的公司要付 $340,000 還是 $0。你必須在今天下班前找到它。
你打開第一份 PDF。47 頁。目錄是圖片,不是文字。在第 23 頁的修正案之後,條款編號從第 1 條重新開始。你搜尋「賠償」,在 9 頁裡找到 14 個結果,每個 context 都微妙不同。你正在看第一份,還有一百九十九份。
這就是 PDF 處理的現實。PDF 是容器,不是文件。它把文字存成定位字元,表格存成隱形格線,圖片存成嵌入式二進位。沒有 DOM,沒有查詢語言。PDF 是一張碰巧存在硬碟上的印刷品。它抵抗所有在其他格式上有效的自動化手段。
手動處理 PDF,就像為了找一句話而讀完圖書館裡每一本書。你需要的是一份會自己建立的索引。
這篇文章就是建那份索引。一條終端 pipeline,讀取目錄裡的 PDF,逐一餵給 AI agent,擷取結構化資料,按類型分類文件,標記重要條款,產出帶頁碼引用的摘要報告。整條 pipeline 是一個 shell script。跑完只要幾分鐘,不是幾天。
你會做出什麼
一條三階段的 CLI pipeline:
- 擷取 -- 把每份 PDF 餵給 Claude Code,它原生讀取文件,把文字、表格、關鍵條款和 metadata 擷取成結構化 JSON。
- 分類 -- Agent 把每份文件歸類(合約、發票、法規遵循文件、修正案、往來函件),並標記關鍵條目。
- 報告 -- 摘要報告彙整所有文件的發現,附上頁碼引用回源 PDF。
輸出是一個目錄,裡面有每份 PDF 的 JSON 擷取檔、一份分類索引,以及一份可以直接交給法務、合規部門或主管的 markdown 報告。
前置需求
- Claude Code 已安裝並完成認證。設定方式請看 Claude Code 第一個小時。
- 一個裝滿 PDF 的目錄。 Pipeline 適用於任何數量,範例以 200 份為基準。
- 基本終端操作能力。 你會跑 shell script 和閱讀 JSON 輸出。
mkdir -p ~/pdf-pipeline/source ~/pdf-pipeline/output
# 把你的 PDF 複製或 symlink 到 ~/pdf-pipeline/source/
為什麼 PDF 會搞壞每條 Pipeline
在建解決方案之前,值得了解為什麼 PDF 特別難搞。這不是學術討論 -- 它決定了 pipeline 的設計。
文字擷取會靜默失敗。 pdftotext 之類的工具擷取字元序列,但會丟失表格結構、標題階層和閱讀順序。雙欄排版出來是交錯的亂碼。表格變成一串沒有欄位對應的數字。
OCR 會引入雜訊。 掃描的 PDF 需要光學字元辨識。法律文件的 OCR 準確率通常在 95-98%。聽起來不錯,直到你算出一份 50 頁合約大約有 15,000 個字。97% 準確率代表 450 個錯字 -- 足以損壞金額、日期和當事人名稱。
結構是隱式的。 Word 文件有標題樣式、表格物件和編號清單元素。PDF 沒有。「第 4.2 條」只是以某個字型大小渲染的文字,沒有 metadata 說「這是子章節」。擷取文件層級結構需要理解視覺排版,而不是解析資料結構。
傳統工具鏈需要逐格式寫程式。 用 PyPDF2、Tabula、Tesseract 和 spaCy 建 PDF pipeline,代表你要寫格式專用的擷取邏輯、表格偵測啟發式和實體辨識規則。當文件格式改變 -- 不同的合約範本、不同的表格排版 -- 程式就壞了。
AI 改變了這個等式。Claude 原生讀取 PDF。它像人一樣看渲染後的頁面:文字有 context,表格是格線,標題是結構元素。文字型 PDF 不需要 OCR,不需要表格偵測演算法。它直接讀頁面。
第一步:設定 CLAUDE.md 文件擷取規則
基礎是一份告訴 agent 如何處理文件的 CLAUDE.md。在你的 pipeline 目錄建立這個檔案:
# PDF 文件處理規則
## 擷取標準
- 完整讀取每份 PDF,不要跳頁。
- 對每份文件擷取:
- 標題(從封面或首個標題取得)
- 文件類型(合約、發票、修正案、法規遵循、往來函件、報告)
- 日期(生效日、簽署日或發行日)
- 當事人(文件中所有具名的當事方)
- 關鍵條款(賠償、責任限制、終止、付款條件、保密、準據法)
- 表格(保留結構,存為物件陣列)
- 金額(每個金額及其 context)
- 期限與日期(每個提及的日期及其 context)
## 分類規則
1. 讀完整份文件再分類。不要只看檔名或副檔名。
2. 如果文件包含修正條文,即使基底是合約,也分類為「修正案」。
3. 標記任何修改標準責任、賠償或付款條件的條款。
4. 標記任何超過 $100,000 的金額,附帶 context。
## 輸出格式
- 每份 PDF 一個 JSON 檔,命名為 {原始檔名}.json
- 所有金額以分為單位(整數),附幣別代碼
- 所有日期為 ISO 8601 格式
- 每個擷取元素附頁碼引用
## 安全規則
- 絕不修改源 PDF
- 記錄每個處理過的檔案,附時間戳和狀態
這份設定只要寫一次。每次執行都遵循同樣的標準,不管你處理的是 10 份還是 10,000 份文件。
第二步:擷取 Script
這是單檔擷取 script。它遍歷源目錄中的每份 PDF,餵給 Claude Code 加上擷取指令,然後儲存結構化 JSON 輸出。
#!/usr/bin/env bash
set -euo pipefail
# === 設定 ===
SOURCE_DIR="${1:?Usage: pdf-extract.sh <source-dir>}"
OUTPUT_DIR="${SOURCE_DIR}/../output"
REPORT_DIR="${SOURCE_DIR}/../reports"
LOG_FILE="${OUTPUT_DIR}/processing.log"
mkdir -p "$OUTPUT_DIR" "$REPORT_DIR"
echo "PDF Processing started at $(date -Iseconds)" > "$LOG_FILE"
# === 計算檔案數 ===
PDF_COUNT=$(find "$SOURCE_DIR" -maxdepth 1 -name "*.pdf" -type f | wc -l | tr -d ' ')
echo "Found $PDF_COUNT PDF files in $SOURCE_DIR"
if [[ "$PDF_COUNT" -eq 0 ]]; then
echo "No PDF files found."
exit 0
fi
PROCESSED=0
FAILED=0
# === 逐一處理 PDF ===
for pdf_file in "$SOURCE_DIR"/*.pdf; do
filename=$(basename "$pdf_file" .pdf)
output_file="$OUTPUT_DIR/${filename}.json"
# 跳過已處理的檔案
if [[ -f "$output_file" ]]; then
echo " skip: $filename (already processed)"
((PROCESSED++))
continue
fi
echo " processing: $filename..."
if claude -p "Read this PDF file completely: $pdf_file
Extract the following into a JSON object:
{
\"filename\": \"original filename\",
\"document_type\": \"contract|invoice|amendment|compliance|correspondence|report|other\",
\"title\": \"document title\",
\"date\": \"ISO 8601 date\",
\"parties\": [\"Party A\", \"Party B\"],
\"page_count\": number,
\"summary\": \"2-3 sentence summary\",
\"key_clauses\": [
{
\"type\": \"indemnification|liability|termination|payment|confidentiality|governing_law|other\",
\"text\": \"exact clause text\",
\"page\": number,
\"flag\": \"normal|attention|critical\"
}
],
\"tables\": [
{
\"description\": \"what the table contains\",
\"page\": number,
\"headers\": [\"col1\", \"col2\"],
\"rows\": [[\"val1\", \"val2\"]]
}
],
\"monetary_values\": [
{
\"amount_cents\": number,
\"currency\": \"USD\",
\"context\": \"what this amount refers to\",
\"page\": number
}
],
\"dates_mentioned\": [
{
\"date\": \"ISO 8601\",
\"context\": \"what this date refers to\",
\"page\": number
}
],
\"flags\": [\"list of anything unusual or requiring attention\"]
}
Output ONLY valid JSON. No markdown fences. No explanation." \
--output-format json > "$output_file" 2>/dev/null; then
echo "$(date -Iseconds) OK $filename" >> "$LOG_FILE"
((PROCESSED++))
else
echo " FAILED: $filename"
echo "$(date -Iseconds) FAIL $filename" >> "$LOG_FILE"
rm -f "$output_file"
((FAILED++))
fi
done
echo ""
echo "=== Extraction Complete ==="
echo "Processed: $PROCESSED"
echo "Failed: $FAILED"
echo "Output: $OUTPUT_DIR/"
echo "Log: $LOG_FILE"
存為 pdf-extract.sh,設定可執行:
chmod +x pdf-extract.sh
./pdf-extract.sh ~/pdf-pipeline/source
關鍵設計決策:
- 跳過已處理的檔案。 如果 script 在處理 200 份 PDF 途中失敗,重跑就好。它會從斷點繼續。
- 每份 PDF 一個 JSON 檔。 每個擷取是獨立的。你可以個別檢查、重新處理或丟棄,不影響其他結果。
- 結構化日誌。 處理日誌精確告訴你哪些檔案成功、哪些失敗,附帶時間戳。
第三步:分類 Script
擷取給你每份文件的原始資料。分類給你全貌:這批文件裡有哪些類型,哪些需要注意。
#!/usr/bin/env bash
set -euo pipefail
OUTPUT_DIR="${1:?Usage: pdf-classify.sh <output-dir>}"
REPORT_DIR="${OUTPUT_DIR}/../reports"
INDEX_FILE="$REPORT_DIR/classification_index.json"
mkdir -p "$REPORT_DIR"
JSON_COUNT=$(find "$OUTPUT_DIR" -maxdepth 1 -name "*.json" ! -name "processing.log" -type f | wc -l | tr -d ' ')
echo "Classifying $JSON_COUNT extracted documents..."
# 把所有擷取的 JSON 合併成單一陣列
echo "[" > /tmp/all_extractions.json
first=true
for json_file in "$OUTPUT_DIR"/*.json; do
[[ "$(basename "$json_file")" == "processing.log" ]] && continue
if $first; then
first=false
else
echo "," >> /tmp/all_extractions.json
fi
cat "$json_file" >> /tmp/all_extractions.json
done
echo "]" >> /tmp/all_extractions.json
claude -p "You are a document analyst. Here is a JSON array of extracted PDF data:
$(cat /tmp/all_extractions.json)
Create a classification index as a JSON object:
{
\"total_documents\": number,
\"by_type\": {
\"contract\": {\"count\": number, \"files\": [\"filename1\", \"filename2\"]},
\"invoice\": {\"count\": number, \"files\": [...]},
...
},
\"flagged_documents\": [
{
\"filename\": \"name\",
\"reason\": \"why flagged\",
\"severity\": \"attention|critical\",
\"details\": \"specific clause or amount\"
}
],
\"total_monetary_value_cents\": number,
\"date_range\": {\"earliest\": \"ISO date\", \"latest\": \"ISO date\"},
\"parties_involved\": [\"unique list of all parties\"]
}
Output ONLY valid JSON." --output-format json > "$INDEX_FILE"
echo "Classification index saved to $INDEX_FILE"
# 顯示摘要
echo ""
echo "=== Document Classification ==="
python3 -c "
import json
idx = json.load(open('$INDEX_FILE'))
print(f\"Total documents: {idx['total_documents']}\")
print(f\"\\nBy type:\")
for dtype, info in idx.get('by_type', {}).items():
print(f\" {dtype}: {info['count']}\")
flagged = idx.get('flagged_documents', [])
if flagged:
print(f\"\\nFlagged documents: {len(flagged)}\")
for f in flagged:
print(f\" [{f['severity'].upper()}] {f['filename']}: {f['reason']}\")
"
對第二步的輸出跑這個 script,你會得到一份分類索引:87 份合約、45 份發票、32 份修正案、19 份法規遵循文件、17 份往來函件。最關鍵的是:6 份被標記的文件,其中 2 份為 critical。
第四步:摘要報告
最後階段產出一份人類可讀的報告,附帶頁碼引用。
#!/usr/bin/env bash
set -euo pipefail
OUTPUT_DIR="${1:?Usage: pdf-report.sh <output-dir>}"
REPORT_DIR="${OUTPUT_DIR}/../reports"
INDEX_FILE="$REPORT_DIR/classification_index.json"
REPORT_FILE="$REPORT_DIR/summary_report.md"
if [[ ! -f "$INDEX_FILE" ]]; then
echo "Error: Run pdf-classify.sh first."
exit 1
fi
echo "Generating summary report..."
claude -p "You are a senior document analyst preparing a summary report for legal review.
Classification index:
$(cat "$INDEX_FILE")
Full extraction data:
$(cat /tmp/all_extractions.json)
Generate a comprehensive markdown report with these sections:
# Document Collection Summary Report
## Executive Summary
- Total documents, types breakdown, date range, parties involved
- Total monetary value across all documents
- Number of flagged items requiring attention
## Critical Flags
For each flagged document:
- Document name and type
- What was flagged and why
- Exact clause text with page reference
- Recommended action
## Document Type Breakdown
For each document type:
- Count and list of files
- Key terms and dates
- Notable variations from standard language
## Monetary Summary
- All monetary values grouped by document
- Total obligations by party
- Payment timelines
## Key Dates and Deadlines
- Chronological list of all deadlines
- Documents expiring within 90 days
- Renewal dates
## Appendix: Document Index
Table with: filename, type, date, parties, page count, flags
Every claim must include the source filename and page number.
Format as clean markdown with tables where appropriate." > "$REPORT_FILE"
echo "Report saved to $REPORT_FILE"
echo ""
head -30 "$REPORT_FILE"
輸出是一份 markdown 報告,律師、合規人員或主管可以直接閱讀,不需要打開任何一份 PDF。每個發現都引用了具體的文件和頁碼。報告回答了你一開始的問題:那個重要的條款在哪裡,它寫了什麼。
完整 Pipeline:一行指令
把三個階段包成一個總控 script:
#!/usr/bin/env bash
set -euo pipefail
SOURCE_DIR="${1:?Usage: pdf-pipeline.sh <source-dir>}"
echo "=== PDF Processing Pipeline ==="
echo "Source: $SOURCE_DIR"
echo ""
echo "--- Stage 1: Extraction ---"
./pdf-extract.sh "$SOURCE_DIR"
echo ""
echo "--- Stage 2: Classification ---"
./pdf-classify.sh "$SOURCE_DIR/../output"
echo ""
echo "--- Stage 3: Report Generation ---"
./pdf-report.sh "$SOURCE_DIR/../output"
echo ""
echo "=== Pipeline Complete ==="
echo "Extractions: $(ls "$SOURCE_DIR/../output/"*.json 2>/dev/null | wc -l | tr -d ' ') files"
echo "Report: $SOURCE_DIR/../reports/summary_report.md"
執行:
chmod +x pdf-pipeline.sh pdf-extract.sh pdf-classify.sh pdf-report.sh
./pdf-pipeline.sh ~/pdf-pipeline/source
200 份 PDF。三個階段。一行指令。輸出目錄包含個別 JSON 擷取檔、分類索引和摘要報告。今早送你那批 PDF 的律師,午餐前就能收到結構化的答案。
用 Extended Thinking 處理複雜文件
有些 PDF 不簡單。一份 200 頁的主服務合約,附帶三份修正案和一封附函,需要 agent 同時持有矛盾的條款並判斷哪個版本有效。這就是 extended thinking 發揮價值的地方。
claude -p "Read this PDF: $pdf_file
This is a complex legal document with amendments. Use careful reasoning:
1. Identify the base agreement and all amendments
2. For each clause that was amended, determine the CURRENT governing version
3. Flag any contradictions between the base agreement and amendments
4. Identify any clauses where the amendment language is ambiguous
Think step by step before producing the final extraction." --thinking extended \
--output-format json > "$OUTPUT_DIR/${filename}.json"
--thinking extended 會分配更多推理 token。Agent 會逐步解析文件結構、追蹤修正案鏈、解決衝突,然後才產出最終輸出。每份文件成本更高,但對於決定六位數賠償責任的合約來說,這個成本微不足道。
選擇性使用 extended thinking。簡單的發票和一頁函件不需要。保留給:
- 多方合約,附帶修正案歷史
- 法規遵循文件,有交叉引用法規條文
- 含複雜表格結構的財務報表
- 任何條款引用其他條款的文件
用分割終端並行處理
循序處理 200 份 PDF 可行但慢。每份 PDF 根據長度和複雜度需要 10-30 秒。200 份文件就是 30 分鐘到一小時。
用多個擷取 process 並行處理來加速。把終端分成多個窗格,每個窗格對一個子集跑擷取 script:
# 把 PDF 分成批次
ls ~/pdf-pipeline/source/*.pdf | head -100 > /tmp/batch1.txt
ls ~/pdf-pipeline/source/*.pdf | tail -100 > /tmp/batch2.txt
# 窗格 1:
while read -r pdf; do
./pdf-extract-single.sh "$pdf"
done < /tmp/batch1.txt
# 窗格 2:
while read -r pdf; do
./pdf-extract-single.sh "$pdf"
done < /tmp/batch2.txt
兩個窗格、兩條並行串流、一半的時間。四個窗格、四分之一時間。擷取輸出是獨立的 JSON 檔,所以沒有 race condition。分類和報告階段在所有擷取完成後才執行。
分割終端排版也讓你在一個窗格監控進度的同時,在另一個窗格檢視已完成的擷取。左邊窗格繼續處理時,右邊打開一個 JSON 擷取檔。即時抽查 agent 的工作品質。
調整 Pipeline
上面的 script 是起點。以下是針對特定用途的常見修改:
合約審閱
在 CLAUDE.md 加入條款專用擷取規則:
## 合約專用規則
- 完整擷取賠償條款全文
- 標記任何低於 $1,000,000 的責任限制
- 識別準據法和爭議解決機制
- 記錄任何非標準的終止條件
- 擷取所有定義名詞及其定義
發票處理
把擷取焦點切換到明細項目和付款條件:
## 發票專用規則
- 擷取每個明細項目:描述、數量、單價、總額
- 識別付款條件(Net 30、Net 60 等)
- 擷取銀行帳戶和匯款指示
- 標記總額超過 $50,000 的發票
- 比對發票號碼與採購單號碼
法規遵循稽核
聚焦在法規引用和需求對照:
## 法規遵循規則
- 識別所有法規引用(CFR、GDPR 條文、SOX 章節)
- 將每份文件對應到其所滿足的具體合規要求
- 標記已過期或缺少必要簽名的文件
- 擷取認證日期和到期日
- 記錄任何缺口:被引用但未有佐證的要求
什麼時候該用(什麼時候不該)
這條 pipeline 適合:
- 盡職調查文件審閱(併購、投資、供應商評估)
- 合約組合分析(我們的總義務是多少?)
- 法規遵循稽核準備(每個要求都有對應佐證嗎?)
- 發票批次處理(擷取明細項目給會計系統)
- 法律調查(在文件集合中找到特定條款)
以下情況用專門工具:
- 你需要通過認證的 OCR 輸出來送交監管機構(用 ABBYY 或類似工具)
- PDF 主要是圖片,幾乎沒有文字(先用專門的 OCR pipeline)
- 你需要即時處理進入的文件(建構事件驅動系統)
- 文件數量超過 1,000 且處理時間很重要(加入佇列基礎設施)
最佳範圍是 10-500 份文件的臨時批次處理。大到手動審閱不切實際,小到終端 pipeline 就能搞定,不需要額外基礎設施。
關鍵重點
PDF 抗拒自動化,因為它們是容器,不是資料。傳統工具鏈需要逐格式撰寫擷取邏輯,文件範本一改就壞。AI agent 讀取渲染後的頁面,不需要格式專用程式碼就能擷取結構化資料。
Pipeline 模式:
- 擷取 -- 一份 PDF 進去,一份 JSON 出來,每個發現都附頁碼引用
- 分類 -- 彙整擷取結果成分類索引,附嚴重程度標記
- 報告 -- 產出人類可讀的摘要,附帶源文件引用
核心設計原則:
- 冪等擷取。 跳過已處理的檔案。失敗後安全重跑。
- Structured output。 每份文件一個 JSON。機器可讀。可與其他工具組合。
- 複雜文件用 extended thinking。 簡單文件快速擷取,複雜文件仔細推理。
- 並行處理。 獨立的輸出代表獨立的 process。用終端窗格擴展。
兩百份 PDF 今早到的。午餐之前,你手上有一份報告,精確告訴你哪個條款重要、在哪一頁、在哪份文件裡。圖書館把自己索引好了。
Ready to streamline your terminal workflow?
Multi-terminal drag-and-drop layout, workspace Git sync, built-in AI integration, AST code analysis — all in one app.