Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Optimize websites for both traditional search engines (Google, Bing) and AI engines (ChatGPT, Perplexity, Gemini)
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/seo_audit.py
1#!/usr/bin/env python32"""3SEO audit script (no API required)4Usage: python3 scripts/seo_audit.py "https://example.com"5"""6import argparse7import urllib.request8import urllib.parse9import re10import time11import sys121314def fetch_url(url: str, timeout: int = 30) -> tuple:15"""Fetch URL and return (content, headers, load_time)"""16try:17start = time.time()18req = urllib.request.Request(url, headers={"User-Agent": "SEO-Audit/1.0"})19with urllib.request.urlopen(req, timeout=timeout) as resp:20content = resp.read().decode("utf-8", errors="ignore")21headers = dict(resp.headers)22load_time = time.time() - start23return content, headers, load_time24except Exception as e:25return None, None, None262728def extract_meta(html: str) -> dict:29"""Extract meta tags from HTML"""30result = {}3132# Title33title_match = re.search(r"<title[^>]*>([^<]+)</title>", html, re.I)34result["title"] = title_match.group(1).strip() if title_match else None3536# Meta description37desc_match = re.search(r'<meta[^>]+name=["\']description["\'][^>]+content=["\']([^"\']+)["\']', html, re.I)38if not desc_match:39desc_match = re.search(r'<meta[^>]+content=["\']([^"\']+)["\'][^>]+name=["\']description["\']', html, re.I)40result["description"] = desc_match.group(1).strip() if desc_match else None4142# OG tags43og_match = re.search(r'<meta[^>]+property=["\']og:title["\']', html, re.I)44result["og_tags"] = bool(og_match)4546# JSON-LD47jsonld_count = len(re.findall(r'application/ld\+json', html, re.I))48result["jsonld_count"] = jsonld_count4950# H1 (handle inline tags like <br>)51h1_match = re.search(r"<h1[^>]*>(.*?)</h1>", html, re.I | re.DOTALL)52if h1_match:53h1_text = re.sub(r"<[^>]+>", " ", h1_match.group(1)) # Remove inner tags54h1_text = re.sub(r"\s+", " ", h1_text).strip() # Normalize whitespace55result["h1"] = h1_text[:100]56else:57result["h1"] = None5859return result606162def check_robots(url: str) -> dict:63"""Check robots.txt"""64parsed = urllib.parse.urlparse(url)65robots_url = f"{parsed.scheme}://{parsed.netloc}/robots.txt"66content, _, _ = fetch_url(robots_url)6768result = {"exists": False, "ai_bots": []}69if content:70result["exists"] = True71ai_bots = ["GPTBot", "PerplexityBot", "ClaudeBot", "anthropic-ai", "ChatGPT-User"]72for bot in ai_bots:73if bot.lower() in content.lower():74result["ai_bots"].append(bot)75return result767778def check_sitemap(url: str) -> bool:79"""Check if sitemap.xml exists"""80parsed = urllib.parse.urlparse(url)81sitemap_url = f"{parsed.scheme}://{parsed.netloc}/sitemap.xml"82content, _, _ = fetch_url(sitemap_url)83if not content:84return False85# Check for common sitemap indicators86return "<urlset" in content.lower() or "<sitemapindex" in content.lower() or "<?xml" in content.lower()878889def main():90parser = argparse.ArgumentParser(description="SEO audit")91parser.add_argument("url", help="URL to audit")92args = parser.parse_args()9394url = args.url95if not url.startswith("http"):96url = f"https://{url}"9798print(f"=== SEO Audit: {url} ===")99print()100101# Fetch page102content, headers, load_time = fetch_url(url)103if not content:104print("error: Could not fetch URL")105sys.exit(1)106107# Meta tags108print("## Meta Tags")109meta = extract_meta(content)110title = meta["title"]111print(f"title: {title[:60] if title else 'MISSING'}{'...' if title and len(title) > 60 else ''}")112print(f"title_length: {len(title) if title else 0} chars")113desc = meta["description"]114print(f"description: {desc[:80] if desc else 'MISSING'}{'...' if desc and len(desc) > 80 else ''}")115print(f"description_length: {len(desc) if desc else 0} chars")116print(f"og_tags: {'yes' if meta['og_tags'] else 'no'}")117print(f"h1: {meta['h1'] if meta['h1'] else 'MISSING'}")118print()119120# Schema121print("## Schema Markup")122print(f"json_ld_blocks: {meta['jsonld_count']}")123print()124125# Performance126print("## Performance")127print(f"load_time: {load_time:.2f}s")128print(f"status: {'good' if load_time < 3 else 'slow'}")129print()130131# robots.txt132print("## robots.txt")133robots = check_robots(url)134print(f"exists: {'yes' if robots['exists'] else 'no'}")135if robots["ai_bots"]:136print(f"ai_bots_mentioned: {', '.join(robots['ai_bots'])}")137else:138print("ai_bots_mentioned: none")139print()140141# Sitemap142print("## Sitemap")143has_sitemap = check_sitemap(url)144print(f"sitemap_xml: {'yes' if has_sitemap else 'no'}")145print()146147print("=== Audit Complete ===")148149150if __name__ == "__main__":151main()152