Precon Harvest · Operator brief

How we stay on Cloudflare’s good side

High-level map of the crawler’s anti-block stack. Not a how-to for abuse — a team brief on what the production Procore harvest already does, and why “throw more workers” is not the same as “look more human.”

System precon_harvest Target network.procore.com Transport curl_cffi 0.15 · Chrome impersonation Egress Rayobyte residential Doctrine don’t get blocked > never lose state > speed

1. Five layers (top → wire)

Cloudflare doesn’t score one field. It scores a coherent story: browser, network, timing, and content outcome.

1

Identity bundle (fingerprint)

TLS ClientHello, HTTP/2 settings, header order, User-Agent, client hints — one measured Chrome profile, never hand-mixed.

2

Session coherence

One cookie jar + one browser identity + one proxy exit for one bounded crawl round. Rotate together between rounds.

3

Residential egress

Rayobyte residential IPs. Fresh exit per session/round, not a datacenter ASN screaming “bot farm.”

4

Human-ish pacing

Non-metronomic delays, burst pauses, random long idles. One in-flight request per worker process.

5

Shape-aware detection + fleet brake

Reject challenge/empty “success.” If many workers see blocks together, auto-pause the whole source.

Key idea: each worker is a slow “person,” not a faster person. Scale = more parallel polite sessions, not shorter delays.

2. Fingerprint & handshake

What Cloudflare’s bot management actually sees first.

TLS / JA3–JA4

We use curl_cffi Chrome impersonation profiles (chrome136chrome146. The ClientHello is a real browser shape, not Python-default OpenSSL. Rollout verified JA4 t13d1516h2_8daaf6152771_d8a2da3f94cd (Chrome-like).

HTTP/2

Settings, priority, and frame behavior come from the same impersonation bundle as TLS — so H2 doesn’t contradict the ClientHello.

User-Agent + client hints

UA is not a string we invent. It ships with the impersonation profile. We only add safe extras: Accept-Language, Referer, Sec-Fetch-Site.

What we refuse to do

No franken-headers. No “Chrome UA + Python TLS.” No random UA rotation under a sticky TLS session. That mismatch is a classic ban signal.

Handshake in one line: look like one real Chrome, end-to-end — or don’t pretend.

3. IP, proxy & session lifecycle

Network reputation and session hygiene.

TopicWhat we doWhy CF cares
Egress class Residential proxy (Rayobyte), not DO datacenter IPs hitting Procore. Datacenter ASN + scrape volume = easy deny.
IP rotation New session ≈ new exit. Verified multi-IP diversity at rollout. One residential IP thrashing is still a bot; diversity spreads heat.
Geo targeting Code can suffix region; this account currently ignores region (country-US works). We don’t depend on true state geo. Fake geo claims aren’t required; lying about geo can hurt if it mismatches.
Session scope One identity + cookies + proxy for one bounded round, then rotate all three together. IP churn under one cookie jar looks broken/malicious.
Compute host Workers run on our droplet; only browser traffic exits via residential proxy. Separates “our infra” from “what Procore sees.”
Limit: more workers = more concurrent residential identities and more total RPS. RAM upgrade lets us run 8 safely on the box; it does not make Cloudflare love 20× more traffic.

4. Behavior (pacing & workload shape)

Procore gets the most conservative politeness of any source.

Procore delays

Base 5–11s between requests, cubic-tail jitter, 10% chance of an extra pause, every 25 requests a long idle 45–150s. Expected ~14s average delay/request/worker.

Concurrency

concurrency = 1 per worker process. N workers = N independent politeness budgets — the real risk lever.

Small rounds

Drain loop: max ~2 list pages + 12 details per round. Controls (pause/reassign) apply between rounds so we never hard-kill mid-fetch.

Parse-first content

Data from Next.js __NEXT_DATA__ / pageProps.business — no headless browser thrashing the DOM, no random clicking.

5. Detection & fleet safety

Looking human is half the job. Not poisoning ourselves is the other half.

Hard block

Status in 401 / 403 / 407 / 429 / 503, or interstitial body markers (“just a moment”, “attention required”, “access denied”, …).

Soft block

HTTP 200 but body lacks expected shape. For details: must have real pageProps.business object — empty challenge JSON is not “done.”

False positive we ignore

Passive cdn-cgi/challenge scripts ride on normal pages. We never flag on that alone.

Fleet circuit breaker

≥5 counting block events from ≥2 workers in 5 minutes → auto source.enabled = false. HTTP 407 (proxy auth) is logged but does not count toward pause.

Resume is human. Auto-pause is allowed. Auto-blast after a wave is not. Diagnose, then re-enable deliberately.

6. What would make Cloudflare (and us) unhappy

Team guardrails — keep these out of “clever” experiments.

Don’tWhy it burns us
Raise per-worker concurrency or slash delays Classic bot signature; undermines the whole stack.
Hand-edit UA / headers against the TLS profile Fingerprint mismatch — easy ML deny.
Rotate IP every request under one cookie jar Looks like session hijack or broken client.
Hit Procore from the droplet’s public IP Datacenter reputation; burns the host ASN.
Seed all 50 states + max workers same hour Burst of list traffic + detail flood; origin heat + our own queue chaos.
Treat 200 empty as success Silent data poison; “success” that is actually a challenge.
Ignore auto-pause and re-enable harder Turns a soft warning into a hard ban.

One-slide summary for the team

We look like

Several slow Chrome users on residential IPs, coherent TLS/H2/UA, cookies that match the exit, reading public directory JSON.

We refuse to look like

A Python script with a stolen Chrome UA, datacenter IP, metronome timing, or 20 parallel tabs from one identity.

Scale rule: add workers only after clean canaries. Host RAM is necessary; origin trust is sufficient.