Batch Bank Material Scanner → Per-Person Word Documents
Trigger Conditions
- Client (law firm / bank / collection agency) sends a batch archive (encrypted RAR/ZIP) or a large docx (61MB+, full of scanned images)
- Requirement: "Extract the ID cards and card applications, place each customer's materials into their own Word document"
- Requirement: "Per-person Word = that customer's ID front + corresponding back + corresponding bank application materials – not a single item missing"
- User emphasizes: "Go through it from the start" – high quality required; recognition errors = client loss
User's Iron Rules (2026-08-19 user)
- Each person's Word must include: ID front + corresponding back (national emblem side) + corresponding application materials (application form) – not a single image missed
- File name clearly marked:
Name-ID+CardApplication.docx - Pending manual review: Unrecognized/unmatched items go into a separate
待人工核对/folder, not mixed with normal results - User requires statistics: time, number of API calls, tokens, cost from recognition start to completion – client can reuse as pricing basis
Complete Workflow (5 Steps)
Step 1 Unpack
# For encrypted RAR, use 7z (local path: C:\Program Files\7-Zip\7z.exe)
"C:/Program Files/7-Zip/7z.exe" x -p"password" input.rar -y
# Ask user for client-provided password; 7z can handle RAR5
- Common case: after unzipping RAR, you get one huge docx (e.g.,
影像材料2.docx61MB) containing no text – only images
Step 2 Extract Images from docx
import zipfile
with zipfile.ZipFile('影像材料2.docx') as z:
media = [f for f in z.namelist() if f.startswith('word/media/') and f.endswith('.png')]
for f in media: z.extract(f, out_dir)
- A docx is essentially a zip; images are under
word/media/named image1.png ~ imageN.png - Image number = original scan order – used later for grouping
Step 3 Visual Recognition & Classification (⭐ Core: Model Selection)
NEVER use GLM-4V-Flash for key document recognition! (This session tested: 11/18 ID card backs hallucinated as "front" + fabricated names)
Use Doubao Seed 2.0 Pro (doubao-seed-2-0-pro-260215, VLM, accurate):
payload = {
"model": "doubao-seed-2-0-pro-260215",
"messages": [{"role": "user", "content": [
{"type": "text", "text": "This is a bank customer material scan. Determine the category and extract key info:\nCategory can only be: ID front, ID back, card application, credit card application, other material\nFront → extract Name + ID number; Back → extract issuing authority; Application → extract applicant name\nOutput format:\nCategory: XXX\nName: XXX (write 'none' if none)\nID number: XXX\nIssuing authority: XXX"},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}
]}],
"max_tokens": 200
}
# base_url: https://ark.cn-beijing.volces.com/api/v3/chat/completions
# KEY: ARK_API_KEY from .env
- Wait 1s between images to avoid rate limiting; save progress JSON every 15 images (resumable); retry 3 times (429/timeout wait 5-8s)
- 136 images ≈ 35-40 minutes (17s/image) – run in background + notify_on_complete
Step 4 Group by Person (Front defines person → match back → match application)
- Front name defines person: Extract name from each "ID front" → create person (each person has fronts/backs/apps lists)
- Back matching (by issuing authority region ↔ front ID first 6 digits region code):
- Maintain region-code-to-police-station mapping table (450122=Wuming, 452128=Wuming, 450126=Binyang... mainly Guangxi when client is from Guangxi bank)
- Match success → assign to person; city-level issuing authorities (Nanning Public Security Bureau / Liuzhou Public Security Bureau) cannot uniquely match → use Doubao paired comparison (send both front and back in one image asking "Are they the same person?") or send to manual review
- Application matching (by name): direct match + fuzzy pinyin match (pypinyin):
lazy_pinyin(name): if first 2 characters are homophones, match (e.g., 黄祉玲→黄令, 唐咸梅→唐凤梅, 姚贤明→姚先明)- Handwritten application names have large recognition deviation; fuzzy matching is key to recovery
- ⚠️ Do NOT use "adjacent original order" to match front & back – when multiple people interleaved, it will wrongly assign backs (lesson from this session: sequential matching gave 44 > actual 34)
Step 5 Generate Per-Person Word (python-docx)
from docx import Document
from docx.shared import Cm
doc = Document()
# Title (centered, bold): {Name} - Bank Materials
# [ID Front] → image width 8cm, add_picture per image + blank line
# [ID Back] → same
# [Bank Application Materials] → image width 12cm
doc.save(f"{out_dir}/{name}-ID+CardApplication.docx")
- File name:
{Name}-ID+CardApplication.docx - Unmatched backs/applications → copy into
待人工核对/subdirectory
Cost Statistics (user required, include in delivery)
# Accumulate from API response usage: prompt_tokens/completion_tokens
# Estimate: high-res scan ≈ 2000 input tokens, 200 output tokens per image
# Doubao Seed 2.0 Pro vision: input ≈ 0.008 RMB per 1000 tokens → 136 images full cost ≈ 2-5 RMB
# Time: from recognition start to Word completion
Delivery statistics table: number of API calls, input/output tokens, estimated cost, total time (this session: 136 images ≈ 180 calls, 450k tokens, 3-5 RMB, 35 min recognition + few minutes grouping)
Critical Pitfalls
- GLM-4V-Flash Hallucination (most painful lesson in this session): If it can't recognize, it "makes up" – turning a back into a front and fabricating a name. Must use Doubao for key recognition
- docx Image Extraction: Use zipfile to read
word/media/directly; do NOT use python-docx's inline_shapes (slow and incomplete for large files) - PIL Image Enhancement: RGBA mode must first convert('RGB') before applying Contrast/Sharpness/autocontrast; for ID cards rotated 90° (h > w*1.5), generate both ±90° versions and recognize each
- Region Code Mapping Table: For Guangxi client, manually maintain county→police bureau mapping (4501=Nanning, 4502=Liuzhou...); for out-of-province clients, use first 6 ID digits to look up administrative divisions
- Doubao is slow: 17s/image – batch must run in background + resumable; don't wait up front
- Unclear scans: First enhance with PIL, then re-recognize; only if still unclear, send to manual review
Related Skills
- Vision model matrix / cost discipline: see model-cost-discipline (user-owned, reference only)
- Local OCR: deepseek-ocr (Ollama) – good for full-page text OCR, not for document classification
About the Author
九品锦锂e | Encapsulating hard-learned lessons into ready-to-use skills, not textbooks.
This skill is my personal version – every pitfall documented was actually stepped in. If you get stuck, want advanced tips, or have another scenario you'd like me to turn into a skill, just add me on WeChat. Use the remark "SkillHub" for priority approval:

Scan to add the author on WeChat · Remark "SkillHub" for priority