968 lines
38 KiB
Python
968 lines
38 KiB
Python
"""
|
|
Technical SEO Crawler UI with Comprehensive Analysis Dashboard.
|
|
|
|
This module provides a professional Streamlit interface for the Technical SEO Crawler
|
|
with detailed analysis results, visualization, and export capabilities.
|
|
"""
|
|
|
|
import streamlit as st
|
|
import pandas as pd
|
|
from typing import Dict, Any, List
|
|
import json
|
|
from datetime import datetime
|
|
import io
|
|
import base64
|
|
import plotly.express as px
|
|
import plotly.graph_objects as go
|
|
from plotly.subplots import make_subplots
|
|
|
|
from .crawler import TechnicalSEOCrawler
|
|
from lib.alwrity_ui.dashboard_styles import apply_dashboard_style, render_dashboard_header
|
|
|
|
class TechnicalSEOCrawlerUI:
|
|
"""Professional UI for Technical SEO Crawler."""
|
|
|
|
def __init__(self):
|
|
"""Initialize the Technical SEO Crawler UI."""
|
|
self.crawler = TechnicalSEOCrawler()
|
|
|
|
# Apply dashboard styling
|
|
apply_dashboard_style()
|
|
|
|
def render(self):
|
|
"""Render the Technical SEO Crawler interface."""
|
|
|
|
# Enhanced dashboard header
|
|
render_dashboard_header(
|
|
"🔧 Technical SEO Crawler",
|
|
"Comprehensive site-wide technical SEO analysis with AI-powered recommendations. Identify and fix technical issues that impact your search rankings."
|
|
)
|
|
|
|
# Main content area
|
|
with st.container():
|
|
# Analysis input form
|
|
self._render_crawler_form()
|
|
|
|
# Session state for results
|
|
if 'technical_seo_results' in st.session_state and st.session_state.technical_seo_results:
|
|
st.markdown("---")
|
|
self._render_results_dashboard(st.session_state.technical_seo_results)
|
|
|
|
def _render_crawler_form(self):
|
|
"""Render the crawler configuration form."""
|
|
st.markdown("## 🚀 Configure Technical SEO Audit")
|
|
|
|
with st.form("technical_seo_crawler_form"):
|
|
# Website URL input
|
|
col1, col2 = st.columns([3, 1])
|
|
|
|
with col1:
|
|
website_url = st.text_input(
|
|
"🌐 Website URL to Audit",
|
|
placeholder="https://yourwebsite.com",
|
|
help="Enter the website URL for comprehensive technical SEO analysis"
|
|
)
|
|
|
|
with col2:
|
|
audit_type = st.selectbox(
|
|
"🎯 Audit Type",
|
|
options=["Standard", "Deep", "Quick"],
|
|
help="Choose the depth of analysis"
|
|
)
|
|
|
|
# Crawl configuration
|
|
st.markdown("### ⚙️ Crawl Configuration")
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
with col1:
|
|
if audit_type == "Quick":
|
|
crawl_depth = st.slider("Crawl Depth", 1, 2, 1)
|
|
max_pages = st.slider("Max Pages", 10, 100, 50)
|
|
elif audit_type == "Deep":
|
|
crawl_depth = st.slider("Crawl Depth", 1, 5, 4)
|
|
max_pages = st.slider("Max Pages", 100, 1000, 500)
|
|
else: # Standard
|
|
crawl_depth = st.slider("Crawl Depth", 1, 4, 3)
|
|
max_pages = st.slider("Max Pages", 50, 500, 200)
|
|
|
|
with col2:
|
|
analyze_images = st.checkbox(
|
|
"🖼️ Analyze Images",
|
|
value=True,
|
|
help="Include image SEO analysis"
|
|
)
|
|
|
|
analyze_security = st.checkbox(
|
|
"🛡️ Security Headers",
|
|
value=True,
|
|
help="Analyze security headers"
|
|
)
|
|
|
|
with col3:
|
|
analyze_mobile = st.checkbox(
|
|
"📱 Mobile SEO",
|
|
value=True,
|
|
help="Include mobile SEO analysis"
|
|
)
|
|
|
|
ai_recommendations = st.checkbox(
|
|
"🤖 AI Recommendations",
|
|
value=True,
|
|
help="Generate AI-powered recommendations"
|
|
)
|
|
|
|
# Analysis scope
|
|
st.markdown("### 🎯 Analysis Scope")
|
|
|
|
analysis_options = st.multiselect(
|
|
"Select Analysis Components",
|
|
options=[
|
|
"Technical Issues Detection",
|
|
"Performance Analysis",
|
|
"Content Structure Analysis",
|
|
"URL Structure Optimization",
|
|
"Internal Linking Analysis",
|
|
"Duplicate Content Detection"
|
|
],
|
|
default=[
|
|
"Technical Issues Detection",
|
|
"Performance Analysis",
|
|
"Content Structure Analysis"
|
|
],
|
|
help="Choose which analysis components to include"
|
|
)
|
|
|
|
# Submit button
|
|
submitted = st.form_submit_button(
|
|
"🚀 Start Technical SEO Audit",
|
|
use_container_width=True,
|
|
type="primary"
|
|
)
|
|
|
|
if submitted:
|
|
# Validate inputs
|
|
if not website_url or not website_url.startswith(('http://', 'https://')):
|
|
st.error("❌ Please enter a valid website URL starting with http:// or https://")
|
|
return
|
|
|
|
# Run technical SEO analysis
|
|
self._run_technical_analysis(
|
|
website_url=website_url,
|
|
crawl_depth=crawl_depth,
|
|
max_pages=max_pages,
|
|
options={
|
|
'analyze_images': analyze_images,
|
|
'analyze_security': analyze_security,
|
|
'analyze_mobile': analyze_mobile,
|
|
'ai_recommendations': ai_recommendations,
|
|
'analysis_scope': analysis_options
|
|
}
|
|
)
|
|
|
|
def _run_technical_analysis(self, website_url: str, crawl_depth: int,
|
|
max_pages: int, options: Dict[str, Any]):
|
|
"""Run the technical SEO analysis."""
|
|
|
|
try:
|
|
with st.spinner("🔄 Running Comprehensive Technical SEO Audit..."):
|
|
|
|
# Initialize progress tracking
|
|
progress_bar = st.progress(0)
|
|
status_text = st.empty()
|
|
|
|
# Update progress
|
|
progress_bar.progress(10)
|
|
status_text.text("🚀 Initializing technical SEO crawler...")
|
|
|
|
# Run comprehensive analysis
|
|
results = self.crawler.analyze_website_technical_seo(
|
|
website_url=website_url,
|
|
crawl_depth=crawl_depth,
|
|
max_pages=max_pages
|
|
)
|
|
|
|
progress_bar.progress(100)
|
|
status_text.text("✅ Technical SEO audit complete!")
|
|
|
|
# Store results in session state
|
|
st.session_state.technical_seo_results = results
|
|
|
|
# Clear progress indicators
|
|
progress_bar.empty()
|
|
status_text.empty()
|
|
|
|
if 'error' in results:
|
|
st.error(f"❌ Analysis failed: {results['error']}")
|
|
else:
|
|
st.success("🎉 Technical SEO Audit completed successfully!")
|
|
st.balloons()
|
|
|
|
# Rerun to show results
|
|
st.rerun()
|
|
|
|
except Exception as e:
|
|
st.error(f"❌ Error running technical analysis: {str(e)}")
|
|
|
|
def _render_results_dashboard(self, results: Dict[str, Any]):
|
|
"""Render the comprehensive results dashboard."""
|
|
|
|
if 'error' in results:
|
|
st.error(f"❌ Analysis Error: {results['error']}")
|
|
return
|
|
|
|
# Results header
|
|
st.markdown("## 📊 Technical SEO Audit Results")
|
|
|
|
# Key metrics overview
|
|
self._render_metrics_overview(results)
|
|
|
|
# Detailed analysis tabs
|
|
self._render_detailed_analysis(results)
|
|
|
|
# Export functionality
|
|
self._render_export_options(results)
|
|
|
|
def _render_metrics_overview(self, results: Dict[str, Any]):
|
|
"""Render key metrics overview."""
|
|
|
|
st.markdown("### 📈 Audit Overview")
|
|
|
|
# Create metrics columns
|
|
col1, col2, col3, col4, col5, col6 = st.columns(6)
|
|
|
|
with col1:
|
|
pages_crawled = results.get('crawl_overview', {}).get('pages_crawled', 0)
|
|
st.metric(
|
|
"🕷️ Pages Crawled",
|
|
pages_crawled,
|
|
help="Total pages analyzed"
|
|
)
|
|
|
|
with col2:
|
|
error_count = results.get('technical_issues', {}).get('http_errors', {}).get('total_errors', 0)
|
|
st.metric(
|
|
"❌ HTTP Errors",
|
|
error_count,
|
|
delta=f"-{error_count}" if error_count > 0 else None,
|
|
help="Pages with HTTP errors (4xx, 5xx)"
|
|
)
|
|
|
|
with col3:
|
|
avg_load_time = results.get('performance_analysis', {}).get('load_time_analysis', {}).get('avg_load_time', 0)
|
|
st.metric(
|
|
"⚡ Avg Load Time",
|
|
f"{avg_load_time:.2f}s",
|
|
delta=f"+{avg_load_time:.2f}s" if avg_load_time > 3 else None,
|
|
help="Average page load time"
|
|
)
|
|
|
|
with col4:
|
|
security_score = results.get('security_headers', {}).get('security_score', 0)
|
|
st.metric(
|
|
"🛡️ Security Score",
|
|
f"{security_score:.0f}%",
|
|
delta=f"{security_score:.0f}%" if security_score < 100 else None,
|
|
help="Security headers implementation score"
|
|
)
|
|
|
|
with col5:
|
|
missing_titles = results.get('content_analysis', {}).get('title_analysis', {}).get('missing_titles', 0)
|
|
st.metric(
|
|
"📝 Missing Titles",
|
|
missing_titles,
|
|
delta=f"-{missing_titles}" if missing_titles > 0 else None,
|
|
help="Pages without title tags"
|
|
)
|
|
|
|
with col6:
|
|
image_count = results.get('image_optimization', {}).get('image_count', 0)
|
|
st.metric(
|
|
"🖼️ Images Analyzed",
|
|
image_count,
|
|
help="Total images found and analyzed"
|
|
)
|
|
|
|
# Analysis timestamp
|
|
if results.get('analysis_timestamp'):
|
|
timestamp = datetime.fromisoformat(results['analysis_timestamp'].replace('Z', '+00:00'))
|
|
st.caption(f"📅 Audit completed: {timestamp.strftime('%Y-%m-%d %H:%M:%S UTC')}")
|
|
|
|
def _render_detailed_analysis(self, results: Dict[str, Any]):
|
|
"""Render detailed analysis in tabs."""
|
|
|
|
# Create main analysis tabs
|
|
tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([
|
|
"🔍 Technical Issues",
|
|
"⚡ Performance",
|
|
"📊 Content Analysis",
|
|
"🔗 URL Structure",
|
|
"🖼️ Image SEO",
|
|
"🛡️ Security",
|
|
"🤖 AI Recommendations"
|
|
])
|
|
|
|
with tab1:
|
|
self._render_technical_issues(results.get('technical_issues', {}))
|
|
|
|
with tab2:
|
|
self._render_performance_analysis(results.get('performance_analysis', {}))
|
|
|
|
with tab3:
|
|
self._render_content_analysis(results.get('content_analysis', {}))
|
|
|
|
with tab4:
|
|
self._render_url_structure(results.get('url_structure', {}))
|
|
|
|
with tab5:
|
|
self._render_image_analysis(results.get('image_optimization', {}))
|
|
|
|
with tab6:
|
|
self._render_security_analysis(results.get('security_headers', {}))
|
|
|
|
with tab7:
|
|
self._render_ai_recommendations(results.get('ai_recommendations', {}))
|
|
|
|
def _render_technical_issues(self, technical_data: Dict[str, Any]):
|
|
"""Render technical issues analysis."""
|
|
|
|
st.markdown("### 🔍 Technical SEO Issues")
|
|
|
|
if not technical_data:
|
|
st.info("No technical issues data available")
|
|
return
|
|
|
|
# HTTP Errors
|
|
if technical_data.get('http_errors'):
|
|
http_errors = technical_data['http_errors']
|
|
|
|
st.markdown("#### ❌ HTTP Status Code Errors")
|
|
|
|
if http_errors.get('total_errors', 0) > 0:
|
|
st.error(f"Found {http_errors['total_errors']} pages with HTTP errors!")
|
|
|
|
# Error breakdown chart
|
|
if http_errors.get('error_breakdown'):
|
|
error_df = pd.DataFrame(
|
|
list(http_errors['error_breakdown'].items()),
|
|
columns=['Status Code', 'Count']
|
|
)
|
|
|
|
fig = px.bar(error_df, x='Status Code', y='Count',
|
|
title="HTTP Error Distribution")
|
|
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
# Error pages table
|
|
if http_errors.get('error_pages'):
|
|
st.markdown("**Pages with Errors:**")
|
|
error_pages_df = pd.DataFrame(http_errors['error_pages'])
|
|
st.dataframe(error_pages_df, use_container_width=True)
|
|
else:
|
|
st.success("✅ No HTTP errors found!")
|
|
|
|
# Redirect Issues
|
|
if technical_data.get('redirect_issues'):
|
|
redirect_data = technical_data['redirect_issues']
|
|
|
|
st.markdown("#### 🔄 Redirect Analysis")
|
|
|
|
total_redirects = redirect_data.get('total_redirects', 0)
|
|
|
|
if total_redirects > 0:
|
|
st.warning(f"Found {total_redirects} redirect(s)")
|
|
|
|
# Redirect types
|
|
if redirect_data.get('redirect_types'):
|
|
redirect_df = pd.DataFrame(
|
|
list(redirect_data['redirect_types'].items()),
|
|
columns=['Redirect Type', 'Count']
|
|
)
|
|
st.bar_chart(redirect_df.set_index('Redirect Type'))
|
|
else:
|
|
st.success("✅ No redirects found")
|
|
|
|
# Duplicate Content
|
|
if technical_data.get('duplicate_content'):
|
|
duplicate_data = technical_data['duplicate_content']
|
|
|
|
st.markdown("#### 📋 Duplicate Content Issues")
|
|
|
|
duplicate_titles = duplicate_data.get('duplicate_titles', 0)
|
|
|
|
if duplicate_titles > 0:
|
|
st.warning(f"Found {duplicate_titles} duplicate title(s)")
|
|
|
|
# Show duplicate title groups
|
|
if duplicate_data.get('pages_with_duplicate_titles'):
|
|
duplicate_df = pd.DataFrame(duplicate_data['pages_with_duplicate_titles'])
|
|
st.dataframe(duplicate_df, use_container_width=True)
|
|
else:
|
|
st.success("✅ No duplicate titles found")
|
|
|
|
# Missing Elements
|
|
if technical_data.get('missing_elements'):
|
|
missing_data = technical_data['missing_elements']
|
|
|
|
st.markdown("#### 📝 Missing SEO Elements")
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
with col1:
|
|
missing_titles = missing_data.get('missing_titles', 0)
|
|
if missing_titles > 0:
|
|
st.error(f"Missing Titles: {missing_titles}")
|
|
else:
|
|
st.success("All pages have titles ✅")
|
|
|
|
with col2:
|
|
missing_meta = missing_data.get('missing_meta_desc', 0)
|
|
if missing_meta > 0:
|
|
st.error(f"Missing Meta Descriptions: {missing_meta}")
|
|
else:
|
|
st.success("All pages have meta descriptions ✅")
|
|
|
|
with col3:
|
|
missing_h1 = missing_data.get('missing_h1', 0)
|
|
if missing_h1 > 0:
|
|
st.error(f"Missing H1 tags: {missing_h1}")
|
|
else:
|
|
st.success("All pages have H1 tags ✅")
|
|
|
|
def _render_performance_analysis(self, performance_data: Dict[str, Any]):
|
|
"""Render performance analysis."""
|
|
|
|
st.markdown("### ⚡ Website Performance Analysis")
|
|
|
|
if not performance_data:
|
|
st.info("No performance data available")
|
|
return
|
|
|
|
# Load Time Analysis
|
|
if performance_data.get('load_time_analysis'):
|
|
load_time_data = performance_data['load_time_analysis']
|
|
|
|
st.markdown("#### 🚀 Page Load Time Analysis")
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
with col1:
|
|
avg_load = load_time_data.get('avg_load_time', 0)
|
|
st.metric("Average Load Time", f"{avg_load:.2f}s")
|
|
|
|
with col2:
|
|
median_load = load_time_data.get('median_load_time', 0)
|
|
st.metric("Median Load Time", f"{median_load:.2f}s")
|
|
|
|
with col3:
|
|
p95_load = load_time_data.get('p95_load_time', 0)
|
|
st.metric("95th Percentile", f"{p95_load:.2f}s")
|
|
|
|
# Performance distribution
|
|
if load_time_data.get('performance_distribution'):
|
|
perf_dist = load_time_data['performance_distribution']
|
|
|
|
# Create pie chart for performance distribution
|
|
labels = ['Fast (≤1s)', 'Moderate (1-3s)', 'Slow (>3s)']
|
|
values = [
|
|
perf_dist.get('fast_pages', 0),
|
|
perf_dist.get('moderate_pages', 0),
|
|
perf_dist.get('slow_pages', 0)
|
|
]
|
|
|
|
fig = px.pie(values=values, names=labels,
|
|
title="Page Load Time Distribution")
|
|
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
# Content Size Analysis
|
|
if performance_data.get('content_size_analysis'):
|
|
size_data = performance_data['content_size_analysis']
|
|
|
|
st.markdown("#### 📦 Content Size Analysis")
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
with col1:
|
|
avg_size = size_data.get('avg_page_size', 0)
|
|
st.metric("Average Page Size", f"{avg_size/1024:.1f} KB")
|
|
|
|
with col2:
|
|
largest_size = size_data.get('largest_page', 0)
|
|
st.metric("Largest Page", f"{largest_size/1024:.1f} KB")
|
|
|
|
with col3:
|
|
large_pages = size_data.get('pages_over_1mb', 0)
|
|
st.metric("Pages >1MB", large_pages)
|
|
|
|
# Server Performance
|
|
if performance_data.get('server_performance'):
|
|
server_data = performance_data['server_performance']
|
|
|
|
st.markdown("#### 🖥️ Server Performance")
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
with col1:
|
|
success_rate = server_data.get('success_rate', 0)
|
|
st.metric("Success Rate", f"{success_rate:.1f}%")
|
|
|
|
with col2:
|
|
error_rate = server_data.get('error_rate', 0)
|
|
st.metric("Error Rate", f"{error_rate:.1f}%")
|
|
|
|
with col3:
|
|
redirect_rate = server_data.get('redirect_rate', 0)
|
|
st.metric("Redirect Rate", f"{redirect_rate:.1f}%")
|
|
|
|
def _render_content_analysis(self, content_data: Dict[str, Any]):
|
|
"""Render content structure analysis."""
|
|
|
|
st.markdown("### 📊 Content Structure Analysis")
|
|
|
|
if not content_data:
|
|
st.info("No content analysis data available")
|
|
return
|
|
|
|
# Title Analysis
|
|
if content_data.get('title_analysis'):
|
|
title_data = content_data['title_analysis']
|
|
|
|
st.markdown("#### 📝 Title Tag Analysis")
|
|
|
|
col1, col2 = st.columns(2)
|
|
|
|
with col1:
|
|
avg_title_length = title_data.get('avg_title_length', 0)
|
|
st.metric("Average Title Length", f"{avg_title_length:.0f} chars")
|
|
|
|
duplicate_titles = title_data.get('duplicate_titles', 0)
|
|
st.metric("Duplicate Titles", duplicate_titles)
|
|
|
|
with col2:
|
|
# Title length distribution
|
|
if title_data.get('title_length_distribution'):
|
|
length_dist = title_data['title_length_distribution']
|
|
|
|
labels = ['Too Short (<30)', 'Optimal (30-60)', 'Too Long (>60)']
|
|
values = [
|
|
length_dist.get('too_short', 0),
|
|
length_dist.get('optimal', 0),
|
|
length_dist.get('too_long', 0)
|
|
]
|
|
|
|
fig = px.pie(values=values, names=labels,
|
|
title="Title Length Distribution")
|
|
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
# Meta Description Analysis
|
|
if content_data.get('meta_description_analysis'):
|
|
meta_data = content_data['meta_description_analysis']
|
|
|
|
st.markdown("#### 🏷️ Meta Description Analysis")
|
|
|
|
col1, col2 = st.columns(2)
|
|
|
|
with col1:
|
|
avg_meta_length = meta_data.get('avg_meta_length', 0)
|
|
st.metric("Average Meta Length", f"{avg_meta_length:.0f} chars")
|
|
|
|
missing_meta = meta_data.get('missing_meta_descriptions', 0)
|
|
st.metric("Missing Meta Descriptions", missing_meta)
|
|
|
|
with col2:
|
|
# Meta length distribution
|
|
if meta_data.get('meta_length_distribution'):
|
|
meta_dist = meta_data['meta_length_distribution']
|
|
|
|
labels = ['Too Short (<120)', 'Optimal (120-160)', 'Too Long (>160)']
|
|
values = [
|
|
meta_dist.get('too_short', 0),
|
|
meta_dist.get('optimal', 0),
|
|
meta_dist.get('too_long', 0)
|
|
]
|
|
|
|
fig = px.pie(values=values, names=labels,
|
|
title="Meta Description Length Distribution")
|
|
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
# Heading Structure
|
|
if content_data.get('heading_structure'):
|
|
heading_data = content_data['heading_structure']
|
|
|
|
st.markdown("#### 📋 Heading Structure Analysis")
|
|
|
|
# Create heading usage chart
|
|
heading_usage = []
|
|
for heading_type, data in heading_data.items():
|
|
heading_usage.append({
|
|
'Heading': heading_type.replace('_usage', '').upper(),
|
|
'Usage Rate': data.get('usage_rate', 0),
|
|
'Pages': data.get('pages_with_heading', 0)
|
|
})
|
|
|
|
if heading_usage:
|
|
heading_df = pd.DataFrame(heading_usage)
|
|
|
|
fig = px.bar(heading_df, x='Heading', y='Usage Rate',
|
|
title="Heading Tag Usage Rates")
|
|
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
st.dataframe(heading_df, use_container_width=True)
|
|
|
|
def _render_url_structure(self, url_data: Dict[str, Any]):
|
|
"""Render URL structure analysis."""
|
|
|
|
st.markdown("### 🔗 URL Structure Analysis")
|
|
|
|
if not url_data:
|
|
st.info("No URL structure data available")
|
|
return
|
|
|
|
# URL Length Analysis
|
|
if url_data.get('url_length_analysis'):
|
|
length_data = url_data['url_length_analysis']
|
|
|
|
st.markdown("#### 📏 URL Length Analysis")
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
with col1:
|
|
avg_length = length_data.get('avg_url_length', 0)
|
|
st.metric("Average URL Length", f"{avg_length:.0f} chars")
|
|
|
|
with col2:
|
|
max_length = length_data.get('max_url_length', 0)
|
|
st.metric("Longest URL", f"{max_length:.0f} chars")
|
|
|
|
with col3:
|
|
long_urls = length_data.get('long_urls_count', 0)
|
|
st.metric("URLs >100 chars", long_urls)
|
|
|
|
# URL Structure Patterns
|
|
if url_data.get('url_structure_patterns'):
|
|
pattern_data = url_data['url_structure_patterns']
|
|
|
|
st.markdown("#### 🏗️ URL Structure Patterns")
|
|
|
|
col1, col2 = st.columns(2)
|
|
|
|
with col1:
|
|
https_usage = pattern_data.get('https_usage', 0)
|
|
st.metric("HTTPS Usage", f"{https_usage:.1f}%")
|
|
|
|
with col2:
|
|
subdomain_usage = pattern_data.get('subdomain_usage', 0)
|
|
st.metric("Subdomains Found", subdomain_usage)
|
|
|
|
# Path Analysis
|
|
if url_data.get('path_analysis'):
|
|
path_data = url_data['path_analysis']
|
|
|
|
st.markdown("#### 📂 Path Depth Analysis")
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
with col1:
|
|
avg_depth = path_data.get('avg_path_depth', 0)
|
|
st.metric("Average Path Depth", f"{avg_depth:.1f}")
|
|
|
|
with col2:
|
|
max_depth = path_data.get('max_path_depth', 0)
|
|
st.metric("Maximum Depth", max_depth)
|
|
|
|
with col3:
|
|
deep_paths = path_data.get('deep_paths_count', 0)
|
|
st.metric("Deep Paths (>4)", deep_paths)
|
|
|
|
# Optimization Issues
|
|
if url_data.get('url_optimization'):
|
|
opt_data = url_data['url_optimization']
|
|
|
|
st.markdown("#### ⚠️ URL Optimization Issues")
|
|
|
|
issues_found = opt_data.get('issues_found', 0)
|
|
recommendations = opt_data.get('optimization_recommendations', [])
|
|
|
|
if issues_found > 0:
|
|
st.warning(f"Found {issues_found} URL optimization issue(s)")
|
|
|
|
for rec in recommendations:
|
|
st.write(f"• {rec}")
|
|
else:
|
|
st.success("✅ No URL optimization issues found")
|
|
|
|
def _render_image_analysis(self, image_data: Dict[str, Any]):
|
|
"""Render image SEO analysis."""
|
|
|
|
st.markdown("### 🖼️ Image SEO Analysis")
|
|
|
|
if not image_data:
|
|
st.info("No image analysis data available")
|
|
return
|
|
|
|
# Image overview
|
|
image_count = image_data.get('image_count', 0)
|
|
st.metric("Total Images Found", image_count)
|
|
|
|
if image_count > 0:
|
|
# Alt text analysis
|
|
if image_data.get('alt_text_analysis'):
|
|
alt_data = image_data['alt_text_analysis']
|
|
|
|
st.markdown("#### 📝 Alt Text Analysis")
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
with col1:
|
|
images_with_alt = alt_data.get('images_with_alt', 0)
|
|
st.metric("Images with Alt Text", images_with_alt)
|
|
|
|
with col2:
|
|
images_missing_alt = alt_data.get('images_missing_alt', 0)
|
|
st.metric("Missing Alt Text", images_missing_alt)
|
|
|
|
with col3:
|
|
alt_coverage = alt_data.get('alt_text_coverage', 0)
|
|
st.metric("Alt Text Coverage", f"{alt_coverage:.1f}%")
|
|
|
|
# Image format analysis
|
|
if image_data.get('image_format_analysis'):
|
|
format_data = image_data['image_format_analysis']
|
|
|
|
st.markdown("#### 🎨 Image Format Analysis")
|
|
|
|
if format_data.get('format_distribution'):
|
|
format_dist = format_data['format_distribution']
|
|
|
|
format_df = pd.DataFrame(
|
|
list(format_dist.items()),
|
|
columns=['Format', 'Count']
|
|
)
|
|
|
|
fig = px.pie(format_df, values='Count', names='Format',
|
|
title="Image Format Distribution")
|
|
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
modern_formats = format_data.get('modern_format_usage', 0)
|
|
st.metric("Modern Formats (WebP/AVIF)", modern_formats)
|
|
else:
|
|
st.info("No images found to analyze")
|
|
|
|
def _render_security_analysis(self, security_data: Dict[str, Any]):
|
|
"""Render security analysis."""
|
|
|
|
st.markdown("### 🛡️ Security Headers Analysis")
|
|
|
|
if not security_data:
|
|
st.info("No security analysis data available")
|
|
return
|
|
|
|
# Security score
|
|
security_score = security_data.get('security_score', 0)
|
|
|
|
col1, col2 = st.columns([1, 2])
|
|
|
|
with col1:
|
|
st.metric("Security Score", f"{security_score:.0f}%")
|
|
|
|
if security_score >= 80:
|
|
st.success("🔒 Good security posture")
|
|
elif security_score >= 50:
|
|
st.warning("⚠️ Moderate security")
|
|
else:
|
|
st.error("🚨 Poor security posture")
|
|
|
|
with col2:
|
|
# Security headers status
|
|
if security_data.get('security_headers_present'):
|
|
headers_status = security_data['security_headers_present']
|
|
|
|
st.markdown("**Security Headers Status:**")
|
|
|
|
for header, present in headers_status.items():
|
|
status = "✅" if present else "❌"
|
|
st.write(f"{status} {header}")
|
|
|
|
# Security recommendations
|
|
if security_data.get('security_recommendations'):
|
|
recommendations = security_data['security_recommendations']
|
|
|
|
if recommendations:
|
|
st.markdown("#### 🔧 Security Recommendations")
|
|
|
|
for rec in recommendations:
|
|
st.write(f"• {rec}")
|
|
else:
|
|
st.success("✅ All security headers properly configured")
|
|
|
|
def _render_ai_recommendations(self, ai_data: Dict[str, Any]):
|
|
"""Render AI-generated recommendations."""
|
|
|
|
st.markdown("### 🤖 AI-Powered Technical Recommendations")
|
|
|
|
if not ai_data:
|
|
st.info("No AI recommendations available")
|
|
return
|
|
|
|
# Critical Issues
|
|
if ai_data.get('critical_issues'):
|
|
st.markdown("#### 🚨 Critical Issues (Fix Immediately)")
|
|
|
|
critical_issues = ai_data['critical_issues']
|
|
for issue in critical_issues:
|
|
st.error(f"🚨 {issue}")
|
|
|
|
# High Priority
|
|
if ai_data.get('high_priority'):
|
|
st.markdown("#### 🔥 High Priority Optimizations")
|
|
|
|
high_priority = ai_data['high_priority']
|
|
for item in high_priority:
|
|
st.warning(f"⚡ {item}")
|
|
|
|
# Medium Priority
|
|
if ai_data.get('medium_priority'):
|
|
st.markdown("#### 📈 Medium Priority Improvements")
|
|
|
|
medium_priority = ai_data['medium_priority']
|
|
for item in medium_priority:
|
|
st.info(f"📊 {item}")
|
|
|
|
# Implementation Steps
|
|
if ai_data.get('implementation_steps'):
|
|
st.markdown("#### 🛠️ Implementation Steps")
|
|
|
|
steps = ai_data['implementation_steps']
|
|
for i, step in enumerate(steps, 1):
|
|
st.write(f"{i}. {step}")
|
|
|
|
# Expected Impact
|
|
if ai_data.get('expected_impact'):
|
|
st.markdown("#### 📈 Expected Impact Assessment")
|
|
|
|
impact = ai_data['expected_impact']
|
|
st.markdown(impact)
|
|
|
|
def _render_export_options(self, results: Dict[str, Any]):
|
|
"""Render export options for analysis results."""
|
|
|
|
st.markdown("---")
|
|
st.markdown("### 📥 Export Technical SEO Audit")
|
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
with col1:
|
|
# JSON export
|
|
if st.button("📄 Export Full Report (JSON)", use_container_width=True):
|
|
json_data = json.dumps(results, indent=2, default=str)
|
|
|
|
st.download_button(
|
|
label="⬇️ Download JSON Report",
|
|
data=json_data,
|
|
file_name=f"technical_seo_audit_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
|
mime="application/json",
|
|
use_container_width=True
|
|
)
|
|
|
|
with col2:
|
|
# CSV export for issues
|
|
if st.button("📊 Export Issues CSV", use_container_width=True):
|
|
issues_data = self._prepare_issues_csv(results)
|
|
|
|
if issues_data:
|
|
st.download_button(
|
|
label="⬇️ Download Issues CSV",
|
|
data=issues_data,
|
|
file_name=f"technical_issues_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
|
|
mime="text/csv",
|
|
use_container_width=True
|
|
)
|
|
else:
|
|
st.info("No issues found to export")
|
|
|
|
with col3:
|
|
# Executive summary
|
|
if st.button("📋 Executive Summary", use_container_width=True):
|
|
summary = self._generate_executive_summary(results)
|
|
|
|
st.download_button(
|
|
label="⬇️ Download Summary",
|
|
data=summary,
|
|
file_name=f"technical_seo_summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
|
|
mime="text/plain",
|
|
use_container_width=True
|
|
)
|
|
|
|
def _prepare_issues_csv(self, results: Dict[str, Any]) -> str:
|
|
"""Prepare CSV data for technical issues."""
|
|
|
|
issues_list = []
|
|
|
|
# HTTP errors
|
|
http_errors = results.get('technical_issues', {}).get('http_errors', {})
|
|
if http_errors.get('error_pages'):
|
|
for error in http_errors['error_pages']:
|
|
issues_list.append({
|
|
'Issue Type': 'HTTP Error',
|
|
'Severity': 'High',
|
|
'URL': error.get('url', ''),
|
|
'Status Code': error.get('status', ''),
|
|
'Description': f"HTTP {error.get('status', '')} error"
|
|
})
|
|
|
|
# Missing elements
|
|
missing_elements = results.get('technical_issues', {}).get('missing_elements', {})
|
|
|
|
# Add more issue types as needed...
|
|
|
|
if issues_list:
|
|
issues_df = pd.DataFrame(issues_list)
|
|
return issues_df.to_csv(index=False)
|
|
|
|
return ""
|
|
|
|
def _generate_executive_summary(self, results: Dict[str, Any]) -> str:
|
|
"""Generate executive summary report."""
|
|
|
|
website_url = results.get('website_url', 'Unknown')
|
|
timestamp = results.get('analysis_timestamp', datetime.now().isoformat())
|
|
|
|
summary = f"""
|
|
TECHNICAL SEO AUDIT - EXECUTIVE SUMMARY
|
|
======================================
|
|
|
|
Website: {website_url}
|
|
Audit Date: {timestamp}
|
|
|
|
AUDIT OVERVIEW
|
|
--------------
|
|
Pages Crawled: {results.get('crawl_overview', {}).get('pages_crawled', 0)}
|
|
HTTP Errors: {results.get('technical_issues', {}).get('http_errors', {}).get('total_errors', 0)}
|
|
Average Load Time: {results.get('performance_analysis', {}).get('load_time_analysis', {}).get('avg_load_time', 0):.2f}s
|
|
Security Score: {results.get('security_headers', {}).get('security_score', 0):.0f}%
|
|
|
|
CRITICAL FINDINGS
|
|
-----------------
|
|
"""
|
|
|
|
# Add critical findings
|
|
error_count = results.get('technical_issues', {}).get('http_errors', {}).get('total_errors', 0)
|
|
if error_count > 0:
|
|
summary += f"• {error_count} pages have HTTP errors requiring immediate attention\n"
|
|
|
|
avg_load_time = results.get('performance_analysis', {}).get('load_time_analysis', {}).get('avg_load_time', 0)
|
|
if avg_load_time > 3:
|
|
summary += f"• Page load times are slow (avg: {avg_load_time:.2f}s), impacting user experience\n"
|
|
|
|
security_score = results.get('security_headers', {}).get('security_score', 0)
|
|
if security_score < 80:
|
|
summary += f"• Security headers need improvement (current score: {security_score:.0f}%)\n"
|
|
|
|
summary += f"\n\nDetailed technical audit completed by ALwrity Technical SEO Crawler\nGenerated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
|
|
return summary
|
|
|
|
# Render function for integration with main dashboard
|
|
def render_technical_seo_crawler():
|
|
"""Render the Technical SEO Crawler UI."""
|
|
ui = TechnicalSEOCrawlerUI()
|
|
ui.render() |