code wiki / _hdl_build / nx_polite_crawl.nx

nx_polite_crawl.nx source

↩ module page · 98 lines · 5379 B

1// nx_polite_crawl.nx -- the POLITE CRAWL GOVERNOR (operator: researcher must "not be small sampled 2// but doesnt trigger bot blowback"). The key synergy this encodes: BROAD coverage and POLITENESS are 3// the SAME move -- you reach hundreds of sources by spreading THIN across many hosts (1-2 hits each, 4// paced), never hammering one. The anti-pattern that triggers bot-blocking is small-sample-but- 5// aggressive (one host 100x). The governor enforces broad-and-gentle: 6// * per-host MIN INTERVAL (>= N ms between requests to the SAME host) + robots crawl-delay 7// * EXPONENTIAL BACKOFF on 429/503 (capped) -- back off the moment a host pushes back 8// * round-robin LONGEST-IDLE scheduler -- always fetch the host idle longest = spreads load 9// * BREADTH metric (distinct hosts / total requests) -- high breadth = both large-sample AND polite 10// Per-host state in parallel i64 arrays (struct-free): last_ms[], backoff_until_ms[], consec_429[]. 11// All integer; deterministic given a clock. Composes nx_research_synth (corroborate what's fetched) 12// + nx_resource_governor (host CPU politeness). LAWS: struct-free, integer-only. license_tier: ORIGINAL 13import "nx_syscalls.nx" 14const PC_MAGIC_1000000000: i64 = 1000000000 15 16const PC_OK: i64 = 200 17const PC_RETRY: i64 = 429 // rate-limited (or 503) -> back off 18const PC_BLOCKED: i64 = 403 // hard block -> stop hitting this host entirely 19 20// exponential backoff in ms: base * 2^consec, capped. consec=0 -> base, 1 -> 2*base, ... 21func pc_backoff_ms(consec: i64, base_ms: i64, cap_ms: i64) -> i64 { 22 var b: i64 = base_ms 23 var i: i64 = 0 24 while i < consec { b = b * 2; if b >= cap_ms { b = cap_ms; i = consec } i = i + 1 } 25 if b > cap_ms { b = cap_ms } 26 return b 27} 28 29// may we fetch this host now? requires BOTH: min-interval since last request, AND past any backoff. 30// crawl_delay_ms is the host's robots.txt Crawl-delay (0 if none) -- the effective interval is the max. 31func pc_may_fetch(last_ms: i64, backoff_until_ms: i64, now_ms: i64, min_interval_ms: i64, crawl_delay_ms: i64) -> i64 { 32 var iv: i64 = min_interval_ms 33 if crawl_delay_ms > iv { iv = crawl_delay_ms } 34 if now_ms < last_ms + iv { return 0 } 35 if now_ms < backoff_until_ms { return 0 } 36 return 1 37} 38 39// record a fetch was issued to host idx (update its last-time) 40func pc_record(last_ms: *i64, idx: i64, now_ms: i64) -> i64 { last_ms[idx] = now_ms; return 0 } 41 42// apply a response: 200 resets backoff; 429/503 -> exponential backoff; 403 -> permanent (huge backoff). 43func pc_on_response(backoff_until_ms: *i64, consec_429: *i64, idx: i64, status: i64, now_ms: i64, base_ms: i64, cap_ms: i64) -> i64 { 44 if status == PC_OK { consec_429[idx] = 0; backoff_until_ms[idx] = 0; return PC_OK } 45 if status == PC_BLOCKED { backoff_until_ms[idx] = now_ms + PC_MAGIC_1000000000; return PC_BLOCKED } // ~stop forever 46 // PC_RETRY / 503 / anything else transient 47 consec_429[idx] = consec_429[idx] + 1 48 backoff_until_ms[idx] = now_ms + pc_backoff_ms(consec_429[idx], base_ms, cap_ms) 49 return PC_RETRY 50} 51 52// the SCHEDULER: among all eligible hosts (may_fetch==1), return the index idle LONGEST (smallest 53// last_ms) so load spreads evenly = broad AND gentle. Returns -1 if NONE is eligible right now 54// (caller should wait/sleep, never busy-hit). crawl[] = per-host robots crawl-delay (0 if none). 55func pc_next_host(last_ms: *i64, backoff_until_ms: *i64, crawl: *i64, n: i64, now_ms: i64, min_interval_ms: i64) -> i64 { 56 var best: i64 = 0 - 1 57 var best_last: i64 = 0 58 var i: i64 = 0 59 while i < n { 60 if pc_may_fetch(last_ms[i], backoff_until_ms[i], now_ms, min_interval_ms, crawl[i]) == 1 { 61 if best < 0 { best = i; best_last = last_ms[i] } 62 else { if last_ms[i] < best_last { best = i; best_last = last_ms[i] } } 63 } 64 i = i + 1 65 } 66 return best 67} 68 69// coverage BREADTH in per-mil: distinct hosts reached / total requests. HIGH = large-sample-and-polite 70// (each host hit few times). LOW = hammering few hosts = blowback risk. 71func pc_breadth_permil(distinct_hosts: i64, total_requests: i64) -> i64 { 72 if total_requests <= 0 { return 0 } 73 return distinct_hosts * 1000 / total_requests 74} 75 76// politeness verdict: every host stayed at or under the per-host request floor (no host hammered). 77// returns 1 if polite, 0 if any host exceeded the floor. 78func pc_is_polite(req_per_host: *i64, n: i64, floor_per_host: i64) -> i64 { 79 var i: i64 = 0 80 while i < n { if req_per_host[i] > floor_per_host { return 0 } i = i + 1 } 81 return 1 82} 83 84// is this sampling BROAD ENOUGH (not small-sampled)? distinct hosts >= a minimum breadth target. 85func pc_is_broad(distinct_hosts: i64, min_hosts: i64) -> i64 { 86 if distinct_hosts >= min_hosts { return 1 } 87 return 0 88} 89 90// the combined gate the researcher must pass: BROAD (many distinct hosts) AND POLITE (no host 91// hammered) AND high BREADTH ratio. Returns 1 only if all three hold. 92func pc_research_gate(distinct_hosts: i64, total_requests: i64, req_per_host: *i64, n: i64, 93 min_hosts: i64, floor_per_host: i64, min_breadth_permil: i64) -> i64 { 94 if pc_is_broad(distinct_hosts, min_hosts) == 0 { return 0 } 95 if pc_is_polite(req_per_host, n, floor_per_host) == 0 { return 0 } 96 if pc_breadth_permil(distinct_hosts, total_requests) < min_breadth_permil { return 0 } 97 return 1 98}