requests + BeautifulSoup vs una scraping API: quando il fai-da-te basta
Per siti statici senza anti-bot, requests e BeautifulSoup costano zero e funzionano bene. Ecco esattamente dove smette di essere vero.

C’è una versione di questo post che prova a venderti un’API per un problema che requests e BeautifulSoup risolvono già. Questo non è quel post. Se il sito che stai scrapando è HTML statico senza anti-bot davanti, la via fai-da-te non costa niente e funziona bene, e non dovresti pagare niente per farla.
Quando il fai-da-te basta davvero
Dillo chiaramente: per una pagina statica, uno script, e un job poco frequente, questa è tutta la soluzione.
import requests
from bs4 import BeautifulSoup
resp = requests.get("https://example.com/products", timeout=30)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
for card in soup.select(".product-card"):
name = card.select_one(".product-name").get_text(strip=True)
price = card.select_one(".price").get_text(strip=True)
print(name, price)
Nessuna dipendenza oltre due librerie note, nessuna bolletta proxy, nessun credito speso. Questo è corretto per: un sito che renderizza HTML completo lato server, nessun CAPTCHA o blocco basato su IP dopo poche richieste, e un job che esegui ogni tanto invece che continuamente contro migliaia di URL. Uno script una tantum per un progetto universitario, uno strumento interno che controlla una manciata di pagine al giorno, un monitoraggio prezzi personale su un paio di schede prodotto: tutto questo è territorio fai-da-te, e mettere un’API davanti è puro overhead.
Dove smette di funzionare
Quattro cose rompono questa configurazione, e si rompono in modo indipendente, ed è per questo che “basta aggiungere proxy” o “basta aggiungere una wait()” toppa solo un sintomo alla volta:
Anti-bot. Sfide Cloudflare, PerimeterX, rate limiting che degenera in un CAPTCHA o un 403 secco dopo una manciata di richieste. requests non ha un fingerprint da browser, nessun comportamento di cookie jar che imiti una sessione reale, e nessun modo di risolvere una sfida. Non è un problema di header che risolvi copiando una stringa User-Agent; i siti che ci tengono rilevano specificamente l’assenza di un browser reale.
Rendering JS. Se il dato che vuoi viene iniettato da JavaScript dopo il caricamento dell’HTML iniziale (un’app React o Vue, contenuto caricato via chiamata XHR in background, infinite scroll), resp.text semplicemente non lo contiene. BeautifulSoup fa il parsing di quello che il server ha mandato, non di quello che il browser renderizzerebbe. Questo è il motivo numero uno per cui uno scraper funzionante smette di colpo di dare risultati: il sito non ha cambiato il blocco, ha cambiato il rendering.
Scala. Una dozzina di pagine al giorno è uno script. Diecimila pagine al giorno è infrastruttura: gestione della concorrenza, logica di retry, un pool di proxy dimensionato sul volume di richieste, monitoraggio del tasso di ban che sale. Niente di tutto questo è difficile singolarmente, ma insieme è un piccolo pezzo di infrastruttura che ora possiedi e devi tenere in piedi.
Manutenzione. I selettori marciscono. Un redesign del sito cambia .product-card in .product-tile e il tuo parser silenziosamente non restituisce niente, o peggio, restituisce il campo sbagliato mappato sul nome giusto. Questo è il costo che non si vede nella prima settimana e non sparisce mai del tutto.
La tabella delle decisioni
| Segnale | Fai-da-te (requests + BeautifulSoup) | Scraping API |
|---|---|---|
| HTML statico, nessun blocco, volume basso | Sì | Eccessivo |
| Job occasionale / una tantum | Sì | Eccessivo |
| Il sito restituisce 403/CAPTCHA dopo poche richieste | No | Sì (premium=true) |
| Contenuto caricato via JavaScript dopo il caricamento pagina | No | Sì (dynamic=true) |
| Migliaia di pagine, senza supervisione, su pianificazione | Faticoso da gestire | Sì |
| Il parser deve sopravvivere ai redesign senza che tu te ne accorga | Nessuna garanzia in entrambi i casi | I campi JSON strutturati riducono (non eliminano) il problema |
Quando superi la linea
Su FastAF, /scrape costa 1 credito per una pagina statica, esattamente il caso sopra ma senza che tu debba eseguire la richiesta da solo:
import requests
API_KEY = "faf_live_..."
resp = requests.get(
"https://api.fastaf.co/v1/scrape",
params={"url": "https://example.com/products"},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=60,
)
resp.raise_for_status()
La stessa chiamata con dynamic=true (5 crediti) renderizza prima la pagina con un browser vero, così il contenuto iniettato da JavaScript è presente nella risposta. premium=true (10 crediti) instrada attraverso una rete residenziale per i siti che bloccano sulla reputazione dell’IP. Insieme (25 crediti) coprono una pagina renderizzata via JS che deve anche sembrare un visitatore residenziale reale, che è la combinazione più difficile e costosa e va usata solo quando il sito la richiede davvero.
Il punto dei livelli in crediti è che paghi esattamente per il problema che hai. Una pagina statica non dovrebbe mai costare più di 1 credito, e pagare premium=true su un sito senza anti-bot è denaro speso su un problema che non hai.
La versione breve
Parti con requests e BeautifulSoup. È gratis e basta per una grande fetta dei task di scraping. Ricorri a un’API specificamente quando incontri una delle quattro modalità di fallimento sopra, anti-bot, rendering JS, scala, o un onere di manutenzione che non vuoi possedere, e paga per URL esattamente per quel problema invece di costruire un pool di proxy e un’abitudine di manutenzione dei parser attorno ad esso. Il piano gratuito ti dà 500 crediti senza carta se vuoi testare dove i tuoi target reali si posizionano in quella tabella.