Python's parser zoo, replaced by one POST.
pypdf for PDFs, python-docx for Word, openpyxl for Excel, extract-msg for Outlook, pytesseract for scans — five libraries, five APIs, five sets of edge cases. txtfetch is one.
the-parser-zoo
Python has the deepest document-parsing ecosystem of any language, which is exactly the problem: covering PDF, Office, email, and scans means installing and maintaining five separate libraries, each with its own API and its own blind spot. pypdf reads digital-native PDF text cleanly but has no OCR path and struggles with multi-column layouts and dense tables. python-docx is OOXML-only — .docx works, but a .doc from 2009 throws before you get a single character back, and it has nothing at all for .pptx or .xlsx. openpyxl reads .xlsx cells directly, but formula cells need data_only=True to get the calculated value instead of the =SUM() string, and that cached value only exists if the file was last saved by Excel itself — a workbook produced by another tool can hand back None. And pytesseract isn't really an OCR library — it's a thin wrapper that shells out to a system tesseract binary you have to install and keep on PATH yourself, plus Pillow for images and pdf2image (which itself needs poppler-utils) if the scan is a PDF rather than a bare image.
| library | covers | stops at |
|---|---|---|
pypdf | Digital-native PDF text extraction | No OCR; struggles with multi-column layouts and dense tables |
python-docx | .docx paragraphs, tables, and headers | OOXML only — no legacy .doc, and nothing for .pptx or .xlsx |
openpyxl | .xlsx cell values, including shared strings | Formula cells need data_only=True plus a prior Excel save — no live recalculation; no .xls |
extract-msg | Outlook .msg header and body parsing | Only .msg — no .pst/.ost mailbox archives, and nothing outside email |
pytesseract | OCR, once paired with Pillow and a system Tesseract install | You install, update, and package the tesseract binary yourself; PDFs need pdf2image + poppler on top |
one-request
txtfetch replaces the imports, not just the parsing. One POST — multipart file or ?url= — returns { "status": "success", "extracted_text": "..." } whether the source was a pypdf-shaped PDF, a python-docx-shaped .docx, an openpyxl-shaped .xlsx, or a pytesseract-shaped scan. The official txtfetch Python SDK wraps the same call in a typed client with exceptions per error code, so a try/except replaces the format-detection branching a hand-rolled pipeline needs before it can even pick which library to call.
import os
import requests
with open("report.pdf", "rb") as f:
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
files={"file": f},
)
print(r.json()["extracted_text"]){
"status": "success",
"extracted_text": "..."
}txtfetch (Python SDK)
pip install txtfetch — one client class, typed exceptions per error code, no per-format branching.
pip install txtfetchfrom txtfetch import Txtfetch
# api_key defaults to the TXTFETCH_KEY environment variable
client = Txtfetch(api_key="tf_live_...")
# Extract from a local file (path, bytes, or a file-like object all work)
result = client.extract(file="whitepaper.pdf")
print(result.extracted_text)
print(result.metadata.content_type, result.metadata.bytes, result.metadata.ocr)
# Extract from a URL — txtfetch fetches it server-side
result = client.extract(url="https://example.com/whitepaper.docx")from-a-url
Skip the download entirely — pass a url parameter and txtfetch fetches the document server-side:
import os
import requests
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
params={"url": "https://example.com/report.pdf"},
)
print(r.json()["extracted_text"])errors
Every non-success response carries a stable error.code — match on that, not on error.message. See the full error reference for every code and HTTP status txtfetch can return.
import os
import requests
with open("report.pdf", "rb") as f:
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
files={"file": f},
)
if r.status_code == 200:
print(r.json()["extracted_text"])
elif r.status_code == 429:
code = r.json()["error"]["code"] # "rate_limited", "quota_exceeded", or "abuse_detected"
print(f"back off: {code}, retry after {r.headers['Retry-After']}s")
else:
error = r.json()["error"]
print(f"extraction failed: {error['code']} — {error['message']}")big-files-and-batches
Large uploads or slow documents are routed to an async job automatically — 202 plus a job_id to poll — and ?async=true forces that path for any request. Direct upload size ceilings by plan: Hobby 10 MB, Developer 50 MB, Scale 200 MB. Use ?url= for anything larger — server-side fetches aren't held to the upload ceiling. See async jobs & webhooks for the full lifecycle, including webhook delivery instead of polling.
import os
import time
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"}
submit = requests.post(
"https://api.txtfetch.com/v1/extract",
headers=HEADERS,
params={"url": "https://example.com/report.pdf", "async": "true"},
)
job_id = submit.json()["job_id"]
while True:
poll = requests.get(f"https://api.txtfetch.com/v1/extract/{job_id}", headers=HEADERS)
result = poll.json()
if result["status"] != "processing":
break
time.sleep(2)
print(result["extracted_text"])gotchas
- pip install pytesseract installs a Python wrapper, not an OCR engine — without the actual tesseract binary on PATH, every call fails at runtime with "tesseract is not installed or it's not in your PATH", not at import time.
- openpyxl's data_only=True reads Excel's last cached calculation, not a live recalculation. A workbook written by a script that never opened it in Excel can have an empty cache, so a perfectly valid formula cell reads back as None.
- pandas.read_csv guesses UTF-8 by default; a Windows-1252 export with curly quotes or em dashes mojibakes silently unless you pass the correct encoding= yourself.
- python-docx, python-pptx, and openpyxl are three separate PyPI packages with three separate object models for what marketing calls "just Office files" — there's no single import that spans the family.
formats
faq
- How do I extract text from a PDF in Python without pypdf or pdfplumber?
- POST the file to https://api.txtfetch.com/v1/extract (multipart, or ?url= for a remote PDF) with your API key in the Authorization header, or use the txtfetch Python SDK's client.extract(file="..."). You get back { "status": "success", "extracted_text": "..." } whether the PDF is digital-native or scanned — no pypdf/pdfplumber import, and no separate OCR branch to write.
- Can I OCR a scanned document in Python without installing Tesseract myself?
- Yes — POST the scan (PNG, JPG, TIFF, or a scanned PDF) to txtfetch and OCR runs server-side automatically. There's no tesseract binary to install, no PATH to configure, and no pytesseract/Pillow/pdf2image chain to keep working across OS upgrades.
- How do I read an Outlook .msg file in Python?
- extract-msg works but only covers .msg. txtfetch's Python SDK handles .msg the same way as every other format — client.extract(file="thread.msg") — and also covers whole .pst/.ost mailbox archives through the same call, which extract-msg doesn't.
- Does openpyxl's data_only=True always give me the calculated cell value?
- Only if the workbook was last saved by Excel itself — data_only=True reads a cached result, and that cache can be empty for files produced by other tools. txtfetch returns the same cached calculated value with no data_only= flag to remember and no separate code path per format, but it reads Excel's cache too: a workbook whose formulas were never calculated by a spreadsheet app has no stored result for either tool to find.
- Is there an official Python SDK for txtfetch?
- Yes — pip install txtfetch. Its only runtime dependency is httpx, and it maps every API error code to its own typed exception class (see /docs/quickstarts).
go-further
other-languages
Stop parsing. Start shipping.
Create an account and get an API key in minutes — the free Hobby plan needs no card.
Get started →