Como monitorar rankings de keywords com Python
Um rank tracker mínimo em Python: consulta, extrai a posição de organic_results, guarda no SQLite, e a matemática de créditos por trás da profundidade.

Um rank tracker não precisa ser um produto. Para um punhado de keywords e um domínio, é um loop, um arquivo de banco de dados, e uma forma de perceber o dia em que algo se moveu. Aqui está tudo em cerca de sessenta linhas.
O formato
Quatro passos, rodados em um schedule: manda cada keyword pelo /serp com país e location fixos, encontra seu domínio em organic_results, escreve a posição no SQLite, e compara com a linha de ontem.
import sqlite3
import requests
from datetime import date
from urllib.parse import urlparse
API_KEY = "faf_live_..."
DB_PATH = "rankings.db"
DOMAIN = "fastaf.co"
COUNTRY = "us"
KEYWORDS = [
"web scraping api",
"serp api pricing",
"google search api alternative",
]
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute("""
CREATE TABLE IF NOT EXISTS rankings (
checked_on TEXT,
keyword TEXT,
domain TEXT,
position INTEGER,
PRIMARY KEY (checked_on, keyword, domain)
)
""")
return conn
Buscando uma posição
organic_results já vem com um campo rank em cada item, então encontrar o seu é uma questão de comparar o hostname de link com o domínio, exato ou como subdomínio (um simples domain in link também casa com https://example.org/review/fastaf.co). Profundidade é um loop sobre page, já que o Google não retorna mais 100 resultados em uma requisição:
def on_domain(link, domain):
host = urlparse(link).hostname or ""
return host == domain or host.endswith("." + domain)
def find_position(keyword, domain, country=COUNTRY, location=None, max_pages=1):
for page in range(max_pages):
params = {"query": keyword, "country": country, "page": page}
if location:
params["location"] = location
resp = requests.get(
"https://api.fastaf.co/v1/serp",
params=params,
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=60,
)
resp.raise_for_status()
for result in resp.json().get("organic_results", []):
if on_domain(result.get("link", ""), domain):
return result["rank"]
return None # não encontrado dentro de max_pages
location restringe a busca a um lugar específico (uma cidade, por exemplo) em vez de só um país, o que importa para qualquer coisa com intenção local. page é o controle de profundidade: cada página é uma requisição faturável separada, dez resultados por vez.
Monitorando uma lista e salvando a rodada do dia
def track(conn, keywords, domain, max_pages=1):
today = date.today().isoformat()
for keyword in keywords:
position = find_position(keyword, domain, max_pages=max_pages)
conn.execute(
"INSERT OR REPLACE INTO rankings VALUES (?, ?, ?, ?)",
(today, keyword, domain, position),
)
print(f"{keyword}: {position}")
conn.commit()
if __name__ == "__main__":
conn = init_db()
track(conn, KEYWORDS, DOMAIN)
Rode isso uma vez por dia (cron, uma função agendada, o que você já usa) e rankings.db acumula uma linha por keyword por dia.
Calculando o delta diário
O objetivo de guardar histórico é o delta, não o snapshot. Puxe as últimas duas linhas de cada keyword e compare:
def deltas(conn, domain):
rows = conn.execute("""
SELECT keyword, checked_on, position
FROM rankings
WHERE domain = ?
ORDER BY keyword, checked_on
""", (domain,)).fetchall()
by_keyword = {}
for keyword, checked_on, position in rows:
by_keyword.setdefault(keyword, []).append((checked_on, position))
for keyword, history in by_keyword.items():
if len(history) < 2:
continue
(_, prev), (_, curr) = history[-2], history[-1]
if prev is None or curr is None:
continue
change = prev - curr # positivo significa que subiu
if change != 0:
print(f"{keyword}: {prev} -> {curr} ({change:+d})")
A matemática de créditos por trás da “profundidade”
Essa é a parte que silenciosamente destrói um orçamento de créditos se não for dimensionada com antecedência. Uma requisição /serp custa 5 créditos independente da profundidade, mas “top 100” são dez requisições separadas, não uma, já que o Google não respeita mais uma única requisição retornando 100 resultados.
| Configuração | Requisições/dia | Créditos/dia | Créditos (30 dias) | Pacote que cobre |
|---|---|---|---|---|
| 500 keywords, diário, profundidade 10 (só page 0) | 500 | 2.500 | 75.000 | Growth, $29 |
| 500 keywords, diário, profundidade 100 (10 páginas) | 5.000 | 25.000 | 750.000 | Pro, $299 |
Profundidade 10 checa se você aparece na primeira página, o que basta para a maioria dos trackings. Profundidade 100 é o que você precisa se também quiser saber onde está entre as páginas 2 e 10, e custa dez vezes mais para a mesma lista de keywords. Defina max_pages deliberadamente, não por hábito: um tracker que sobe de profundidade 10 para profundidade 100 sem ninguém perceber é uma fatura em créditos 10x maior para a mesma lista de keywords.
Checar duas vezes por dia em vez de uma dobra todo número dessa tabela; checar semanalmente em vez de diariamente divide por sete. Escolha uma frequência que o movimento de ranking que importa para você realmente exige, não o intervalo mais curto que seu scheduler suporta.
Resumo
Um rank tracker é uma chamada /serp, uma comparação de domínio contra organic_results, e uma tabela SQLite, nada mais exótico que isso. O número que decide sua fatura é profundidade vezes frequência vezes quantidade de keywords, não a ferramenta. Calcule isso antes de agendar o job: os preços têm o custo em créditos por pacote, e a documentação cobre o conjunto completo de parâmetros do /serp, incluindo location e device.