Rendering JavaScript nello scraping: quando serve e quanto costa
Come capire se una pagina richiede un browser vero invece di un fetch semplice, perché il rendering costa di più ovunque, e i moltiplicatori in crediti, agosto 2026.

Un fetch HTTP semplice ti dà l’HTML che il server ha mandato. Sempre più spesso, non è la pagina che vede un visitatore. Il divario tra i due è il rendering JavaScript, ed è il motivo numero uno per cui uno scraper che funziona su un sito fallisce in silenzio sul prossimo.
Come capisci se una pagina ha bisogno di rendering?
Due controlli, entrambi veloci, prima di scrivere qualsiasi codice di scraping:
Confronta view-source con il DOM. In un browser, view-source (Ctrl+U o Cmd+Option+U) mostra esattamente quello che riceverebbe un fetch semplice: l’HTML grezzo dal server, prima che qualunque script giri. Ora apri i dev tools e guarda il pannello Elements, che mostra il DOM dopo l’esecuzione di JavaScript. Se il contenuto che vuoi, prezzi, annunci, recensioni, è presente in Elements ma assente o sostituito da uno scheletro di caricamento in view-source, la pagina costruisce quel contenuto lato client.
Fai il fetch da solo senza browser. Lo stesso controllo da codice, senza bisogno dei dev tools:
import requests
resp = requests.get("https://example.com/products", timeout=30)
print(len(resp.text))
print("add-to-cart" in resp.text) # sostituisci con una stringa che sai essere nel contenuto reale
Se una stringa marker che vedi sulla pagina live è assente da resp.text, un fetch semplice non ti sta dando il contenuto, e ti serve qualcosa che esegua JavaScript prima di leggere la pagina.
Un motivo rapido per cui questo succede più di prima: i framework moderni (React, Vue, Svelte e affini) spediscono un guscio HTML quasi vuoto e costruiscono la pagina vera nel browser. È una scelta architetturale deliberata del sito, non una misura anti-scraping, ma per te produce lo stesso risultato in entrambi i casi.
Perché il rendering costa di più, ovunque?
Un fetch semplice è un socket, una richiesta, una risposta. Il rendering significa mettere in piedi qualcosa di simile a un browser completo: caricare il DOM, eseguire gli script, aspettare che le chiamate di rete si stabilizzino, a volte scorrere per attivare contenuto lazy-loaded. Sono CPU e memoria reali per pagina, tenute occupate per tutta la durata del rendering, non i microsecondi di un fetch statico.
Questa struttura di costo non è specifica di un fornitore. Ogni provider che offre sia un fetch statico che uno con rendering fa pagare di più il secondo, perché è genuinamente infrastruttura più costosa da far girare su scala: un pool di processi browser reali invece di un connection pool. Se un preventivo per scraping con rendering ha lo stesso prezzo di uno statico, guarda bene cosa intendono con “rendering” nella loro documentazione.
Quanto costa il rendering su FastAF
/scrape ha prezzi su quattro livelli, e il moltiplicatore segue il costo reale di cosa gira dietro la richiesta:
| Modalità | Parametro | Crediti |
|---|---|---|
| Fetch statico | (default) | 1 |
| Rendering JS | dynamic=true |
5 |
| Proxy premium, senza rendering | premium=true |
10 |
| Rendering JS + proxy premium | dynamic=true&premium=true |
25 |
Da statico è un salto di 5x per aggiungere il rendering, e un salto di 25x per aggiungere sia rendering che routing su proxy premium. È il costo reale dell’infrastruttura extra, non un markup arbitrario, ed è per questo che vale la pena controllare se il rendering serve davvero prima di usarlo di default.
La strategia: prova statico prima, sali di livello solo se fallisce
La maggior parte dei job di scraping mescola pagine che hanno bisogno di rendering con pagine che non ne hanno bisogno, anche dentro lo stesso sito. Pagare dynamic=true su ogni richiesta quando solo alcune pagine ne hanno bisogno è il modo più comune di spendere troppo su questo endpoint:
import requests
API_KEY = "faf_live_..." # la tua chiave FastAF
def scrape(url, dynamic=False, premium=False, wait=None):
params = {"url": url}
if dynamic:
params["dynamic"] = "true"
if premium:
params["premium"] = "true"
if wait:
params["wait"] = wait
resp = requests.get(
"https://api.fastaf.co/v1/scrape",
params=params,
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=90,
)
resp.raise_for_status()
return resp.text
def scrape_smart(url, marker):
html = scrape(url)
if marker in html:
return html # statico è bastato, speso 1 credito
html = scrape(url, dynamic=True) # sali di livello, spesi 5 crediti
return html
marker è una stringa che sai comparire nel contenuto reale, un titolo di prodotto, un formato di prezzo, qualunque cosa mancherebbe da una pagina guscio. Se il fetch statico ce l’ha già, hai risparmiato 4 crediti su quell’URL. Se non ce l’ha, sali di livello una volta, non per un’ipotesi.
Usa premium=true separatamente: riguarda la qualità del proxy e superare i blocchi basati su IP, non il JavaScript. Un sito può avere bisogno di uno, di entrambi o di nessuno dei due. wait (millisecondi) vale la pena impostarlo esplicitamente sulle richieste dynamic=true per le pagine che caricano contenuto dopo che una chiamata API finisce, invece di assumere che l’attesa di default basti.
La versione breve
Confronta view-source con il DOM renderizzato, o fai il fetch della pagina da solo, prima di assumere che ti serva un browser. Statico è 1 credito, il rendering è 5, i proxy premium sono 10, insieme sono 25: i moltiplicatori esistono perché l’infrastruttura browser reale costa genuinamente di più da far girare. Prova statico per primo e sali a dynamic=true solo sugli URL dove il contenuto marker manca davvero, e pagherai il sovrapprezzo del rendering solo dove è meritato.