code wiki / _hdl_build / nx_crawl_web.nx

nx_crawl_web.nx source

↩ module page · 198 lines · 11681 B

1// nx_crawl_web.nx -- REAL-WEB BFS crawler: "crawl whatever is out there", sovereign + polite. Combines what the 2// ecosystem already had in pieces: nx_crawl_https (validated TLS fetch, but single-seed) + nx_crawl_frontier 3// (link extraction, absolute-only) + adds RELATIVE-URL resolution (the v2 the frontier flagged) so the spider 4// actually WALKS a site's links, not just the seed. Pipeline per page: dequeue -> polite sleep -> sovereign 5// nx_https_fetch_follow (TLS-1.3 + Mozilla CA, the proven fetcher) -> nx_html_to_text (now OOM-safe) -> simhash 6// dedup -> record -> extract+resolve outbound links -> enqueue. Bounded (max_pages + the per-process certloop 7// leak ~19 fetches = the NEXT OOM-class fix for crawl-at-scale). Seeded on a cost-intelligence page so the crawl 8// grows the corpus. license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_x509_trust_store.nx" 11import "nx_trust_store_load_from_certdata.nx" 12import "nx_https_fetch_follow.nx" 13import "nx_html_to_text.nx" 14import "nx_simhash.nx" 15const K_MAGIC_8192: i64 = 8192 16const K_MAGIC_4194304: i64 = 4194304 17const K_MAGIC_8388608: i64 = 8388608 18const K_MAGIC_1048576: i64 = 1048576 19const K_MAGIC_2048: i64 = 2048 20 21func gp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 22func gn(v0: i64) -> i64 { var v: i64=v0; if v<0{sys_write(1,"-" as *u8,1);v=0-v} let b: *u8=sys_mmap(24); var k: i64=0; if v==0{b[0]=48 as u8;k=1} while v>0{b[k]=(48+(v%10)) as u8;v=v/10;k=k+1} let o: *u8=sys_mmap(24); var j: i64=0; while j<k{o[j]=b[k-1-j];j=j+1} sys_write(1,o,k); return 0 } 23func slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 24func streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]!=(0 as u8){return 0} return 1 } 25func cw_lc(c: i64) -> i64 { if c>=0x41 { if c<=0x5a { return c+0x20 } } return c } 26// html[off..] begins with literal lit? 27func cw_pre(h: *u8, hlen: i64, off: i64, lit: *u8, litlen: i64) -> i64 { 28 if off+litlen > hlen { return 0 } 29 var k: i64=0; while k<litlen { if h[off+k]!=lit[k] { return 0 } k=k+1 } return 1 30} 31// "href=" (case-insensitive) at off? 32func cw_href_at(h: *u8, hlen: i64, off: i64) -> i64 { 33 if off+5 > hlen { return 0 } 34 if cw_lc(h[off] as i64)!=0x68 { return 0 } 35 if cw_lc(h[off+1] as i64)!=0x72 { return 0 } 36 if cw_lc(h[off+2] as i64)!=0x65 { return 0 } 37 if cw_lc(h[off+3] as i64)!=0x66 { return 0 } 38 if (h[off+4] as i64)!=0x3d { return 0 } 39 return 1 40} 41// enqueue a null-term URL (copied) if not already SEEN + room. seen is the dedup set (all ever enqueued). 42func eq_nl(a: *u8, alen: i64, b: *u8) -> i64 { 43 var i: i64=0; while i<alen { if a[i]!=b[i] { return 0 } i=i+1 } 44 if b[alen]!=(0 as u8) { return 0 } 45 return 1 46} 47func cw_enqueue(url: *u8, ulen: i64, seen: **u8, nseen: *i64, queue: **u8, qt: *i64, cap: i64) -> i64 { 48 // S-class: dedup + capacity check BEFORE allocating -> the common dup/full path leaks NOTHING (was the page-11 OOM). 49 var s: i64=0; while s<nseen[0] { if eq_nl(url, ulen, seen[s])==1 { return 0 } s=s+1 } 50 if nseen[0] >= cap { return 0 } 51 let nu: *u8 = sys_mmap(ulen+1); var i: i64=0; while i<ulen { nu[i]=url[i]; i=i+1 } nu[ulen]=0 as u8 52 seen[nseen[0]] = nu; queue[qt[0]] = nu; nseen[0]=nseen[0]+1; qt[0]=qt[0]+1 53 return 1 54} 55// extract outbound links from HTML + enqueue (absolute http(s) kept; relative "/path" resolved against base host). 56func cw_links(h: *u8, hlen: i64, base: *u8, blen: i64, seen: **u8, nseen: *i64, queue: **u8, qt: *i64, cap: i64) -> i64 { 57 var found: i64=0 58 let scratch: *u8 = sys_mmap(K_MAGIC_8192) // reused per relative link -> no per-link buffer leak 59 var i: i64=0 60 while i < hlen { 61 var step: i64=1 62 if cw_href_at(h, hlen, i)==1 { 63 let q: i64 = h[i+5] as i64 64 if q==0x22 { step=0 } else { if q==0x27 { step=0 } else { step=1 } } 65 if step==0 { 66 let cs: i64 = i+6 67 var e: i64 = cs 68 var run: i64=1 69 while run==1 { run=0; if e<hlen { if (h[e] as i64)!=q { e=e+1; run=1 } } } 70 let clen: i64 = e-cs 71 if clen>0 { 72 if cw_pre(h,hlen,cs,"http://" as *u8,7)==1 { 73 if cw_enqueue(((h as i64)+cs) as *u8, clen, seen, nseen, queue, qt, cap)==1 { found=found+1 } 74 } else { if cw_pre(h,hlen,cs,"https://" as *u8,8)==1 { 75 if cw_enqueue(((h as i64)+cs) as *u8, clen, seen, nseen, queue, qt, cap)==1 { found=found+1 } 76 } else { 77 // relative "/path" (but not "//host"): resolve against base host 78 if (h[cs] as i64)==0x2f { 79 var protorel: i64=0 80 if clen>=2 { if (h[cs+1] as i64)==0x2f { protorel=1 } } 81 if protorel==0 { if blen+clen+1 < K_MAGIC_8192 { 82 var b: i64=0; while b<blen { scratch[b]=base[b]; b=b+1 } 83 var c: i64=0; while c<clen { scratch[blen+c]=h[cs+c]; c=c+1 } 84 scratch[blen+clen]=0 as u8 85 if cw_enqueue(scratch, blen+clen, seen, nseen, queue, qt, cap)==1 { found=found+1 } 86 } } 87 } 88 } } 89 } 90 step = (e-i)+1 91 } else { step=1 } 92 } 93 i = i + step 94 } 95 return found 96} 97 98// S-CLASS arg-driven helpers (2026-06-25, operator: "not seed fixed ... s-class capable"): derive the 99// scheme://host base from ANY seed URL (so relative links resolve on any site), and parse an int arg. 100func cw_base(url: *u8, out: *u8) -> i64 { 101 let n: i64 = slen(url) 102 var schemeend: i64 = 0 103 var i: i64 = 0 104 var hit: i64 = 0 105 while hit == 0 { 106 if i + 2 >= n { hit = 1 } else { 107 if url[i]==(58 as u8) { if url[i+1]==(47 as u8) { if url[i+2]==(47 as u8) { schemeend = i+3; hit = 1 } } } 108 if hit == 0 { i = i + 1 } 109 } 110 } 111 var e: i64 = schemeend 112 var go: i64 = 1 113 while go == 1 { if e >= n { go = 0 } else { if url[e]==(47 as u8) { go = 0 } else { e = e + 1 } } } 114 var k: i64 = 0 115 while k < e { out[k] = url[k]; k = k + 1 } 116 out[e] = 0 as u8 117 return e 118} 119func cw_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } return v } 120 121func main(argc: i64, argv: *i64) -> i64 { 122 var pass: i64=0; var fail: i64=0 123 let r: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, K_MAGIC_4194304) 124 if r <= 0 { gp("CRAWL-WEB FAIL: certdata load\n" as *u8); sys_exit(1); return 1 } 125 let store: *TrustStore = r as *TrustStore 126 gp("=== nx_crawl_web: REAL-WEB BFS crawl (sovereign TLS, polite, link-following) ===\n" as *u8) 127 gp(" CA roots=" as *u8); gn(trust_store_count(store)); gp("\n" as *u8) 128 129 let cap: i64 = K_MAGIC_8388608 130 let out: *u8 = sys_mmap(cap) 131 let tcap: i64 = K_MAGIC_1048576 132 let text: *u8 = sys_mmap(tcap) 133 let status: *i64 = sys_mmap(8) as *i64 134 let QCAP: i64 = 256 135 let queue: **u8 = sys_mmap(QCAP*8) as **u8 136 let seen: **u8 = sys_mmap(QCAP*8) as **u8 137 let qt: *i64 = sys_mmap(8) as *i64; qt[0]=0 138 let nseen: *i64 = sys_mmap(8) as *i64; nseen[0]=0 139 var qh: i64 = 0 140 let keptfp: *i64 = sys_mmap(64*8) as *i64 141 // S-CLASS 2026-06-25: seed URL + max_pages are ARG-DRIVEN; base (scheme://host, for relative-link 142 // resolution) is DERIVED from the seed -> crawl ANY site, not just wikipedia. No-arg keeps the default 143 // Lithium seed + the standing self-gate (so the crawler's CI gate still proves out). 144 var seed: *u8 = "https://en.wikipedia.org/wiki/Lithium" as *u8 145 var argmode: i64 = 0 146 var max_pages: i64 = 12 147 if argc >= 2 { seed = argv[1] as *u8; argmode = 1 } 148 if argc >= 3 { max_pages = cw_atoi(argv[2] as *u8) } 149 let base: *u8 = sys_mmap(K_MAGIC_2048) 150 let blen: i64 = cw_base(seed, base) 151 gp(" seed=" as *u8); gp(seed); gp(" base=" as *u8); gp(base); gp(" max_pages=" as *u8); gn(max_pages); gp("\n" as *u8) 152 cw_enqueue(seed, slen(seed), seen, nseen, queue, qt, QCAP) 153 var crawled: i64=0; var fails: i64=0; var dups: i64=0; var discovered: i64=0 154 var running: i64=1 155 while running==1 { 156 if qh >= qt[0] { running=0 } 157 if crawled >= max_pages { running=0 } 158 if running==1 { 159 let url: *u8 = queue[qh]; qh=qh+1 160 sys_sleep_ms(350) // polite, ISP-safe 161 let n: i64 = nx_https_fetch_follow(url, store, out, cap, 6, status) 162 if n <= 0 { fails=fails+1; gp(" fetch-fail verdict=" as *u8); gn(n); gp(" " as *u8); gp(url); gp("\n" as *u8) } 163 else { if status[0] != 200 { fails=fails+1; gp(" status=" as *u8); gn(status[0]); gp(" " as *u8); gp(url); gp("\n" as *u8) } 164 else { 165 let tlen: i64 = nx_html_to_text(out, n, text, tcap) 166 let fp: i64 = nx_simhash_fingerprint(text, tlen) 167 var dup: i64=0; var k: i64=0 168 while k<crawled { if nx_simhash_hamming(fp, keptfp[k]) <= 4 { dup=1 } k=k+1 } 169 if dup==1 { dups=dups+1; gp(" dup " as *u8); gp(url); gp("\n" as *u8) } 170 else { 171 keptfp[crawled]=fp; crawled=crawled+1 172 gp(" crawled #" as *u8); gn(crawled); gp(" " as *u8); gp(url); gp(" (" as *u8); gn(n); gp(" bytes -> " as *u8); gn(tlen); gp(" chars)\n" as *u8) 173 let nl: i64 = cw_links(out, n, base, blen, seen, nseen, queue, qt, QCAP) 174 discovered = discovered + nl 175 } 176 } } 177 } 178 } 179 gp("CRAWL-WEB done: crawled=" as *u8); gn(crawled); gp(" pages, discovered=" as *u8); gn(discovered); gp(" new links, dups=" as *u8); gn(dups); gp(" fails=" as *u8); gn(fails); gp(" (frontier qsize=" as *u8); gn(qt[0]); gp(")\n" as *u8) 180 181 // ===== verdict ===== ARG mode (operator seed): success iff the seed actually fetched -- a single arbitrary 182 // URL (or a PDF with no outbound HTML links) must NOT false-RED the crawler. NO-ARG mode: the standing 183 // self-gate below proves a real multi-page link-following crawl (the CI guarantee, unchanged). 184 if argmode == 1 { 185 if crawled >= 1 { gp("CRAWL-WEB verdict=GREEN (arg-seed crawl: " as *u8); gn(crawled); gp(" page(s) fetched + " as *u8); gn(discovered); gp(" links discovered from the operator seed)\n" as *u8); sys_exit(0); return 0 } 186 gp("CRAWL-WEB verdict=RED (arg-seed fetched nothing -- TLS / redirect / host / paywall?)\n" as *u8); sys_exit(1); return 1 187 } 188 // ===== self-gate (default seed): PROVE a real multi-page link-following crawl happened ===== 189 if crawled >= 5 { pass=pass+1 } else { fail=fail+1; gp(" FAIL crawled<5 (not a multi-page crawl)\n" as *u8) } 190 if discovered >= 10 { pass=pass+1 } else { fail=fail+1; gp(" FAIL discovered<10 (link-following broken)\n" as *u8) } 191 if qt[0] > crawled { pass=pass+1 } else { fail=fail+1; gp(" FAIL frontier didn't grow (no real link discovery)\n" as *u8) } 192 // the seed itself must have fetched (crawled>=1 means TLS worked on the real web) 193 if crawled >= 1 { pass=pass+1 } else { fail=fail+1; gp(" FAIL nothing fetched (TLS/network)\n" as *u8) } 194 195 gp("CRAWL-WEB pass=" as *u8); gn(pass); gp(" fail=" as *u8); gn(fail) 196 if fail==0 { gp(" verdict=GREEN (our own spider crawled the REAL web over sovereign TLS, followed real links, deduped)\n" as *u8); sys_exit(0); return 0 } 197 gp(" verdict=RED\n" as *u8); sys_exit(1); return 1 198}