Skip to content

feat: deletion detection + fast scraper intervals + scraper reliability fixes#32

Merged
AshrafHosam merged 12 commits into
mainfrom
feature/deletion-detection
Jun 24, 2026
Merged

feat: deletion detection + fast scraper intervals + scraper reliability fixes#32
AshrafHosam merged 12 commits into
mainfrom
feature/deletion-detection

Conversation

@AshrafHosam

Copy link
Copy Markdown
Collaborator

Summary

  • Deletion detection: crawler bumps CrawlUrls no longer in live listing → scraper fetches → 404 → offer archived within ~1s
  • Archive-only: all offers archived on removal (never hard-deleted from DB); both PUBLISHED and DRAFT → ARCHIVED
  • 4xx → immediate archive: any client error triggers archiving; 5xx keeps exponential backoff
  • Fast intervals: crawler and scraper now run every 1s (configurable via CRAWLER_INTERVAL_SECONDS / SCRAPER_INTERVAL_SECONDS)
  • APScheduler overlap fix: replaced APScheduler interval jobs with thread loops (_loop(fn, interval, stop)) — crawler/scraper never overlap
  • Ollama connection fallthrough: connection errors bypass 60s cooldown and fall through to deterministic extraction immediately
  • 1s HTTP timeout: SCRAPER_TIMEOUT_SECONDS=1 (was 30s) for fast failure detection
  • Clean descriptions: extract_deterministic stores <meta name="description"> as summary; TF-IDF signal from body <li> elements, not injected into description
  • UI tracking fix: 4xx deletions show grey "archived" chip (not red "error"); errors_count reflects only genuine failures
  • Empty run suppression: scraping runs with zero log entries are deleted (not persisted to DB/UI)

Files Changed

File Change
backend/content/scrapers/queue_service.py deletion bump, 4xx archive, archive-only, empty run suppression
backend/content/scrapers/service.py clean classifier_text (title+summary+details)
backend/content/scrapers/ollama_client.py connection error fast-fallthrough
backend/content/management/commands/run_scraper_worker.py thread loops, BackgroundScheduler for cron only
backend/content/templates/content/mock_website_detail.html meta = real description, body li = type signal
backend/ui/src/app/shared/run-log.parser.ts url_archived event → archived flag
backend/ui/src/app/pages/scrapper-admin-page.component.* archived row + chip styles
docker-compose.yml seconds-based env vars, 1s timeout
backend/.env / .env.example updated defaults

Test Plan

  • 332 Django tests pass (python manage.py test content)
  • Angular TypeScript type-check clean (tsc --noEmit)
  • Angular production build succeeds (budget warnings pre-exist on main)
  • End-to-end: add offer to mock site → appears in OSS → delete from mock → archived in OSS within ~2s
  • Empty scraping runs no longer appear in tracking UI
  • Archived URLs show grey chip, not red error chip
  • APScheduler max-instances warnings gone (thread loops)
  • Ollama not running → no 60s wait, immediate deterministic fallback

When crawler scans a source listing, any CrawlUrl no longer present
is bumped to next_check_at=now so the scraper picks it up in the next
batch, fetches it, gets 404, and archives the linked offer.

Manually created offers (no CrawlUrl) are HEAD-checked directly by the
crawler: filtered by organization, source domain, and crawl_urls__isnull.
Published offers → ARCHIVED, drafts → deleted on 404/410.

Guard: if live_url_set is empty (network flake), both deletion steps are
skipped to prevent mass false-archiving.

Scheduler intervals switched from minutes to seconds via env vars
CRAWLER_INTERVAL_SECONDS / SCRAPER_INTERVAL_SECONDS (default 1s),
updated in docker-compose.yml, backend/.env, and backend/.env.example.
…ype-aware mock template

- Remove empty-live-set guard: network errors throw exceptions (caught in run());
  a 200 response with 0 URLs is a genuine empty listing, not a flake
- Clear crawl_url.offer_id after offer.delete() to avoid Django's
  'save() prohibited to prevent data loss due to unsaved related object' ValueError
- Update mock_website_detail.html meta description to use type-specific keywords
  so TF-IDF classifier reliably scores above 0.30 threshold (tested: 0.59-0.67)
…ng UI

A 404/410 during deletion detection is expected success — the scraper
correctly archives the offer. Previously it emitted event=url_failed
(shown as red "error" chip) while errors_count stayed 0, causing
a confusing mismatch.

Now 404/410 emits event=url_archived, the URL table shows a grey
"archived" chip, and only genuine errors (5xx, network timeouts)
increment errors_count and show as red.
Draft and published offers both move to ARCHIVED when their source URL
returns 404/410. Hard-deleting drafts lost audit trail and any manual
enrichment done before publishing.
…for 5xx

Any 4xx client error means the resource is gone/inaccessible — archive
immediately instead of only 404/410. 5xx server errors keep the existing
exponential backoff since they indicate transient server issues.

200 responses returning a generic/empty page also archive the linked
offer (if one exists); new URLs with no linked offer still just neglect.
Meta description was enriched with type keywords causing those keywords
to be stored as the offer description. Reverted meta to real description
only. Type keywords now come from body list items (offer_type field)
which feed extracted.details. Classifier now uses title+summary+details
so it picks up the body signal.
…scraper

APScheduler skipped every tick when crawl took longer than the interval
(1s) with multiple sources, flooding logs with 'maximum instances' warnings.

Thread loops run fn → sleep N seconds → repeat, guaranteeing no overlap
and no skip warnings regardless of how long a crawl takes. APScheduler
kept only for infrequent cron jobs (translation, link-checker, archive).
…erministic immediately

When Ollama is unreachable (connection refused), models were put in 60s
cooldown and _wait_for_available_model slept for ~58s before retrying —
blocking the scraper thread indefinitely.

Now connection errors (non-429) are tracked separately. If all cooled-down
models failed due to connection errors, the wait is skipped entirely and
the caller falls through to deterministic classification immediately.
Rate-limit (429) cooldowns still wait as before.

LLM enabled/disabled per-source via admin panel llm_fallback_enabled flag.
Empty batches (all neglected, nothing logged) no longer create a ScrapingRun
DB row — they are deleted before saving. Only runs with actual log entries
(created/updated/archived/errors) appear in the UI tracking tab.
@AshrafHosam
AshrafHosam merged commit a9a5db0 into main Jun 24, 2026
2 checks passed
@AshrafHosam
AshrafHosam deleted the feature/deletion-detection branch June 24, 2026 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant