Malware-detection / app /services /pdf_extractor.py
kumar-aditya's picture
Upload 60 files
c8f4a46 verified
Raw
History Blame Contribute Delete
2.05 kB
import re
from pathlib import Path
import fitz # PyMuPDF
from app.core.logging import get_logger
logger = get_logger(__name__)
# If extracted text is fewer than this many characters it's almost certainly
# a scanned / image-only PDF that we cannot handle in MVP.
SCANNED_THRESHOLD = 100
class ExtractionError(Exception):
"""Raised when the PDF cannot be meaningfully extracted."""
def extract_text(path: Path) -> tuple[str, int]:
"""
Extract and clean text from a digital PDF.
Returns
-------
text : str
Cleaned, whitespace-normalised body text.
page_count : int
Number of pages in the document.
Raises
------
ExtractionError
If the file is unreadable or appears to be a scanned/image PDF.
"""
try:
doc = fitz.open(str(path))
except Exception as exc:
logger.error("Could not open PDF %s: %s", path, exc)
raise ExtractionError("Could not read this PDF.") from exc
page_count = len(doc)
raw_pages: list[str] = []
for page in doc:
raw_pages.append(page.get_text("text")) # type: ignore[arg-type]
doc.close()
full_text = "\n".join(raw_pages)
cleaned = _clean(full_text)
if len(cleaned) < SCANNED_THRESHOLD:
logger.warning(
"PDF %s yielded only %d chars — likely scanned", path, len(cleaned)
)
raise ExtractionError(
"This looks like a scanned resume; MVP supports digital text PDFs only."
)
logger.info("Extracted %d chars from %d-page PDF %s", len(cleaned), page_count, path.name)
return cleaned, page_count
def _clean(raw: str) -> str:
"""Normalise whitespace and remove common PDF artifacts."""
# Collapse runs of whitespace that aren't newlines
text = re.sub(r"[ \t]+", " ", raw)
# Collapse 3+ consecutive blank lines into 2
text = re.sub(r"\n{3,}", "\n\n", text)
# Strip leading/trailing whitespace per line
lines = [line.strip() for line in text.splitlines()]
return "\n".join(lines).strip()