CI / Lint e auditorias (push) Successful in 48s
CI / Trivy (dependências e segredos) (push) Successful in 12s
CI / Imagem Docker (linux/arm64) (push) Successful in 3m41s
Release / Plano (reaproveitar a imagem?) (push) Successful in 6s
Release / ci (push) Skipped
Release / Publica a imagem (linux/arm64) (push) Successful in 16s
Release / Cria a release no Gitea (push) Successful in 6s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
76 lines
2.7 KiB
Python
Executable File
76 lines
2.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Confere canonical, hreflang e <html lang> de todas as URLs do sitemap.
|
|
|
|
python3 scripts/verifica-seo.py http://127.0.0.1:3998
|
|
|
|
Regras: o canonical é a própria URL; o conjunto de hreflang da página é o mesmo
|
|
do sitemap; há x-default quando existe mais de um idioma; o livro (só em pt)
|
|
não aponta para outros idiomas. Cada falha é impressa e o script sai com 1.
|
|
"""
|
|
import re
|
|
import sys
|
|
import urllib.request
|
|
from urllib.parse import urlparse
|
|
|
|
base = sys.argv[1].rstrip("/")
|
|
LANG = {"pt": "pt-BR", "en": "en", "es": "es-419", "fr": "fr", "de": "de", "zh-CN": "zh-CN"}
|
|
|
|
|
|
def get(url):
|
|
req = urllib.request.Request(url, headers={"User-Agent": "ESPDocs-SmokeTest/1.0"})
|
|
return urllib.request.urlopen(req, timeout=15).read().decode("utf-8")
|
|
|
|
|
|
def caminho(u):
|
|
return urlparse(u).path or "/"
|
|
|
|
|
|
sitemap = get(base + "/sitemap.xml")
|
|
falhas = 0
|
|
total = 0
|
|
for bloco in re.findall(r"<url>(.*?)</url>", sitemap, re.S):
|
|
loc = caminho(re.search(r"<loc>([^<]+)</loc>", bloco).group(1))
|
|
esperado = {
|
|
h: caminho(u)
|
|
for h, u in re.findall(r'<xhtml:link rel="alternate" hreflang="([^"]+)" href="([^"]+)"', bloco)
|
|
}
|
|
total += 1
|
|
try:
|
|
html = get(base + loc)
|
|
except Exception as e:
|
|
print(f"FALHOU {loc}: {e}")
|
|
falhas += 1
|
|
continue
|
|
head = html.split("</head>")[0]
|
|
|
|
idioma = loc.strip("/").split("/")[0]
|
|
m = re.search(r'<html[^>]*\blang="([^"]+)"', html)
|
|
if not m or m.group(1) != LANG.get(idioma):
|
|
print(f"FALHOU {loc}: <html lang> é {m and m.group(1)}, esperado {LANG.get(idioma)}")
|
|
falhas += 1
|
|
|
|
canon = re.search(r'<link rel="canonical" href="([^"]+)"', head)
|
|
if not canon or caminho(canon.group(1)) != loc:
|
|
print(f"FALHOU {loc}: canonical é {canon and caminho(canon.group(1))}")
|
|
falhas += 1
|
|
|
|
na_pagina = {
|
|
h: caminho(u)
|
|
for h, u in re.findall(r'<link rel="alternate" hrefLang="([^"]+)" href="([^"]+)"', head)
|
|
}
|
|
sem_default_pagina = {k: v for k, v in na_pagina.items() if k != "x-default"}
|
|
sem_default_sitemap = {k: v for k, v in esperado.items() if k != "x-default"}
|
|
if len(sem_default_sitemap) > 1 and sem_default_pagina != sem_default_sitemap:
|
|
print(f"FALHOU {loc}: hreflang da página difere do sitemap")
|
|
falhas += 1
|
|
if len(sem_default_sitemap) > 1 and "x-default" not in na_pagina:
|
|
print(f"FALHOU {loc}: sem x-default")
|
|
falhas += 1
|
|
if len(sem_default_sitemap) <= 1 and len(sem_default_pagina) > 1:
|
|
print(f"FALHOU {loc}: página de um idioma só com hreflang para outros: {sorted(sem_default_pagina)}")
|
|
falhas += 1
|
|
|
|
if falhas == 0:
|
|
print(f" ✔ SEO: canonical, hreflang e lang corretos em {total} URLs do sitemap.")
|
|
sys.exit(1 if falhas else 0)
|