Spaces:
Running
Running
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() | |