Skip to main content
MDX Engineering Note · automation_hub··6 min read

How automation_hub scrapes and processes PDFs with Celery, Redis, and Gemini Schema contracts

Decoupling Playwright headless browsers and multi-page PDF ingestion from FastAPI HTTP threads using Redis queues, exponential backoff workers, and Kubernetes HPA.

68 Pytest Tests · Docker & Kubernetes HPASource: /content/writing/automation-hub-celery-redis-pdf-pipeline.mdxFastAPICeleryRedisPlaywrightGemini APIKubernetes

1. Why Synchronous Scraping Breaks HTTP Event Loops

When an API endpoint tries to launch a headless Chromium browser via Playwright or parse a multi-megabyte PDF inside a synchronous HTTP request cycle, two failure modes appear immediately: event-loop starvation and silent upstream failures.

Launching Chromium and waiting for dynamic DOM hydration easily exceeds reverse-proxy timeouts when multiple requests arrive concurrently. Furthermore, external target sites and LLM APIs enforce rate limits. Without a persistent message broker and retry state machine, a transient 429 Too Many Requests drops the entire job.

In automation_hub, the HTTP layer never executes Playwright or PDF parsing directly. Instead, FastAPI acts strictly as a schema validation and task-dispatch gateway.

2. Non-Blocking FastAPI Ingestion & Redis Broker Queueing

Every incoming scrape or PDF extraction job is validated at the boundary using Pydantic v2 models before being pushed to Redis via Celery's apply_async. The endpoint immediately returns a 202 Accepted response with a deterministic task_id so the API event loop stays unblocked.

app/api/routes/tasks.py(python)
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, HttpUrl, Field
from app.worker.celery_app import process_document_pipeline

router = APIRouter(prefix="/api/v1/jobs", tags=["jobs"])

class ExtractionJobRequest(BaseModel):
    target_url: HttpUrl | None = None
    pdf_storage_key: str | None = Field(default=None, max_length=256)
    schema_contract: str = Field(default="invoice_v1", pattern=r"^[a-z0-9_]+$")

@router.post("/submit", status_code=status.HTTP_202_ACCEPTED)
async def submit_extraction_job(payload: ExtractionJobRequest):
    if not payload.target_url and not payload.pdf_storage_key:
        raise HTTPException(status_code=400, detail="Provide target_url or pdf_storage_key")

    async_result = process_document_pipeline.apply_async(
        kwargs=payload.model_dump(mode="json"),
        queue="extraction_high_priority",
    )
    return {
        "task_id": async_result.id,
        "status": "PENDING",
        "poll_url": f"/api/v1/jobs/{async_result.id}",
    }

3. Celery Worker Pools with Exponential Backoff & Structured Gemini Contracts

Inside the Celery worker container, tasks pull serialized payloads from Redis. Rather than asking Gemini to return free-form prose from scraped HTML or PDF pages, the worker enforces a strict JSON schema contract validated by Pydantic before persisting the result.

If an upstream target times out or returns a transient connection error, Celery automatically schedules an exponential backoff retry with jitter so workers do not thundering-herd the upstream service.

app/worker/tasks.py(python)
from celery import shared_task
from pydantic import BaseModel
from app.services.pdf_parser import extract_pdf_text_chunks
from app.services.gemini_extractor import extract_structured_json

class ExtractedDocumentSummary(BaseModel):
    document_title: str
    entities: list[str]
    key_findings: list[str]
    page_count: int

@shared_task(
    bind=True,
    autoretry_for=(TimeoutError, ConnectionError),
    retry_backoff=True,
    retry_jitter=True,
    max_retries=3,
)
def process_document_pipeline(self, target_url=None, pdf_storage_key=None, schema_contract="invoice_v1"):
    raw_chunks = extract_pdf_text_chunks(pdf_storage_key) if pdf_storage_key else []
    structured_payload = extract_structured_json(
        chunks=raw_chunks,
        response_schema=ExtractedDocumentSummary,
    )
    return structured_payload.model_dump()

4. Containerization, Kubernetes HPA & 68 Pytest Verification Tests

To keep the deployment reproducible across environments, automation_hub separates the stateless FastAPI web tier from the stateful Redis broker and Celery worker pods using multi-stage Dockerfiles and Kubernetes Horizontal Pod Autoscaler (k8s/hpa.yaml) manifests.

The entire pipeline is verified across 68 automated pytest unit and integration tests covering Pydantic v2 request validation, Redis queue serialization, worker retry transitions, and Kubernetes readiness/liveness probe endpoints.

Zero invented benchmarks: All architectural claims in this note map directly to the automation_hub repository and its 68-test pytest suite.

Share Card & OpenGraph Preview

1200×630 Dynamic OG Card
RAHUL ROY · ENGINEERING NOTE · AUTOMATION_HUB6 min read

How automation_hub scrapes and processes PDFs with Celery, Redis, and Gemini Schema contracts

68 Pytest Tests · Docker & Kubernetes HPAgithub.com/Rahul03ll