Risposta rapida
Come chiamo un'API SERP da Python?
Invia una richiesta GET a https://serpcrypto.com/api/v1/search con requests o httpx, la tua chiave nell'header Authorization: Bearer e la query in params, poi leggi response.json(). SerpCrypto addebita 1 credito per ricerca riuscita, a partire da 0,30 USD/1K ricerche, e i retry delle chiamate fallite sono gratuiti. Paga in criptovalute, senza KYC. Il codice qui sotto richiede solo requests o httpx.
Setup di Python in breve
| Installa | pip install requests o pip install httpx |
|---|---|
| Chiave | Variabile d'ambiente SERPCRYPTO_API_KEY |
| Header | Authorization: Bearer <key> |
| Timeout | Da 60 a 90 secondi: una ricerca gira live |
| Retry | Solo 429, 502 e 504, con backoff; gli errori sono gratuiti |
| Ricerche in parallelo | Fino a concurrency_limit da GET /credits |
Come imposto un client Python riutilizzabile?
Crea una requests.Session con la chiave nei suoi header e una policy di retry per i tre errori temporanei. Una sessione riusa le connessioni, così un ciclo di ricerche passa meno tempo negli handshake TLS. Salva il file come serp_client.py; gli esempi successivi lo importano.
Retry di urllib3 aspetta sempre di più prima di ogni nuovo tentativo e rispetta un header Retry-After se ne viene inviato uno. Non ritenta mai 400, 401 o 402, che richiedono una correzione, non un secondo tentativo.
"""serp_client.py: a small, reusable client for the SerpCrypto SERP API (requests)."""
import os
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
API_URL = "https://serpcrypto.com/api/v1/search"
CREDITS_URL = "https://serpcrypto.com/api/v1/credits"
def make_session():
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["SERPCRYPTO_API_KEY"]
retry = Retry(
total=4,
backoff_factor=1.0, # waits about 1 s, 2 s, 4 s between attempts
status_forcelist=(429, 502, 504),
allowed_methods=("GET",),
respect_retry_after_header=True,
)
session.mount("https://", HTTPAdapter(max_retries=retry))
return session
SESSION = make_session()
def search(q, **params):
"""Run one search (1 credit if it succeeds) and return the parsed JSON."""
r = SESSION.get(API_URL, params={"q": q, **params}, timeout=90)
r.raise_for_status()
return r.json()
def credits():
"""Free: credits left, used today and the concurrency limit."""
r = SESSION.get(CREDITS_URL, timeout=30)
r.raise_for_status()
return r.json()
if __name__ == "__main__":
data = search("project management software", gl="us", hl="en")
for item in data.get("organic_results", []):
print(item["position"], item["link"])
print(credits())Come raccolgo più di 10 risultati?
Chiedi le pagine successive una alla volta. Circa 10 risultati organici per pagina, come Google serve da quando ha smesso di rispettare num=100 a settembre 2025; ogni pagina in più costa un altro credito. Fermati appena manca pagination.next_page: non c'è altro da recuperare e tieni i crediti.
Per la maggior parte del lavoro di rank tracking bastano la prima pagina o le prime tre, perché lì si concentrano i clic. Trenta risultati per una keyword costano tre crediti.
from serp_client import search
def top_results(q, pages=3, **params):
"""Collect organic results over several pages: each page is 1 credit."""
rows = []
for page in range(1, pages + 1):
data = search(q, page=page, **params)
for item in data.get("organic_results", []):
rows.append({"page": page, "position": item["position"], "title": item["title"], "link": item["link"]})
if not data.get("pagination", {}).get("next_page"):
break # no further page: stop early and save credits
return rows
rows = top_results("crm for small business", pages=3, gl="gb", hl="en")
print(len(rows), "results")Come eseguo molte ricerche in parallelo?
Usa httpx.AsyncClient con un asyncio.Semaphore dimensionato al limite del tuo account. Lo script legge concurrency_limit dall'endpoint dei crediti gratuiti, così si adatta quando il supporto alza il tuo limite. Le ricerche oltre il limite riceverebbero comunque 429; il semaforo evita di sprecare quei tentativi.
Ogni task ritenta gli errori temporanei con una pausa crescente e restituisce None dopo quattro fallimenti, così una query ostinata non blocca mai il batch.
"""Run many searches in parallel without exceeding the account's concurrency limit."""
import asyncio
import os
import httpx
API_URL = "https://serpcrypto.com/api/v1/search"
CREDITS_URL = "https://serpcrypto.com/api/v1/credits"
HEADERS = {"Authorization": "Bearer " + os.environ["SERPCRYPTO_API_KEY"]}
async def search_one(client, gate, q):
async with gate: # at most N searches at the same time
for attempt in range(4):
r = await client.get(API_URL, params={"q": q, "gl": "us", "hl": "en"})
if r.status_code in (429, 502, 504):
await asyncio.sleep(2 ** attempt)
continue
r.raise_for_status()
return q, r.json()
return q, None
async def main(queries):
async with httpx.AsyncClient(headers=HEADERS, timeout=90) as client:
limits = (await client.get(CREDITS_URL)).json()
gate = asyncio.Semaphore(limits["concurrency_limit"])
results = await asyncio.gather(*(search_one(client, gate, q) for q in queries))
for q, data in results:
first = (data or {}).get("organic_results", [{}])[0].get("link")
print(f"{q!r}: {first}")
asyncio.run(main(["best vpn router", "home espresso machine", "4k monitor for coding"]))Come salvo le posizioni giornaliere in un file CSV?
Aggiungi una riga per risultato, con il giorno, la keyword, la posizione, il link e le impostazioni applicate dall'API. Leggere gl e device da search_parameters invece che dalle tue variabili significa che il file registra ciò che è stato effettivamente usato.
Esegui lo script una volta al giorno con cron o con lo Task Scheduler di Windows. Tre keyword su mobile costano tre crediti al giorno, circa 90 al mese.
import csv
from datetime import date
from serp_client import search
KEYWORDS = ["standing desk", "ergonomic chair", "monitor arm"]
FIELDS = ["day", "keyword", "position", "title", "link", "gl", "device"]
with open("rankings.csv", "a", newline="", encoding="utf-8") as fh:
writer = csv.DictWriter(fh, fieldnames=FIELDS)
if fh.tell() == 0:
writer.writeheader()
for kw in KEYWORDS:
data = search(kw, gl="gb", hl="en", device="mobile")
applied = data["search_parameters"]
for item in data.get("organic_results", []):
writer.writerow({
"day": date.today().isoformat(),
"keyword": kw,
"position": item["position"],
"title": item["title"],
"link": item["link"],
"gl": applied["gl"],
"device": applied["device"],
})Quale client HTTP Python devo usare?
Usa requests per script e cron job: è semplice e il suo adapter di retry è maturo. Usa httpx quando ti serve codice asincrono, come per una web app, un agente o un batch di centinaia di keyword. Entrambi inviano la stessa richiesta e ricevono lo stesso JSON; all'API non importa quale scegli.
Evita timeout molto brevi con entrambe le librerie. Un client che si arrende dopo cinque secondi può rinviare una ricerca ancora in corso e pagarla due volte.
Quanto mi costerà lo script?
Conta le ricerche, non i risultati: una chiamata è un credito quando va a buon fine. Un job di 500 keyword, una pagina ciascuna, ogni giorno per 30 giorni, sono 15.000 ricerche. Sul pack 25.000 ricerche (18,75 USD) resta margine in abbondanza, e I crediti di ricerca prepagati non scadono mai: le ricerche non utilizzate restano sul tuo account finché non le usi.
Controlla il saldo dal codice con l'helper credits() qui sopra; non costa nulla.
Domande su Python
C'è un package SerpCrypto su PyPI?
No. Gli esempi chiamano l'endpoint HTTPS con requests o httpx, due librerie molto diffuse, quindi non c'è nulla di nostro da installare o aggiornare. Il client qui sopra è di circa 40 righe ed è tuo.
Quali versioni di Python supporta questo codice?
Python 3.9 e successive. L'esempio asincrono usa asyncio.run, disponibile da Python 3.7, e il resto usa solo la standard library più requests o httpx.
Posso usare l'API da un Jupyter notebook?
Sì. Imposta SERPCRYPTO_API_KEY prima di avviare Jupyter, oppure leggi la chiave con getpass dentro il notebook così non finisce in una cella salvata. In un notebook, usa await main(...) invece di asyncio.run(main(...)).
Come trasformo i risultati in un pandas DataFrame?
Passa la lista di dizionari dei risultati a pandas.DataFrame(data["organic_results"]). Ogni campo diventa una colonna; i campi opzionali mancanti come date diventano celle vuote.