SEO Avançado & Data Science

Detecção Automatizada de Anomalias na Search Console API com Python e Prophet: Como Antecipar Quedas de Tráfego por Core Updates

SEO Avançado & Data Science
Anomalias no Search Console com Prophet
Cayo Galotti Especialista Google

Monitorar o desempenho orgânico pela interface padrão do Google Search Console costuma produzir diagnósticos tardios. Quando o Google inicia a implementação de um Core Update, a volatilidade da SERP se estende por duas a três semanas. Equipes que dependem da inspeção visual de gráficos com comparações arbitrárias de 7 ou 28 dias demoram dias para diferenciar um recuo sazonal de fim de semana de uma desvalorização algorítmica real.

A detecção de anomalias search console python prophet resolve essa defasagem ao tratar o fluxo de cliques e impressões como uma série temporal estocástica decomposta em componentes matemáticos claros. Em vez de olhar médias móveis simples, o modelo do Prophet isola a sazonalidade semanal, o efeito de feriados móveis e a tendência de longo prazo. Quando o tráfego observado rompe o limite inferior da faixa de incerteza estatística, você recebe um alerta objetivo de anomalia negativa logo no início do rollout do algoritmo.

A limitação estrutural dos gráficos nativos do Search Console

A interface do Search Console oferece uma visão agregada que esconde padrões essenciais para a tomada de decisão. O comportamento padrão da maioria dos analistas é abrir o relatório de desempenho e comparar os últimos 28 dias com o período anterior. Esse método falha por três razões estatísticas fundamentais.

Primeiro, o efeito do dia da semana distorce comparações diretas. Portais B2B sofrem quedas previsíveis de 40% a 60% no tráfego durante sábados e domingos. Por outro lado, e-commerces e sites de entretenimento registram picos nos mesmos dias. Se uma janela de comparação de 28 dias contém cinco finais de semana enquanto o período base teve quatro, a comparação percentual apresenta uma variação artificial.

Segundo, feriados móveis como Carnaval, Páscoa e Corpus Christi geram choques de demanda que não decorrem de perda de posições na SERP. O tráfego despenca porque o volume de buscas diminuiu temporariamente, não porque o site perdeu relevância técnica ou autoridade tópica.

Terceiro, o atraso de dados no Search Console varia entre 48 e 72 horas. Se você aguarda uma média móvel de 7 dias acusar uma tendência de baixa, o site já acumulou mais de dez dias de perda contínua de receita antes que qualquer ação de contenção seja iniciada. Conectar um pipeline automatizado com a Search Console API com Python e BigQuery elimina essa lentidão operacional e permite transformar dados brutos em decisões analíticas imediatas.

Como o modelo aditivo do Prophet funciona em SEO

Desenvolvido pela equipe de engenharia do Meta, o Prophet foi desenhado para lidar com séries temporais de negócios que exibem sazonalidade forte em múltiplos ciclos e mudanças pontuais de tendência.

O modelo decompõe uma série temporal de cliques ou impressões $y(t)$ em quatro partes aditivas:

$$y(t) = g(t) + s(t) + h(t) + \epsilon_t$$

Cada termo possui uma função matemática específica:

  1. Tendência $g(t)$: modela o crescimento ou declínio não periódico do site. O Prophet utiliza curvas lineares por partes (piecewise linear) com pontos de mudança (changepoints) identificados automaticamente no histórico.
  2. Sazonalidade $s(t)$: captura oscilações periódicas por meio de séries de Fourier. No contexto do Search Console, o ciclo semanal é o mais evidente, acompanhado pelo ciclo anual em portais de conteúdo ou varejo.
  3. Feriados e eventos $h(t)$: adiciona coeficientes binários para datas especiais onde o comportamento do usuário desvia abruptamente do padrão, como Black Friday, Natal ou feriados nacionais.
  4. Erro residual $\epsilon_t$: representa o ruído aleatório não explicado pelo modelo, com distribuição normal em torno de zero.

Diferente de modelos ARIMA, que exigem estacionariedade estrita e diferenciações complexas, o Prophet acomoda valores faltantes, alterações de tendência históricas e variações sazonais sem necessidade de pré-processamento agressivo.

Ao treinar o modelo sobre uma janela histórica de 180 a 365 dias, o algoritmo projeta o valor esperado ($\hat{y}$) para cada data e calcula um intervalo de confiança baseado na variância do erro residual, delimitado por um limite superior ($\hat{y}{upper}$) e um limite inferior ($\hat{y}{lower}$).

Uma anomalia ocorre exatamente quando o tráfego real observado no Search Console fica fora desse túnel de previsão:

$$y_{real} < \hat{y}_{lower}$$

Se os cliques caem em uma terça-feira, mas o valor permanece dentro da margem inferior de 95% do modelo, o evento é ruído estatístico ou oscilação tolerável. Se o valor rompe o limite inferior por dois dias seguidos, o sistema detecta uma perda anômala com alta significância estatística.

Extração automatizada de dados via Search Console API

Para alimentar o modelo, é necessário extrair a série temporal diretamente pela Google Search Console API. O método recomendado utiliza uma conta de serviço (Service Account) com permissões de leitura na propriedade do Search Console.

A biblioteca oficial google-api-python-client gerencia as requisições autenticadas. O código abaixo extrai os cliques diários agregados do site nos últimos 365 dias:

import datetime
from google.oauth2 import service_account
from googleapiclient.discovery import build
import pandas as pd

def extrair_dados_gsc(site_url: str, creds_path: str, dias_historico: int = 365) -> pd.DataFrame:
    credentials = service_account.Credentials.from_service_account_file(
        creds_path,
        scopes=['https://www.googleapis.com/auth/webmasters.readonly']
    )
    service = build('searchconsole', 'v1', credentials=credentials)
    
    data_fim = datetime.date.today() - datetime.timedelta(days=3)
    data_inicio = data_fim - datetime.timedelta(days=dias_historico)
    
    payload = {
        'startDate': data_inicio.strftime('%Y-%m-%d'),
        'endDate': data_fim.strftime('%Y-%m-%d'),
        'dimensions': ['date'],
        'rowLimit': 25000
    }
    
    response = service.searchanalytics().query(
        siteUrl=site_url,
        body=payload
    ).execute()
    
    linhas = response.get('rows', [])
    dados = []
    for linha in linhas:
        dados.append({
            'ds': pd.to_datetime(linha['keys'][0]),
            'y': float(linha['clicks'])
        })
        
    df = pd.DataFrame(dados)
    df = df.sort_values('ds').reset_index(drop=True)
    return df

Note que a data final foi configurada para três dias antes da data atual. Isso previne que dias com dados parciais no Search Console gerem falsas anomalias no final da série temporal.

Treinando o Prophet e calculando intervalos de incerteza

Com o DataFrame estruturado contendo as colunas padrão exigidas pelo Prophet (ds para data e y para a métrica avaliada), o próximo passo é configurar o modelo com a inclusão de feriados brasileiros e calibrar o intervalo de confiança.

from prophet import Prophet
import numpy as np

def detectar_anomalias_prophet(df: pd.DataFrame, interval_width: float = 0.95) -> pd.DataFrame:
    modelo = Prophet(
        interval_width=interval_width,
        yearly_seasonality=True,
        weekly_seasonality=True,
        daily_seasonality=False,
        changepoint_prior_scale=0.05
    )
    
    # Inclusao de feriados nacionais do Brasil
    modelo.add_country_holidays(country_name='BR')
    
    # Ajuste do modelo
    modelo.fit(df)
    
    # Predicao sobre todo o historico observado
    forecast = modelo.predict(df[['ds']])
    
    # Consolidacao dos resultados
    resultado = df.merge(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']], on='ds')
    
    # Identificacao de anomalias
    resultado['anomalia_negativa'] = resultado['y'] < resultado['yhat_lower']
    resultado['anomalia_positiva'] = resultado['y'] > resultado['yhat_upper']
    
    # Calculo do desvio relativo em porcentagem para anomalias negativas
    resultado['desvio_pct'] = np.where(
        resultado['anomalia_negativa'],
        ((resultado['y'] - resultado['yhat_lower']) / resultado['yhat_lower']) * 100,
        0.0
    )
    
    return resultado

O parâmetro changepoint_prior_scale=0.05 controla a flexibilidade da linha de tendência. Valores muito altos fazem o modelo se ajustar excessivamente a flutuações de curto prazo, tratando uma queda de algoritmo como uma simples mudança natural de tendência. Manter o valor conservador garante que quedas bruscas sejam sinalizadas como anomalias em vez de assimiladas pela tendência.

Severidade do desvio

Nem toda quebra de limite inferior exige mobilização de emergência. Um desvio de 3% abaixo de yhat_lower em um único dia pode decorrer de instabilidades temporárias de rede ou oscilações de CTR.

Por outro lado, quedas com desvio superior a 15% abaixo do limite inferior sustentadas por dois dias consecutivos indicam perda severa de posicionamento médio ou perda de impressões na primeira página.

Como isolar Core Updates de content decay e falhas técnicas

Ao detectar uma anomalia negativa, a prioridade é classificar a causa raiz. A dinâmica temporal do desvio revela o tipo de problema que atingiu o domínio:

Indicador TécnicoCore Update do GoogleFalha Técnica / CódigoContent Decay Natural
Velocidade de quedaDegrau súbito em 2 a 5 diasRuptura vertical em 24hDeclínio suave ao longo de meses
Escopo das páginasClusters inteiros ou sitewideURLs que compartilham o templateURLs antigas individuais
Comportamento das ImpressõesQueda abrupta nas posições 1 a 10Pode zerar se houver noindexPerda gradual de palavras secundárias
Posição MédiaPiora generalizadaPode se manter se a URL sumirPiora lenta de décimos de posição
Sinais de RecuperaçãoRequer ajustes de qualidade e novo updateRecupera assim que o bug é corrigidoExige atualização e expansão de conteúdo

Quando ocorre um Core Update, o Google reavalia a relevância relativa do domínio frente aos concorrentes da SERP. O tráfego não cai para zero como em um erro de meta robots: noindex. Em vez disso, páginas que ocupavam as posições 2 e 3 caem para as posições 7 ou 8, derrubando o CTR de forma acentuada.

Para entender a mecânica dessas reclassificações algorítmicas causadas por recalibração de sinais de engajamento do usuário, vale estudar como o Google NavBoost e sinais de clique operam na pontuação das páginas de resultados.

Se o declínio for lento e contínuo ao longo de trimestres, o fenômeno não é um Core Update, mas sim a perda gradual de relevância temporal, que deve ser tratada com as metodologias explicadas no guia de Content Decay SEO com Python.

Por fim, caso você esteja conduzindo testes controlados de otimização on-page e precise separar o efeito das suas alterações da volatilidade natural do algoritmo, a técnica adequada envolve métodos causais robustos como o SEO Split Testing com CausalImpact.

Detecção granular por cluster de páginas e diretórios

Rodar o Prophet apenas no tráfego agregado do domínio pode mascarar a realidade. Muitas vezes, um Core Update afeta violentamente a seção de blog ou tutoriais informacionais enquanto a seção de catálogo de e-commerce permanece intocada. Na média geral, a queda parece branda, atrasando o diagnóstico.

Para solucionar isso, agrupamos as métricas da Search Console API por diretórios ou grupos semânticos antes de aplicar a modelagem:

def extrair_dados_por_cluster(site_url: str, creds_path: str, dias_historico: int = 180) -> pd.DataFrame:
    credentials = service_account.Credentials.from_service_account_file(
        creds_path,
        scopes=['https://www.googleapis.com/auth/webmasters.readonly']
    )
    service = build('searchconsole', 'v1', credentials=credentials)
    
    data_fim = datetime.date.today() - datetime.timedelta(days=3)
    data_inicio = data_fim - datetime.timedelta(days=dias_historico)
    
    payload = {
        'startDate': data_inicio.strftime('%Y-%m-%d'),
        'endDate': data_fim.strftime('%Y-%m-%d'),
        'dimensions': ['date', 'page'],
        'rowLimit': 25000
    }
    
    response = service.searchanalytics().query(siteUrl=site_url, body=payload).execute()
    linhas = response.get('rows', [])
    
    registros = []
    for l in linhas:
        url = l['keys'][1]
        
        # Mapeamento do cluster pelo padrao de URL
        if '/blog/' in url:
            cluster = 'editorial'
        elif '/produtos/' in url:
            cluster = 'ecommerce'
        elif '/servicos/' in url:
            cluster = 'servicos'
        else:
            cluster = 'institucional'
            
        registros.append({
            'ds': pd.to_datetime(l['keys'][0]),
            'cluster': cluster,
            'clicks': float(l['clicks']),
            'impressions': float(l['impressions'])
        })
        
    df_detalhado = pd.DataFrame(registros)
    
    # Agrupamento diario por cluster
    df_cluster = df_detalhado.groupby(['ds', 'cluster'])['clicks'].sum().reset_index()
    return df_cluster

Com os dados agrupados por cluster, iteramos o algoritmo do Prophet individualmente sobre cada grupo:

def auditar_todos_clusters(df_cluster: pd.DataFrame):
    clusters = df_cluster['cluster'].unique()
    alertas = []
    
    for c in clusters:
        sub_df = df_cluster[df_cluster['cluster'] == c][['ds', 'clicks']].rename(columns={'clicks': 'y'})
        
        # O Prophet exige pelo menos algumas dezenas de observacoes consistentes
        if len(sub_df) < 60:
            continue
            
        resultado = detectar_anomalias_prophet(sub_df)
        ultimo_dia = resultado.iloc[-1]
        
        if ultimo_dia['anomalia_negativa']:
            alertas.append({
                'cluster': c,
                'data': ultimo_dia['ds'].strftime('%Y-%m-%d'),
                'real': ultimo_dia['y'],
                'limite_inferior': ultimo_dia['yhat_lower'],
                'desvio_pct': ultimo_dia['desvio_pct']
            })
            
    return pd.DataFrame(alertas)

Essa abordagem aponta exatamente qual seção do site perdeu tração. Se o cluster editorial rompe a margem inferior em 25% enquanto servicos e ecommerce seguem a sazonalidade prevista, o problema não é de infraestrutura de servidor, mas sim de intenção de busca, profundidade de conteúdo ou cobertura de entidades na área do blog.

Rotina de alertas automáticos para equipes de engenharia e SEO

Para transformar o script em uma defesa ativa, o processo deve ser agendado em uma rotina automatizada, como uma cron task em servidor Linux, uma Cloud Function no Google Cloud Platform ou fluxos agendados no GitHub Actions.

Para garantir que a esteira de publicação e deploys técnicos não introduza falhas silenciosas de SEO, a integração dessa rotina pode complementar a estratégia de testes de regressão no CI/CD com GitHub Actions.

O envio de alertas para canais do Slack ou Teams permite que a equipe técnica seja notificada sem precisar acessar relatórios manuais:

import json
import requests

def enviar_alerta_slack(webhook_url: str, alertas_df: pd.DataFrame):
    if alertas_df.empty:
        return
        
    blocos = [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": "Alerta de Anomalia Negativa de Tráfego Orgânico"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "O pipeline estatístico detectou desvios significativos abaixo da margem de segurança do Prophet na Search Console API."
            }
        }
    ]
    
    for _, linha in alertas_df.iterrows():
        texto_item = (
            f"*Cluster:* `{linha['cluster']}`\n"
            f"*Data:* {linha['data']}\n"
            f"*Cliques Observados:* {int(linha['real'])}\n"
            f"*Limite Mínimo Esperado:* {int(linha['limite_inferior'])}\n"
            f"*Queda Relativa:* `{linha['desvio_pct']:.1f}%`"
        )
        blocos.append({
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": texto_item
            }
        })
        
    payload = {"blocks": blocos}
    requests.post(webhook_url, data=json.dumps(payload), headers={'Content-Type': 'application/json'})

Protocolo de resposta quando o alerta dispara

Quando o alerta acusa uma anomalia negativa sustentada durante o período de lançamento de uma atualização do Google, siga um plano de triagem metódico antes de alterar código ou deletar URLs:

  1. Confirmação no Google Search Central: consulte o Search Status Dashboard oficial para verificar se há um Core Update em andamento e em qual data o rollout começou.
  2. Avaliação da SERP: investigue as consultas com maior volume do cluster afetado. Observe se o Google introduziu novos blocos de resposta como AI Overviews, carrosséis de vídeos ou expansão de painéis locais que empurraram os resultados orgânicos para baixo da dobra.
  3. Análise de perda de posição versus perda de CTR: determine se a queda nos cliques decorre de perda de posições médias ou de erosão do CTR mesmo com a posição estável. Se a posição se manteve, o problema reside na alteração do layout da SERP. Se as posições caíram uniformemente, o domínio sofreu reavaliação de qualidade.
  4. Verificação de canibalização interna: cruze os dados da API para checar se múltiplas URLs do seu domínio começaram a competir pelas mesmas palavras-chave principais, fragmentando o histórico de sinais.
  5. Auditoria de logs de rastreamento: analise se o Googlebot reduziu a frequência de rastreamento no cluster afetado nos últimos 14 dias, indicativo de problemas de orçamento de rastreamento ou excesso de URLs de baixo valor agregado.

Implementar a detecção automatizada com Python e Prophet retira a equipe de marketing da posição passiva de esperar relatórios mensais para descobrir que o tráfego despencou. O monitoramento estatístico contínuo transforma a incerteza dos algoritmos em dados auditáveis, permitindo correções arquiteturais e de conteúdo muito antes que o impacto financeiro comprometa o negócio.