Spaces:
Paused
Paused
| import requests | |
| from bs4 import BeautifulSoup | |
| from urllib.parse import urlparse, urljoin | |
| import gradio as gr | |
| def seo_check(url): | |
| report = [] | |
| suggestions = [] | |
| # Ensure HTTPS | |
| if not url.startswith("http"): | |
| url = "https://" + url | |
| try: | |
| response = requests.get(url, timeout=10) | |
| response.raise_for_status() | |
| html = response.text | |
| except Exception as e: | |
| return f"❌ Error accessing URL: {e}", "" | |
| soup = BeautifulSoup(html, "html.parser") | |
| # Title Tag | |
| title = soup.title.string.strip() if soup.title else "" | |
| if not title: | |
| report.append("❌ Missing <title> tag.") | |
| suggestions.append("Add a <title> tag that describes your page in 50–60 characters.") | |
| elif len(title) > 70: | |
| report.append("⚠️ Title is too long.") | |
| suggestions.append("Keep title under 70 characters.") | |
| # Meta Description | |
| desc_tag = soup.find("meta", attrs={"name": "description"}) | |
| desc = desc_tag["content"].strip() if desc_tag and desc_tag.get("content") else "" | |
| if not desc: | |
| report.append("❌ Missing meta description.") | |
| suggestions.append("Add a <meta name='description'> summarizing the page.") | |
| elif len(desc) > 160: | |
| report.append("⚠️ Meta description is too long.") | |
| suggestions.append("Keep meta descriptions under 160 characters.") | |
| # Canonical Tag | |
| canonical = soup.find("link", rel="canonical") | |
| if not canonical: | |
| report.append("❌ Missing canonical link.") | |
| suggestions.append("Add a <link rel='canonical'> to avoid duplicate content.") | |
| # H1 Tag | |
| h1_tags = soup.find_all("h1") | |
| if len(h1_tags) != 1: | |
| report.append(f"⚠️ Found {len(h1_tags)} <h1> tags.") | |
| suggestions.append("Use exactly one <h1> tag for SEO clarity.") | |
| # Mobile viewport | |
| viewport = soup.find("meta", attrs={"name": "viewport"}) | |
| if not viewport: | |
| report.append("⚠️ No viewport meta tag.") | |
| suggestions.append("Add a viewport meta tag for mobile responsiveness.") | |
| # HTTPS check | |
| if not url.startswith("https://"): | |
| report.append("⚠️ URL is not secure (no HTTPS).") | |
| suggestions.append("Install SSL and redirect HTTP to HTTPS.") | |
| # Robots.txt and sitemap.xml | |
| parsed = urlparse(url) | |
| base = f"{parsed.scheme}://{parsed.netloc}" | |
| robots_url = urljoin(base, "/robots.txt") | |
| sitemap_url = urljoin(base, "/sitemap.xml") | |
| try: | |
| r1 = requests.get(robots_url) | |
| if r1.status_code != 200: | |
| report.append("❌ robots.txt not found.") | |
| suggestions.append("Create a robots.txt to guide search bots.") | |
| except: | |
| report.append("❌ Could not access robots.txt.") | |
| try: | |
| r2 = requests.get(sitemap_url) | |
| if r2.status_code != 200: | |
| report.append("❌ sitemap.xml not found.") | |
| suggestions.append("Add sitemap.xml for better crawling.") | |
| except: | |
| report.append("❌ Could not access sitemap.xml.") | |
| # Open Graph Tags | |
| og_title = soup.find("meta", property="og:title") | |
| if not og_title: | |
| report.append("⚠️ Missing Open Graph (og:title).") | |
| suggestions.append("Add OG tags to improve sharing on social media.") | |
| # Image alt text | |
| images = soup.find_all("img") | |
| alt_missing = [img for img in images if not img.get("alt")] | |
| if alt_missing: | |
| report.append(f"⚠️ {len(alt_missing)} images missing alt text.") | |
| suggestions.append("Add descriptive alt attributes to all images.") | |
| # Internal and external links | |
| links = soup.find_all("a", href=True) | |
| internal = 0 | |
| external = 0 | |
| for link in links: | |
| href = link['href'] | |
| if parsed.netloc in href: | |
| internal += 1 | |
| elif href.startswith("http"): | |
| external += 1 | |
| report.append(f"ℹ️ Internal Links: {internal} | External Links: {external}") | |
| suggestions.append("Ensure most important links are internal. Check broken links.") | |
| # Keyword density (basic) | |
| body_text = soup.get_text().lower() | |
| words = body_text.split() | |
| word_count = len(words) | |
| keyword = parsed.netloc.replace("www.", "").split(".")[0] | |
| keyword_freq = words.count(keyword) | |
| density = (keyword_freq / word_count) * 100 if word_count else 0 | |
| report.append(f"ℹ️ Keyword '{keyword}' appears {keyword_freq} times ({density:.2f}% density)") | |
| if density < 0.5: | |
| suggestions.append("Consider using your main keyword more often (target 1–2%).") | |
| return "\n".join(report), "\n".join(suggestions) | |
| # Gradio UI | |
| gr.Interface( | |
| fn=seo_check, | |
| inputs=gr.Textbox(label="Enter Website URL"), | |
| outputs=[ | |
| gr.Textbox(label="SEO Report", lines=15), | |
| gr.Textbox(label="Suggestions & Fixes", lines=15) | |
| ], | |
| title="SEO Website Checker", | |
| description="Analyze your website's SEO like Sitechecker.pro & SEOSiteCheckup, with clear solutions!" | |
| ).launch() | |