Skip to content

Detect Cloudflare block pages and pace the Best Matches feed - #21

Merged
roperi merged 1 commit into
mainfrom
fix/cloudflare-block-detection
Oct 30, 2026
Merged

roperi merged 1 commit into
mainfrom
fix/cloudflare-block-detection

Conversation

@roperi

@roperi roperi commented Oct 30, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the reported "You're blocked" behaviour, where the scraper navigated to Best Matches, treated Cloudflare's block page as a completed login, scrolled it and then reported a successful run with zero jobs.

  • Block page detection — auth.py recognises Cloudflare's block page (cf/"You have been blocked"/"Attention Required"), so _authenticated() no longer accepts it, and ensure_not_blocked() raises UpworkBlockedError with an actionable message. Checks run before login, after navigating to Best Matches, and on every scroll step.
  • One navigation instead of two — a persistent profile now opens Best Matches directly; the login page is only used when no feed or login form appears. /nx/s/find-work/best-matches is recognised as the session-scoped redirect target.
  • Feed-aware pacing — waits for the first job tile before sending input, jitters the scroll pause, and stops when two consecutive scrolls move nothing.
  • Robust job URLs — reads the rendered /jobs/... links, falling back to the opening UID; zero job links is now a hard failure instead of a silent success.
  • Response logging — each run logs the HTTP status, cf-ray, cf-mitigated and retry-after of every document load, so a refused run can be diagnosed from the response rather than inferred.

Related issue

No issue filed; reported in conversation with reproduction evidence.

Validation

  • I ran make check.
  • I added or updated tests for behavioral changes.
  • I updated documentation for user-visible changes.
  • I removed credentials, cookies, private data, and generated artifacts from the change.

39 passed, Ruff clean, mypy upwork_scraper clean. Tests cover block-page detection, authentication state, login navigation order and fallback, response-header parsing, feed waiting, and both job URL extraction paths.

Notes for reviewers

Blocking is decided by Cloudflare per request and varies between runs, so the change makes the failure explicit and diagnostic rather than attempting to defeat the scoring. The new log lines are the intended way to identify the trigger on a real blocked run.

Cloudflare's block page is served at the requested URL, so _authenticated() treated it as a completed login: the scraper scrolled the block page and then reported a successful run with zero jobs. Detect the block page explicitly, fail fast with an actionable message, and check for it before and during scrolling.

Open Best Matches directly instead of the login page on every run. With a persistent profile the login page only redirects to the dashboard, and a redirect followed by a second top-level navigation within a second is scored as automation. Accept the /nx/s/ session-scoped path so an authenticated session is not navigated again.

Wait for feed tiles before sending input, jitter the scroll cadence, and stop once the feed stops moving. Read job URLs from the rendered job links with the opening UID as a fallback, and fail when no job links are found instead of reporting zero processed jobs.

Log the HTTP status and Cloudflare headers of each document load so a refused run can be diagnosed from the response instead of inferred from page text.
@roperi
roperi merged commit 6924d06 into main Oct 30, 2026
2 checks passed
@roperi
roperi deleted the fix/cloudflare-block-detection branch October 30, 2026 10:34
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