import sys import hashlib import requests import xml.etree.ElementTree as ET from bs4 import BeautifulSoup from collections import defaultdict from urllib.parse import urlparse # Force UTF-8 for console output if possible if sys.stdout.encoding != 'utf-8': try: import codecs sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict') sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict') except: pass # Production Settings PRODUCTION_DOMAIN = "https://radionica3d.me" PRODUCTION_SITEMAP = f"{PRODUCTION_DOMAIN}/sitemap.xml" def get_content_hash(soup): # Remove elements that change frequently or are boilerplate clean_soup = BeautifulSoup(str(soup), "lxml") for tag in clean_soup(["script", "style", "header", "footer", "nav", "aside", "noscript"]): tag.decompose() text = clean_soup.get_text(separator=' ', strip=True) return hashlib.md5(text.encode('utf-8')).hexdigest() def load_sitemap(url): print(f"Loading production sitemap from {url}...") urls = set() try: response = requests.get(url, timeout=15) response.raise_for_status() root = ET.fromstring(response.content) ns = {'ns': 'http://www.sitemaps.org/schemas/sitemap/0.9'} for loc in root.findall('.//ns:loc', ns): urls.add(loc.text.strip()) except Exception as e: print(f"Error loading sitemap: {e}") return urls def audit_production(): sitemap_urls = load_sitemap(PRODUCTION_SITEMAP) if not sitemap_urls: print("No URLs found. Is the site online?") return print(f"\n--- Starting Production SEO Audit ({len(sitemap_urls)} URLs) ---") print(f"Target: {PRODUCTION_DOMAIN}\n") results = [] content_map = defaultdict(list) errors = [] warnings = [] info = [] session = requests.Session() # Mask as a real browser to see what Google/Users see session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' }) for i, url in enumerate(sorted(sitemap_urls), 1): print(f"[{i}/{len(sitemap_urls)}] Checking: {url} ...", end="\r") try: response = session.get(url, timeout=15, allow_redirects=True) if response.history: # Page with redirect info.append(f"Page with redirect: {url} (Google sees redirect to {response.url})") if response.status_code != 200: errors.append(f"HTTP {response.status_code} for {url}") continue soup = BeautifulSoup(response.text, "lxml") title = soup.title.string if soup.title else None description = soup.find("meta", attrs={"name": "description"}) description = description["content"] if description else None canonical = soup.find("link", rel="canonical") canonical = canonical["href"] if canonical else None h1 = [h.get_text(strip=True) for h in soup.find_all("h1")] content_hash = get_content_hash(soup) page_data = { "url": url, "title": title, "description": description, "canonical": canonical, "h1": h1, "hash": content_hash, "status": response.status_code } results.append(page_data) content_map[content_hash].append(page_data) except Exception as e: errors.append(f"Failed to fetch {url}: {e}") print("\n\nAnalyzing results...") # 1. Duplicates Analysis (GSC Style) for content_hash, pages in content_map.items(): if len(pages) > 1: canonicals = set(p['canonical'] for p in pages if p['canonical']) urls_str = "\n ".join(p['url'] for p in pages) if not canonicals: errors.append(f"Page is a duplicate. Canonical version not selected by user:\n {urls_str}") elif len(canonicals) > 1: warnings.append(f"Duplicate, submitted URL not selected as canonical (Multiple canonicals found):\n {urls_str}") else: info.append(f"Duplicate content correctly handled by canonical ({list(canonicals)[0]})") # 2. Canonical & SEO Tag Analysis for p in results: # Canonical Tag if not p['canonical']: warnings.append(f"Indexing issue: Missing canonical tag (Google will choose one): {p['url']}") elif p['canonical'] != p['url']: info.append(f"Variant of page with canonical tag: {p['url']} -> {p['canonical']}") if not p['canonical'].startswith(PRODUCTION_DOMAIN): warnings.append(f"Canonical points to EXTERNAL domain: {p['url']} -> {p['canonical']}") # Meta Tags if not p['title']: errors.append(f"SEO Error: Missing