This commit is contained in:
oliver
2026-08-28 10:11:11 -03:00
parent f533ff8e36
commit 585951c8ce
1380 changed files with 1540268 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
"""Depth + duplicate title/meta analysis for odoo-expertos.com (read-only)."""
import json, re, sys, urllib.parse as up
import requests
from bs4 import BeautifulSoup
BASE = "https://www.odoo-expertos.com"
S = requests.Session()
S.headers["User-Agent"] = "Mozilla/5.0 (SEO-audit; +read-only)"
def get(u):
try:
r = S.get(u, timeout=30)
return r.status_code, r.text
except Exception as e:
return None, str(e)
def meta(html):
soup = BeautifulSoup(html, "lxml")
t = (soup.title.string.strip() if soup.title and soup.title.string else "")
md = ""
m = soup.find("meta", attrs={"name": "description"})
if m and m.get("content"): md = m["content"].strip()
h1s = [h.get_text(strip=True) for h in soup.find_all("h1")]
return t, md, h1s, soup
def internal_links(soup):
out = set()
for a in soup.find_all("a", href=True):
href = a["href"].split("#")[0].strip()
if not href or href.startswith(("mailto:", "tel:", "javascript:")): continue
full = up.urljoin(BASE + "/", href)
if full.startswith(BASE):
out.add(full.rstrip("/") + "/")
return out
# 1. CLICK DEPTH (Spanish tree): home -> categories -> articles
depth = {}
_, home = get(BASE + "/")
soup_home = BeautifulSoup(home, "lxml") if home else None
d1 = internal_links(soup_home) if soup_home else set()
cats = [u for u in d1 if re.search(r"/(odoo|odoo-hosting|odoo-ia)/$", u)]
depth["home_internal_links"] = len(d1)
depth["categories_linked_from_home"] = sorted(cats)
d2 = set()
cat_link_counts = {}
for c in ["/odoo/", "/odoo-hosting/", "/odoo-ia/"]:
code, html = get(BASE + c)
if html and code == 200:
s = BeautifulSoup(html, "lxml")
links = internal_links(s)
arts = {u for u in links if re.search(r"/(odoo|odoo-hosting|odoo-ia)/[^/]+/$", u)}
cat_link_counts[c] = len(arts)
d2 |= arts
depth["articles_linked_from_category_indexes(depth2)"] = len(d2)
depth["per_category_article_links"] = cat_link_counts
# Compare to total ES articles in sitemap
try:
es = open(r"C:\Users\eugen\.cursor\projects\Odoo Expertos\odoo-expertos\seo-audit-2026-05-27\raw\sitemap-es.xml", encoding="utf-8").read()
es_locs = set(re.findall(r"<loc>(https[^<]+)</loc>", es))
es_articles = {u.rstrip("/") + "/" for u in es_locs if re.search(r"/(odoo|odoo-hosting|odoo-ia)/[^/]+/$", u)}
depth["total_ES_articles_in_sitemap"] = len(es_articles)
reachable = (d2 & es_articles)
depth["ES_articles_reachable_within_2_clicks"] = len(reachable)
depth["ES_articles_NOT_reachable_within_2_clicks"] = len(es_articles - reachable)
except Exception as e:
depth["sitemap_compare_error"] = str(e)
# 2. DUPLICATE / IDENTICAL title + meta across a sample
sample = [
"/", "/odoo/", "/odoo-hosting/", "/odoo-ia/",
"/odoo/odoo-hosting-cloud-infraestructura-2025/",
"/odoo/odoo-crm-gestion-clientes-2025/",
"/odoo/odoo-inventario-gestion-almacen-2025/",
"/odoo-hosting/aws-hosting-odoo-2026/",
"/odoo-hosting/odoo-docker-hosting-2026/",
"/odoo-hosting/servidor-dedicado-odoo-2026/",
"/odoo-ia/que-es-odoo-ia-guia-basica-2025/",
"/odoo-ia/odoo-openai-api-integracion-2025/",
"/en/", "/de/", "/fr/", "/pt/", "/ar/",
]
rows = []
for path in sample:
code, html = get(BASE + path)
if html and code == 200:
t, md, h1s, _ = meta(html)
rows.append({
"url": path, "status": code,
"title": t, "title_len": len(t),
"meta": md, "meta_len": len(md),
"h1": h1s[0] if h1s else "", "h1_count": len(h1s),
"title_eq_h1": (t.split("|")[0].strip().lower() == (h1s[0].lower() if h1s else "")),
"title_eq_meta": (t.strip().lower() == md.strip().lower()),
})
else:
rows.append({"url": path, "status": code})
# duplicate detection within sample
titles = {}
metas = {}
for r in rows:
if r.get("title"): titles.setdefault(r["title"], []).append(r["url"])
if r.get("meta"): metas.setdefault(r["meta"], []).append(r["url"])
dup_titles = {k: v for k, v in titles.items() if len(v) > 1}
dup_metas = {k: v for k, v in metas.items() if len(v) > 1}
result = {
"depth": depth,
"pages": rows,
"duplicate_titles_in_sample": dup_titles,
"duplicate_metas_in_sample": dup_metas,
}
print(json.dumps(result, ensure_ascii=False, indent=2))