Se você trabalha com SEO técnico, sabe que um sitemap pode facilmente se tornar um problema invisível.
URLs quebradas, páginas com noindex, redirecionamentos… tudo isso pode estar dentro do seu sitemap sem você perceber.
E isso impacta diretamente:
crawl budget
indexação
performance orgânica
Neste post, vamos criar um script em Python para validar automaticamente um XML Sitemap, verificando:
status HTTP
indexabilidade
redirecionamentos
erros críticos
O que vamos validar
O script vai analisar:
URLs com erro (404, 500)
redirecionamentos (301, 302)
páginas não indexáveis
tempo de resposta
estrutura do sitemap
Requisitos
Antes de começar:
Script completo
import requests
import xml.etree.ElementTree as ET
import pandas as pd
from concurrent.futures import ThreadPoolExecutor
SITEMAP_URL = "https://adrock.com.br/sitemap.xml"
HEADERS = {"User-Agent": "Mozilla/5.0"}
TIMEOUT = 10
MAX_THREADS = 10
def get_sitemap_urls(sitemap_url):
response = requests.get(sitemap_url, headers=HEADERS)
root = ET.fromstring(response.content)
urls = []
for sitemap in root.findall("{http://www.sitemaps.org/schemas/sitemap/0.9}sitemap"):
loc = sitemap.find("{http://www.sitemaps.org/schemas/sitemap/0.9}loc").text
urls.extend(get_sitemap_urls(loc))
for url in root.findall("{http://www.sitemaps.org/schemas/sitemap/0.9}url"):
loc = url.find("{http://www.sitemaps.org/schemas/sitemap/0.9}loc").text
urls.append(loc)
return urls
def check_url(url):
try:
response = requests.get(url, headers=HEADERS, timeout=TIMEOUT, allow_redirects=True)
status = response.status_code
final_url = response.url
return {
"url": url,
"status": status,
"final_url": final_url,
"redirect": url != final_url,
"response_time": response.elapsed.total_seconds()
}
except Exception as e:
return {
"url": url,
"status": "ERROR",
"final_url": None,
"redirect": False,
"response_time": None
}
def validate_sitemap():
print("🔍 Carregando sitemap...")
urls = get_sitemap_urls(SITEMAP_URL)
print(f"📊 Total de URLs: {len(urls)}")
results = []
with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
results = list(executor.map(check_url, urls))
df = pd.DataFrame(results)
return df
def generate_report(df):
print("\n📈 RELATÓRIO\n")
print("URLs OK (200):", len(df[df["status"] == 200]))
print("Redirecionamentos:", len(df[df["redirect"] == True]))
print("Erros:", len(df[df["status"] != 200]))
df.to_csv("sitemap_report.csv", index=False)
print("\n📁 Relatório salvo em sitemap_report.csv")
if __name__ == "__main__":
df = validate_sitemap()
generate_report(df)import requests
import xml.etree.ElementTree as ET
import pandas as pd
from concurrent.futures import ThreadPoolExecutor
SITEMAP_URL = "https://adrock.com.br/sitemap.xml"
HEADERS = {"User-Agent": "Mozilla/5.0"}
TIMEOUT = 10
MAX_THREADS = 10
def get_sitemap_urls(sitemap_url):
response = requests.get(sitemap_url, headers=HEADERS)
root = ET.fromstring(response.content)
urls = []
for sitemap in root.findall("{http://www.sitemaps.org/schemas/sitemap/0.9}sitemap"):
loc = sitemap.find("{http://www.sitemaps.org/schemas/sitemap/0.9}loc").text
urls.extend(get_sitemap_urls(loc))
for url in root.findall("{http://www.sitemaps.org/schemas/sitemap/0.9}url"):
loc = url.find("{http://www.sitemaps.org/schemas/sitemap/0.9}loc").text
urls.append(loc)
return urls
def check_url(url):
try:
response = requests.get(url, headers=HEADERS, timeout=TIMEOUT, allow_redirects=True)
status = response.status_code
final_url = response.url
return {
"url": url,
"status": status,
"final_url": final_url,
"redirect": url != final_url,
"response_time": response.elapsed.total_seconds()
}
except Exception as e:
return {
"url": url,
"status": "ERROR",
"final_url": None,
"redirect": False,
"response_time": None
}
def validate_sitemap():
print("🔍 Carregando sitemap...")
urls = get_sitemap_urls(SITEMAP_URL)
print(f"📊 Total de URLs: {len(urls)}")
results = []
with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
results = list(executor.map(check_url, urls))
df = pd.DataFrame(results)
return df
def generate_report(df):
print("\n📈 RELATÓRIO\n")
print("URLs OK (200):", len(df[df["status"] == 200]))
print("Redirecionamentos:", len(df[df["redirect"] == True]))
print("Erros:", len(df[df["status"] != 200]))
df.to_csv("sitemap_report.csv", index=False)
print("\n📁 Relatório salvo em sitemap_report.csv")
if __name__ == "__main__":
df = validate_sitemap()
generate_report(df)import requests
import xml.etree.ElementTree as ET
import pandas as pd
from concurrent.futures import ThreadPoolExecutor
SITEMAP_URL = "https://adrock.com.br/sitemap.xml"
HEADERS = {"User-Agent": "Mozilla/5.0"}
TIMEOUT = 10
MAX_THREADS = 10
def get_sitemap_urls(sitemap_url):
response = requests.get(sitemap_url, headers=HEADERS)
root = ET.fromstring(response.content)
urls = []
for sitemap in root.findall("{http://www.sitemaps.org/schemas/sitemap/0.9}sitemap"):
loc = sitemap.find("{http://www.sitemaps.org/schemas/sitemap/0.9}loc").text
urls.extend(get_sitemap_urls(loc))
for url in root.findall("{http://www.sitemaps.org/schemas/sitemap/0.9}url"):
loc = url.find("{http://www.sitemaps.org/schemas/sitemap/0.9}loc").text
urls.append(loc)
return urls
def check_url(url):
try:
response = requests.get(url, headers=HEADERS, timeout=TIMEOUT, allow_redirects=True)
status = response.status_code
final_url = response.url
return {
"url": url,
"status": status,
"final_url": final_url,
"redirect": url != final_url,
"response_time": response.elapsed.total_seconds()
}
except Exception as e:
return {
"url": url,
"status": "ERROR",
"final_url": None,
"redirect": False,
"response_time": None
}
def validate_sitemap():
print("🔍 Carregando sitemap...")
urls = get_sitemap_urls(SITEMAP_URL)
print(f"📊 Total de URLs: {len(urls)}")
results = []
with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
results = list(executor.map(check_url, urls))
df = pd.DataFrame(results)
return df
def generate_report(df):
print("\n📈 RELATÓRIO\n")
print("URLs OK (200):", len(df[df["status"] == 200]))
print("Redirecionamentos:", len(df[df["redirect"] == True]))
print("Erros:", len(df[df["status"] != 200]))
df.to_csv("sitemap_report.csv", index=False)
print("\n📁 Relatório salvo em sitemap_report.csv")
if __name__ == "__main__":
df = validate_sitemap()
generate_report(df)O que esse script faz
1. Lê o sitemap automaticamente
suporta sitemap index
percorre todos os arquivos
2. Testa cada URL
Para cada página:
verifica status HTTP
identifica redirecionamento
mede tempo de resposta
3. Gera relatório
Saída em CSV com:
URL
status
redirecionamento
tempo
Como usar na prática
Rodar localmente
Automatizar (cron)
Monitoramento contínuo
Você pode integrar com:
Google Sheets
Looker Studio
alertas por e-mail
Melhorias recomendadas
Você pode evoluir o script para:
verificar meta robots (index/noindex)
detectar canonical incorreto
validar conteúdo duplicado
integrar com Search Console API
Por que isso é importante
Segundo o Google:
apenas páginas indexáveis devem estar no sitemap
erros reduzem eficiência de rastreamento
Fonte oficial:
https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview
O erro que você evita
Sem validação:
sitemap cheio de erro
desperdício de crawl
páginas importantes ignoradas
O papel da Ad Rock
Na Ad Rock, usamos automação para:
validar SEO técnico
monitorar indexação
escalar análise de sites
Porque SEO moderno não é manual.
É programático.
Conclusão
Validar sitemap não é opcional.
É parte do SEO técnico avançado.
E com Python, você consegue:
automatizar
escalar
detectar problemas rapidamente
Quer implementar isso no seu projeto?
👉 https://adrock.com.br/contato
Adendo: validação avançada — canonical e conteúdo duplicado
Além das verificações básicas de status HTTP, existem dois pontos críticos que impactam diretamente SEO:
canonical incorreto
conteúdo duplicado
Abaixo estão scripts complementares para evoluir sua análise.
1. Detectar canonical incorreto
Esse script verifica:
se a página possui canonical
se o canonical aponta para outra URL
se existe inconsistência
from bs4 import BeautifulSoup
def check_canonical(url):
try:
response = requests.get(url, headers=HEADERS, timeout=TIMEOUT)
soup = BeautifulSoup(response.text, "html.parser")
canonical_tag = soup.find("link", rel="canonical")
if canonical_tag:
canonical_url = canonical_tag.get("href")
return {
"url": url,
"canonical": canonical_url,
"canonical_ok": url == canonical_url
}
else:
return {
"url": url,
"canonical": None,
"canonical_ok": False
}
except:
return {
"url": url,
"canonical": None,
"canonical_ok": False
}from bs4 import BeautifulSoup
def check_canonical(url):
try:
response = requests.get(url, headers=HEADERS, timeout=TIMEOUT)
soup = BeautifulSoup(response.text, "html.parser")
canonical_tag = soup.find("link", rel="canonical")
if canonical_tag:
canonical_url = canonical_tag.get("href")
return {
"url": url,
"canonical": canonical_url,
"canonical_ok": url == canonical_url
}
else:
return {
"url": url,
"canonical": None,
"canonical_ok": False
}
except:
return {
"url": url,
"canonical": None,
"canonical_ok": False
}from bs4 import BeautifulSoup
def check_canonical(url):
try:
response = requests.get(url, headers=HEADERS, timeout=TIMEOUT)
soup = BeautifulSoup(response.text, "html.parser")
canonical_tag = soup.find("link", rel="canonical")
if canonical_tag:
canonical_url = canonical_tag.get("href")
return {
"url": url,
"canonical": canonical_url,
"canonical_ok": url == canonical_url
}
else:
return {
"url": url,
"canonical": None,
"canonical_ok": False
}
except:
return {
"url": url,
"canonical": None,
"canonical_ok": False
}O que analisar
canonical apontando para outra URL → possível duplicação
ausência de canonical → risco de indexação incorreta
2. Detectar conteúdo duplicado (hash simples)
Aqui usamos um hash do conteúdo da página para identificar duplicação.
import hashlib
def get_content_hash(url):
try:
response = requests.get(url, headers=HEADERS, timeout=TIMEOUT)
content = response.text
content_hash = hashlib.md5(content.encode("utf-8")).hexdigest()
return {
"url": url,
"hash": content_hash
}
except:
return {
"url": url,
"hash": None
}import hashlib
def get_content_hash(url):
try:
response = requests.get(url, headers=HEADERS, timeout=TIMEOUT)
content = response.text
content_hash = hashlib.md5(content.encode("utf-8")).hexdigest()
return {
"url": url,
"hash": content_hash
}
except:
return {
"url": url,
"hash": None
}import hashlib
def get_content_hash(url):
try:
response = requests.get(url, headers=HEADERS, timeout=TIMEOUT)
content = response.text
content_hash = hashlib.md5(content.encode("utf-8")).hexdigest()
return {
"url": url,
"hash": content_hash
}
except:
return {
"url": url,
"hash": None
}3. Identificar duplicados
Depois de coletar os hashes:
def find_duplicates(df):
duplicates = df[df.duplicated("hash", keep=False)]
return duplicates.sort_values("hash")def find_duplicates(df):
duplicates = df[df.duplicated("hash", keep=False)]
return duplicates.sort_values("hash")def find_duplicates(df):
duplicates = df[df.duplicated("hash", keep=False)]
return duplicates.sort_values("hash")Limitações importantes
Esse método:
detecta duplicação exata
não identifica conteúdo parcialmente duplicado
não ignora elementos como menu ou footer
Evoluções recomendadas
Para análise mais avançada:
remover HTML e analisar apenas texto
usar similaridade semântica (NLP / embeddings)
ignorar elementos comuns do layout
Por que isso importa
Segundo o Google:
canonical ajuda a consolidar sinais de ranking
conteúdo duplicado dilui autoridade
Fonte oficial:
https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls
Conclusão do adendo
Com essas duas validações, você adiciona uma camada crítica ao seu SEO técnico:
evita indexação errada
consolida autoridade
melhora eficiência de crawl
SEO técnico eficiente hoje exige esse nível de controle.