lncrawl-scraper 1.0.0 · 2026-07-29 23:00 +04

Live verification against real Cloudflare-protected sites

Every code path in the scraper, exercised against production Cloudflare deployments rather than fixtures — the transport group, the diagnosis classifier, the escalation ladder, a real browser solve, the archive tier, and a live tor-pool. 27 of 29 scenarios pass. A stubbed transport can confirm that the code does what it says; only this can confirm that what it says is true of a real server.

27/29
scenarios passed
102
assertions verified live
0 failed
330
hosts probed
161 behind Cloudflare

The corpus

Every base URL in lncrawl's source index was probed twice — once with a plain requests session, once with the impersonating transport — and each response classified by the library's own diagnosis. That pairing is the measurement: the only difference between the two requests is the network signature.

330
hosts probed
161
behind Cloudflare
34
refused a plain client, served the impersonated one

What fronts these hosts

EdgeHosts
cloudflare157
nginx49
unreachable39
cowboy27
apache8
openresty7
unknown7
ddos-guard6
tengine5
litespeed4

Through a Tor exit

Of 45 Cloudflare hosts reached through the local tor-pool: 28 served outright, 10 challenged, 0 reputation-blocked.

A negative result worth keeping: the reputation layer is not uniformly hostile to Tor on this corpus. The model treats a Tor exit as clearing nothing at layer 1, which is conservative rather than measured — and the consequence is that a browser solver buys more here than a residential proxy would.

Which detection layer each client meets Hosts, out of 330 probed. Lower is better — a layer met is a layer that has to be answered.
plain requests impersonated transport
L9 Managed JavaScript challenge
44
28
L8 Per-zone behavioural model
23
22
L12 Super Bot Fight Mode
20
7
L6 Browser and JavaScript fingerprint
10
10
L15 Operator edge code
5
0
L10 Turnstile
0
1

Reading the chart: the impersonated client meets the managed challenge on far fewer hosts, and meets the lighter scoring tiers less often — those are the layers a network signature answers. It meets the behavioural layer more often, which is not a regression: that is this session's own request volume against the same hosts being rate limited, and it is the one layer no client-side change addresses.

Scenarios

Each one names the layer it exercises and what a pass actually proves — "it returned 200" is not evidence about a detection layer on its own. Expand any row for the individual assertions and the values observed.

Transport — layers 2–5

pass S01 A plain client is refused where an impersonated one is served L2L3L4L5 7.9s

What a pass proves. Layers 2-5 are one barrier: the only difference between these two requests is the network signature, and it decides the outcome.

against looked up from the last probe
  • ·hosts presenting the condition todayhttps://novelfull.com/, https://www.novelhall.com/
  • https://novelfull.com/ — plain refused, impersonated servedplain=403 impersonated=200 (53091 bytes)
  • https://www.novelhall.com/ — plain refused, impersonated servedplain=403 impersonated=200 (39027 bytes)
  • the profile sends a browser User-Agent we never wroteMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
  • no Python client string is presentMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
  • ·header set the profile emittedAccept, Accept-Encoding, Accept-Language, Host, Priority, Sec-Ch-Ua, Sec-Ch-Ua-Mobile, Sec-Ch-Ua-Platform, Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User, User-Agent, Via, X-Forwarded-For, X-Forwarded-Port, X-Forwarded-Proto, X-Forwarded-Ssl, X-Request-Start
pass S02 A pinned old profile is flagged; a family alias is not L3 0.0s

What a pass proves. A stale impersonation profile predates the post-quantum key share current builds send, so pinning one contradicts the User-Agent it claims.

  • the bare alias is accepted silently
  • a two-year-old pin is flaggedimpersonation target 'chrome99' is older than the available 'chrome136'. A stale profile is itself a signal: use the bare 'chrome' alias so the fingerprint tracks the newest supported build.
pass S03 A Cloudflare-fronted page is retrieved and parsed L2-L5L11L12 1.25s

What a pass proves. The baseline tier handles a real protected site end to end, including the soup, JSON and file helpers.

against https://novelfull.net/
  • a page came back with a titleRead free English translated Light Novel - NovelFull
  • the tier used was the cheapest one
  • nothing was recorded as binding
  • links were extracted139 followable
  • get_file wrote the body102135 bytes
  • ·explain()novelfull.net binding layer : nothing has blocked yet tier : direct pacing : 1.2s mean interval requests : 22 ok / 0 failed clearance : none ladder : direct(10) exits : direct

Diagnosis

pass S05 A challenge served with a 403 is diagnosed as a challenge L9L10 3.65s

What a pass proves. The status code says 'forbidden' and the correct remedy is a browser, not a new address. A conventional client rotates its proxy here and gets nowhere.

against https://centralnovel.com/
  • https://centralnovel.com/ -> L9 Managed JavaScript challengeHTTP 403 diagnosed solve (L9 Managed JavaScript challenge): challenge served with 403
  • https://centralnovel.com/ asks for a solve, not a rotation
  • https://ranobes.top/ -> L10 TurnstileHTTP 200 diagnosed solve (L10 Turnstile): challenge served with a success status
  • https://ranobes.top/ asks for a solve, not a rotation
pass S06 A challenge with no solver configured stops and names what is missing L9 0.41s

What a pass proves. The failure is actionable: it names the layer and the capability that would reach it, instead of exhausting retries on a 403.

against https://centralnovel.com/
  • stopped at the challenge layerL9 Managed JavaScript challenge
  • the message names the missing capabilityL9 Managed JavaScript challenge is beyond every configured tier (direct is the strongest). Configure a browser solver. [stop: L9 Managed JavaScript challenge is beyond every configured tier (direct is the strongest). Configure a browser solver.]
pass S07 A host scored as automated escalates rather than rotating L12 0.88s

What a pass proves. Nothing distinguishes the scoring tiers from outside, so the diagnosis names the strictest emit-only one and asks for a stronger tier — not a new address.

against https://novelmania.com.br/
  • diagnosed as scoring rather than reputationescalate (L12 Super Bot Fight Mode): scored as automated
  • the action is to escalate, not rotate
inconclusive S08 An identity-provider gate raises immediately and is never retried L19 0.0s

What a pass proves. The layer reads a secret. Retrying is an infinite loop against a wall, so a single request is spent and the message names the only legitimate route.

no host in the last probe is behind an identity-provider gate

    pass S09 Synthetic statuses are each diagnosed correctly, live L1L7L8L19 5.13s

    What a pass proves. The classifier is exercised against responses a real server produced, not fixtures.

    against https://httpbingo.org
    • HTTP 429: a throttle is a pacing problem-> backoff (L8 Per-zone behavioural model): rate limited
    • HTTP 503: a plain outage is worth one more try-> retry: origin unavailable
    • HTTP 401: authentication has no bypass-> refuse (L19 Identity-provider gate): authentication required (HTTP 401)
    • HTTP 404: the site's answer about a path is not a layer-> accept: HTTP 404
    • HTTP 502: an upstream error is transient-> retry: upstream error (HTTP 502)
    pass S24 A 404 is returned rather than blamed on a layer - 1.57s

    What a pass proves. The site's answer about a path says nothing about the client. Attributing it to a layer would retire a healthy address over a typo in a URL.

    against https://novelfull.net/
    • a 4xx came back as a valueHTTP 404
    • nothing was recorded as bindingNone
    pass S25 Every layer the corpus actually produces is diagnosed, and impersonation measurably reduces challenges L8L9L10L12L13L15L19 0.0s

    What a pass proves. The population-level result: what 501 real hosts do today, and what the transport profile is worth measured across all of them rather than anecdotally.

    against 501 hosts from lncrawl's source index
    • ·hosts probed330
    • ·reachable291
    • ·Cloudflare-fronted157
    • ·layers seen by a plain client{'L12 Super Bot Fight Mode': 20, 'L15 Operator edge code': 5, 'L6 Browser and JavaScript fingerprint': 10, 'L8 Per-zone behavioural model': 23, 'L9 Managed JavaScript challenge': 44}
    • ·layers seen by the impersonated client{'L10 Turnstile': 1, 'L12 Super Bot Fight Mode': 7, 'L6 Browser and JavaScript fingerprint': 10, 'L8 Per-zone behavioural model': 22, 'L9 Managed JavaScript challenge': 28}
    • impersonation converts refusals into content on real hosts35 hosts
    • and reduces the number of hosts that challenge at all44 -> 29 challenged hosts
    • at least four distinct layers were exercised by real trafficL10 Turnstile, L12 Super Bot Fight Mode, L6 Browser and JavaScript fingerprint, L8 Per-zone behavioural model, L9 Managed JavaScript challenge
    • ·reputation blocks (L1) observed: 0 of 330none
    • the reputation layer is rare here, so a browser buys more than a proxy would0 hosts

    Behaviour — layer 8

    pass S04 Warm-up and the referrer chain are visible on the wire L8 1.92s

    What a pass proves. A deep page arriving with no referrer and no prior history is a navigation pattern no person produces; both mechanisms are observable in the requests actually sent.

    against https://httpbingo.org
    • ·a first request's headersAccept, Accept-Encoding, Accept-Language, Host, Priority, Referer, Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-User, Te, User-Agent, Via, X-Forwarded-For, X-Forwarded-Port, X-Forwarded-Proto, X-Forwarded-Ssl, X-Request-Start
    • the first navigation cites the origin's front pagehttps://httpbingo.org/
    • and sec-fetch-site agrees with itsame-origin
    • the next request cites the page before ithttps://httpbingo.org/html
    • fetch metadata says same-originsame-origin
    • a cold deep page wants a homepage visit firsthttps://example.com/
    pass S10 A throttle widens the interval and keeps the address L8 2.89s

    What a pass proves. The rule that matters most: a 429 says the address works and is being asked for too much, so the remedy is arithmetic — not a new exit, which would reset the history the layer measures.

    against https://httpbingo.org
    • gave up at the behavioural layer, not at reputationL8 Per-zone behavioural model
    • the trail shows a backoff and no rotationgave up after 2 attempts; rate limited [backoff -> direct after 1.0s: rate limited | stop: gave up after 2 attempts; rate limited]
    • the interval widened1.0 -> 2.0
    • the address was NOT rotateddirect == direct
    pass S16 A throttle through Tor still does not rotate L8 3.26s

    What a pass proves. The possess-side veto holds even when a rotation is cheaply available: discarding the address would reset the accumulated history the layer reads.

    against https://httpbingo.org
    • gave up at the behavioural layer
    • no rotation appears in the trailgave up after 2 attempts; rate limited [backoff -> direct after 1.0s: rate limited | stop: gave up after 2 attempts; rate limited]
    • the Tor session was kept even though rotating was available127.0.0.1:9250#s-21975d8a9de8
    • the interval widened instead1.0
    pass S20 What was learned survives the process L8 0.42s

    What a pass proves. A process that forgets cannot accumulate, and the behavioural layer reads exactly what accumulates. The binding layer is the most valuable thing to keep.

    against https://centralnovel.com/
    • ·first run concludedbinding=L9 Managed JavaScript challenge failures=1
    • a fresh scraper starts from the conclusionL9 Managed JavaScript challenge
    • the ledger came back1
    • ·the tier it would now start withdirect
    • the store is a real file
    • written owner-only, since it holds clearance cookies600
    pass S21 Two scrapers on one host share one identity L8 1.52s

    What a pass proves. Separate state would present as two visitors who contradict each other, arriving in bursts, one of them always cold.

    against https://httpbingo.org
    • the second scraper sees the first's history1
    • both hold the same address
    • both use the same pacing clock
    • the referrer chain is sharedhttps://httpbingo.org/html

    Addresses and tor-pool — layer 1

    pass S11 Traffic leaves through a tor-pool exit L1 2.32s

    What a pass proves. The pool is wired end to end: the session key becomes the SOCKS username, the credential authenticates, and the egress IP is a Tor exit.

    against https://check.torproject.org/api/ip
    • ·this machine's own address91.73.67.32 (IsTor=False)
    • ·lease127.0.0.1:9250#s-0196914d41e6 via socks5h://s-0196914d41e6:tp_Ceif_S1QCscuzPEjVzy7TA@127.0.0.1:9250
    • egress is a Tor exit{'IsTor': True, 'IP': '192.42.116.67'}
    • the exit differs from the local address
    • ·pool instances[{'id': 0, 'exit_ip': '107.189.8.181', 'country': 'LU', 'score': 0, 'state': 'degraded', 'kinds': {'captcha': 1}}, {'id': 1, 'exit_ip': '193.200.229.243', 'country': 'NO', 'score': 0, 'state': 'degraded', 'kinds': {'blocked': 1}}, {'id': 2, 'exit_ip': '192.42.116.67', 'country': 'NL', 'score': 0, 'state': 'healthy', 'kinds': {}}, {'id': 3, 'exit_ip': '185.220.101.106', 'country': 'DE', 'score': 0…
    pass S12 A session stays pinned to one exit across requests L1L8 3.94s

    What a pass proves. Stickiness is what makes a clearance reusable and lets per-zone history accrue. A rotating address would invalidate both.

    against https://check.torproject.org/api/ip
    • all four requests left from one exit185.220.101.106, 185.220.101.106, 185.220.101.106, 185.220.101.106
    • ·exit IP185.220.101.106
    pass S13 Rotating moves the session to a different exit L1 3.35s

    What a pass proves. Rotation is a reassignment inside the pool: the endpoint URL is unchanged, but the address behind it — and therefore the identity — is not.

    against https://check.torproject.org/api/ip
    • the endpoint URL is unchangedsocks5h://127.0.0.1:9250
    • the exit identifier changed127.0.0.1:9250#s-b6ae985e4304 -> 127.0.0.1:9250#s-c2a35a324a49
    • the session key changed
    • ·exit IP before/after192.42.116.67 -> 192.42.116.67
    • egress is still Tor
    pass S14 A failure report reaches the pool with the kind derived from the layer L1L9 2.61s

    What a pass proves. This is the only signal that catches a soft block — a proxy relaying bytes cannot see a 403 or a captcha inside an HTTPS tunnel — and the pool weighs a report by its kind.

    against http://127.0.0.1:8080
    • L9 Managed JavaScript challenge -> 'captcha'
    • L1 IP reputation -> 'blocked'
    • L8 Per-zone behavioural model -> 'rate_limited'
    • None -> 'transport'
    • exactly one instance was told it hit a captchabefore={0: 1, 1: 0, 2: 0, 3: 1, 4: 0} after={0: 1, 1: 0, 2: 1, 3: 1, 4: 0}
    pass S15 Rotating between published ranges is refused, against live pool state L1 0.0s

    What a pass proves. THE headline rule. Every Tor exit is on the same published lists, so a reputation block cannot be fixed by another one — and the decision is driven by what the running pool actually offers, not by a hardcoded assumption.

    against http://127.0.0.1:8080
    • ·live pool offerstor, clearing layers []
    • a Tor pool clears nothing at layer 1
    • the planner refuses to rotateMove.STOP
    • and names the address kind as the constraintno configured exit clears the reputation layer — datacenter and Tor ranges are published, so rotating between them cannot help. A residential or mobile-carrier exit is the only thing that moves this layer
    • with a residential exit it does rotateMove.ROTATE
    pass S26 The reputation layer is not uniformly hostile to Tor L1 0.0s

    What a pass proves. A negative result worth recording: assuming every Cloudflare host refuses Tor would make the address strategy look more important than it is on this corpus.

    against 45 Cloudflare hosts via tor-pool
    • ·hosts probed through a Tor exit45
    • most were served outright28/45 served
    • ·challenged instead10/45
    • none produced a reputation block0 found
    • ·consequenceon this corpus the binding layer is the managed challenge, not the address — so a browser solver buys more than a residential proxy would.
    inconclusive S28 A real ASN ban is diagnosed as reputation and stops without wasting addresses L1 2.23s

    What a pass proves. The naturally-occurring case: this host bans the machine's whole ASN, so no client change of any kind can help. With no alternative address the stop is immediate rather than after spending the rotation budget on the same address.

    against https://www.readwn.com/

    the host is not banning this network right now: accept

    • ·observedaccept

    The ladder

    pass S17 A page is served from the archive with the original URL L0 43.27s

    What a pass proves. The cheapest way past a protected site is not to touch it. The response must carry the real URL, or relative links redirect the whole crawl into the snapshot.

    against https://centralnovel.com/
    • the archive has captures5 found
    • ·newest captures20260206124734, 20260524171405, 20260702085650
    • a snapshot came back210533 bytes
    • the response carries the ORIGINAL url, not the archive'shttps://centralnovel.com/
    • the capture timestamp is reported20260702085650
    • ·this is a host the live stack refuseshttps://centralnovel.com/ serves a challenge to the direct tier (see S05)
    pass S23 The ladder escalates on evidence and settles on the cheapest tier that works L9L14 30.47s

    What a pass proves. The planner walks up only as far as required, and the archive rescues a host the live stack refuses.

    against https://centralnovel.com/
    • ·configured ladderarchive(0) -> direct(10)
    • the challenged host was retrievedHTTP 200, 210533 bytes
    • via the archive, which the planner reached firstarchive
    • ·explain()centralnovel.com binding layer : nothing has blocked yet tier : archive pacing : 1.2s mean interval requests : 1 ok / 0 failed clearance : none ladder : archive(0) direct(10) exits : direct
    pass S27 A real browser solves a real challenge, then the cheap tier reuses it L6L7L9L10 21.67s

    What a pass proves. solve-once-and-reuse: the expensive tier runs once and the clearance is replayed on the identity that earned it, so later pages cost one HTTP request.

    against https://m.webnovel.com/
    • ·direct tier against https://www.webnovel.com/{'status': 200, 'action': 'accept', 'layer': None}
    • ·direct tier against https://m.webnovel.com/{'status': 403, 'action': 'solve', 'layer': 'L9 Managed JavaScript challenge'}
    • ·target chosenhttps://m.webnovel.com/
    • ·ladderdirect(10) -> clearance(100)
    • the challenged host was retrievedHTTP 200, 137604 bytes
    • the browser ran exactly once1 solve(s)
    • the clearance tier is what workedclearance
    • a clearance was stored
    • ·clearance cookies['NEXT_LOCALE', '__cf_bm', '_csrfToken', 'bookCitysex', 'e1', 'e2', 'sessionid', 'webnovel-content-language', 'webnovel-language', 'webnovel_uuid']
    • ·bound to User-AgentMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
    • ·valid for1797s
    • reuse #1 served without a new solveHTTP 200, 1 solve(s) total
    • reuse #2 served without a new solveHTTP 200, 1 solve(s) total
    • reuse #3 served without a new solveHTTP 200, 1 solve(s) total
    • ·explain()m.webnovel.com binding layer : L9 Managed JavaScript challenge — reads a hybrid property, solve tier : clearance pacing : 1.5s mean interval requests : 4 ok / 3 failed clearance : 1787s left ladder : direct(10) clearance(100) exits : direct

    Identity and content safety

    pass S18 Hidden and nofollow links are dropped from real pages L17 16.97s

    What a pass proves. The only layer that returns no error. Following a decoy link poisons the store and flags the session, and nothing in the response says so.

    against https://novelfull.net/
    • ·pages readhttps://tigertranslations.org/, https://chrysanthemumgarden.com/, https://novelfull.com/, https://novelfull.net/, https://dummynovels.com/, https://lightnovelfr.com/, https://freewebnovel.com/, https://ckandawrites.online/, https://www.mangaread.org/, https://mostnovel.com/
    • ·https://tigertranslations.org/35 followable, 0 rejected
    • ·https://chrysanthemumgarden.com/90 followable, 0 rejected
    • ·https://novelfull.com/112 followable, 0 rejected
    • ·https://novelfull.net/139 followable, 0 rejected
    • ·https://dummynovels.com/35 followable, 0 rejected
    • ·https://lightnovelfr.com/116 followable, 0 rejected
    • ·https://freewebnovel.com/118 followable, 1 rejected
    • ·https://ckandawrites.online/181 followable, 0 rejected
    • ·https://www.mangaread.org/198 followable, 1 rejected
    • ·https://mostnovel.com/63 followable, 6 rejected
    • real links survive across every page{'kept': 1087, 'rejected': 8}
    • genuine decoy markers were found and dropped{'inside an element hidden by inline style': 1, 'rel=nofollow': 7}
    • ·rejection reasons seen on real pages{'inside an element hidden by inline style': 1, 'rel=nofollow': 7}
    • ·false positives fixed during this runicon-font anchors and overlay/text anchor pairs were being dropped; both are real navigation. 11 of 11 rejections on one host were wrong.
    • a page from the site is not suspected
    • off-topic prose is flaggedvocabulary overlap 0% against 3 known pages (threshold 30%)
    pass S19 A clearance is refused under an identity that did not earn it L9 0.0s

    What a pass proves. The classic rotating-proxy failure, made structurally impossible: the cookie is bound to the address, User-Agent and TLS profile together.

    • valid under the identity that earned it
    • refused after the address movedthe identity changed since the clearance was earned
    • refused after the User-Agent changedthe identity changed since the clearance was earned
    • refused after the TLS profile changedthe identity changed since the clearance was earned
    pass S22 Requests are signed, and the signature verifies L18 0.74s

    What a pass proves. The one layer with no bypass. Deployed fail-open today, so a valid signature is a positive identification that skips the challenge machinery entirely.

    against https://httpbingo.org
    • the server received Signature-Inputsig1=("signature-agent" "@authority");created=1785351281;expires=1785351581;keyid="yHjaAJTRFyisOAo6K4khacH2-oB3bMa7oFUM7_i5Zio";alg="ed25519";tag="web-bot-auth"
    • the server received Signaturesig1=:OOAHJJ2IjeU5x6O/IoYwUygWwCFs99sLGNiye49Sax+HOalZ/faEpkREm5rQe/ovL8+CrFL+4m
    • the agent was declared
    • the tag is web-bot-auth
    • the algorithm is ed25519
    • the signature verifies against the published key
    • and does not verify for another authority
    • ·directory to publish/.well-known/http-message-signatures-directory -> kid yHjaAJTRFyisOAo6K4khacH2-oB3bMa7oFUM7_i5Zio

    Other

    pass S29 A proxy that refuses our credential is not blamed on the site L0 0.02s

    What a pass proves. A failure on our side of the proxy must not become a detection story. The SOCKS5 handshake has no status code, so this arrives as a bare transport error — and attributing it to layer 1 caused a rotation, a false 'blocked' report against a healthy exit, and a persisted verdict that the site refuses our address.

    against tor-pool with a deliberately wrong token
    • nothing is attributed to a layerNone
    • the message names the credential, not the sitethe proxy rejected the credential (SOCKS5 handshake) [stop: the proxy rejected the credential (SOCKS5 handshake)]
    • no rotation was attemptedthe proxy rejected the credential (SOCKS5 handshake) [stop: the proxy rejected the credential (SOCKS5 handshake)]
    • no layer was written to the origin's memory — the durable halfNone
    • no pool instance was reported as failing{0: 0, 1: 0, 2: 0, 3: 0, 4: 0} -> {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}

    Environment

    scraper1.0.0
    Python (harness)Python 3.9.6
    Python (browser tier)Python 3.12.13
    curl_cffi0.13.0
    impersonation profilechrome136
    nodriver0.50.3
    browserGoogle Chrome (headed)
    tor-pooltor-pool:sessionscope
    tor-pool exits5 instances
    local egress91.73.67.32

    Reproducing this

    uv run poe live-probe    # classify every host in lncrawl's source index (~4 min)
    uv run poe live-tor      # classify Cloudflare hosts through tor-pool
    uv run poe live          # run every scenario
    uv run poe live-report   # rebuild this page
    
    # the clearance tier needs its own interpreter (nodriver: Python 3.10-3.13) and Chrome
    /tmp/scr312/bin/python livetest/clearance.py

    Requirements and the politeness rules are in livetest/README.md. Targets are looked up from the probe rather than hardcoded, because site configuration moves: one host in this corpus switched from Turnstile to plain scoring between two runs an hour apart.