Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Extract text from PDFs and scanned documents. Use web_extract for remote URLs, pymupdf for local text-based PDFs, marker-pdf for OCR/scanned docs. For DOCX use python-docx, for PPTX see the powerpoint skill.
.claude/skills/graniet-ocr-and-documents/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-17 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 25% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 21% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 49% | 0% |
This skill is repo-local and stays inactive until explicitly activated.
When the original instructions refer to legacy tool names, use these Kheish mappings:
terminal => bashweb_extract => web_fetch, plus web_search when discovery is neededsearch_files => grep_search and glob_searchbrowser_* tools require a browser-capable surfaced tool or MCP; if none is available, use the closest available surface and say so explicitlyWhen the instructions mention local helper files, resolve them from ${KHEISH_SKILL_DIR}.
For DOCX: use python-docx (parses actual document structure, far better than OCR). For PPTX: see the powerpoint skill (uses python-pptx with full slide/notes support). This skill covers PDFs and scanned documents.
If the document has a URL, always try web_extract first:
web_extract(urls=["https://arxiv.org/pdf/2402.03300"])
web_extract(urls=["https://example.com/report.pdf"])This handles PDF-to-markdown conversion via Firecrawl with no local dependencies.
Only use local extraction when: the file is local, web_extract fails, or you need batch processing.
| Feature | pymupdf (~25MB) | marker-pdf (~3-5GB) | |---------|-----------------|---------------------| | Text-based PDF | ✅ | ✅ | | Scanned PDF (OCR) | ❌ | ✅ (90+ languages) | | Tables | ✅ (basic) | ✅ (high accuracy) | | Equations / LaTeX | ❌ | ✅ | | Code blocks | ❌ | ✅ | | Forms | ❌ | ✅ | | Headers/footers removal | ❌ | ✅ | | Reading order detection | ❌ | ✅ | | Images extraction | ✅ (embedded) | ✅ (with context) | | Images → text (OCR) | ❌ | ✅ | | EPUB | ✅ | ✅ | | Markdown output | ✅ (via pymupdf4llm) | ✅ (native, higher quality) | | Install size | ~25MB | ~3-5GB (PyTorch + models) | | Speed | Instant | ~1-14s/page (CPU), ~0.2s/page (GPU) |
Decision: Use pymupdf unless you need OCR, equations, forms, or complex layout analysis.
If the user needs marker capabilities but the system lacks ~5GB free disk: > "This document needs OCR/advanced extraction (marker-pdf), which requires ~5GB for PyTorch and models. Your system has X]GB free. Options: free up space, provide a URL so I can use web_extract, or I can try pymupdf which works for text-based PDFs but not scanned documents or equations."
bashpip install pymupdf pymupdf4llm
Via helper script:
bashpython scripts/extract_pymupdf.py document.pdf # Plain text python scripts/extract_pymupdf.py document.pdf --markdown # Markdown python scripts/extract_pymupdf.py document.pdf --tables # Tables python scripts/extract_pymupdf.py document.pdf --images out/ # Extract images python scripts/extract_pymupdf.py document.pdf --metadata # Title, author, pages python scripts/extract_pymupdf.py document.pdf --pages 0-4 # Specific pages
Inline:
bashpython3 -c " import pymupdf doc = pymupdf.open('document.pdf') for page in doc: print(page.get_text()) "
bash# Check disk space first python scripts/extract_marker.py --check pip install marker-pdf
Via helper script:
bashpython scripts/extract_marker.py document.pdf # Markdown python scripts/extract_marker.py document.pdf --json # JSON with metadata python scripts/extract_marker.py document.pdf --output_dir out/ # Save images python scripts/extract_marker.py scanned.pdf # Scanned PDF (OCR) python scripts/extract_marker.py document.pdf --use_llm # LLM-boosted accuracy
CLI (installed with marker-pdf):
bashmarker_single document.pdf --output_dir ./output marker /path/to/folder --workers 4 # Batch
# Abstract only (fast)
web_extract(urls=["https://arxiv.org/abs/2402.03300"])
# Full paper
web_extract(urls=["https://arxiv.org/pdf/2402.03300"])
# Search
web_search(query="arxiv GRPO reinforcement learning 2026")pymupdf handles these natively — use execute_code or inline Python:
python# Split: extract pages 1-5 to a new PDF import pymupdf doc = pymupdf.open("report.pdf") new = pymupdf.open() for i in range(5): new.insert_pdf(doc, from_page=i, to_page=i) new.save("pages_1-5.pdf")
python# Merge multiple PDFs import pymupdf result = pymupdf.open() for path in ["a.pdf", "b.pdf", "c.pdf"]: result.insert_pdf(pymupdf.open(path)) result.save("merged.pdf")
python# Search for text across all pages import pymupdf doc = pymupdf.open("report.pdf") for i, page in enumerate(doc): results = page.search_for("revenue") if results: print(f"Page {i+1}: {len(results)} match(es)") print(page.get_text("text"))
No extra dependencies needed — pymupdf covers split, merge, search, and text extraction in one package.
web_extract is always first choice for URLs--help for full usage~/.cache/huggingface/ on first usepip install python-docx (better than OCR — parses actual structure)powerpoint skill (uses python-pptx)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-17 | fail→pass | 9,723 | 1,843 | -81% | 1 | 1 | 0% | 1,488 | 1,932 | +30% | 0 | 0 | — |
case-01 | fail→fail | 17,967 | 14,186 | -21% | 1 | 1 | 0% | 3,296 | 2,122 | -36% | 0 | 0 | — |
case-02 | fail→fail | 6,287 | 6,069 | -3% | 1 | 1 | 0% | 1,215 | 1,966 | +62% | 0 | 0 | — |
case-03 | fail→fail | 7,415 | 7,018 | -5% | 1 | 1 | 0% | 1,486 | 2,015 | +36% | 0 | 0 | — |
case-04 | pass→pass | 8,151 | 3,410 | -58% | 1 | 1 | 0% | 1,478 | 2,272 | +54% | 0 | 0 | — |
case-18 | pass→pass | 5,924 | 2,328 | -61% | 1 | 1 | 0% | 1,143 | 2,076 | +82% | 0 | 0 | — |
case-05 | pass→pass | 6,505 | 2,928 | -55% | 1 | 1 | 0% | 1,279 | 2,185 | +71% | 0 | 0 | — |
case-06 | fail→pass | 8,593 | 1,995 | -77% | 1 | 1 | 0% | 1,432 | 1,933 | +35% | 0 | 0 | — |
case-07 | pass→pass | 6,801 | 3,568 | -48% | 1 | 1 | 0% | 1,243 | 2,314 | +86% | 0 | 0 | — |
case-08 | pass→pass | 6,280 | 2,411 | -62% | 1 | 1 | 0% | 1,251 | 2,079 | +66% | 0 | 0 | — |
case-09 | pass→pass | 7,801 | 3,061 | -61% | 1 | 1 | 0% | 1,533 | 2,204 | +44% | 0 | 0 | — |
case-10 | pass→pass | 12,123 | 3,741 | -69% | 1 | 1 | 0% | 2,015 | 2,268 | +13% | 0 | 0 | — |
case-11 | fail→pass | 13,953 | 7,920 | -43% | 1 | 1 | 0% | 2,468 | 3,082 | +25% | 0 | 0 | — |
case-12 | fail→pass | 9,397 | 3,026 | -68% | 1 | 1 | 0% | 1,797 | 2,172 | +21% | 0 | 0 | — |
case-13 | fail→pass | 8,328 | 3,962 | -52% | 1 | 1 | 0% | 1,611 | 2,407 | +49% | 0 | 0 | — |
case-14 | pass→pass | 10,180 | 5,046 | -50% | 1 | 1 | 0% | 1,772 | 2,542 | +43% | 0 | 0 | — |
case-15 | fail→pass | 7,048 | 2,040 | -71% | 1 | 1 | 0% | 1,313 | 1,996 | +52% | 0 | 0 | — |
case-16 | fail→pass | 7,329 | 2,600 | -65% | 1 | 1 | 0% | 1,374 | 2,125 | +55% | 0 | 0 | — |
case-19 | fail→pass | 12,136 | 5,473 | -55% | 1 | 1 | 0% | 2,126 | 2,627 | +24% | 0 | 0 | — |
case-20 | pass→pass | 4,927 | 1,419 | -71% | 1 | 1 | 0% | 777 | 1,870 | +141% | 0 | 0 | — |
case-21 | pass→pass | 4,445 | 21,249 | +378% | 1 | 1 | 0% | 756 | 1,946 | +157% | 0 | 0 | — |
case-22 | fail→pass | 11,434 | 1,802 | -84% | 1 | 1 | 0% | 2,079 | 1,900 | -9% | 0 | 0 | — |
case-23 | fail→pass | 7,417 | 1,691 | -77% | 1 | 1 | 0% | 1,331 | 1,918 | +44% | 0 | 0 | — |
case-24 | fail→pass | 6,912 | 1,955 | -72% | 1 | 1 | 0% | 1,226 | 1,974 | +61% | 0 | 0 | — |
case-25 | fail→pass | 8,017 | 2,659 | -67% | 1 | 1 | 0% | 1,324 | 2,104 | +59% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted, and 22 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +48 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.