Update on_page_seo_analyzer.py
PEP 8 Compliance: Ensure proper spacing around operators and after commas. Group import statements by standard library, third-party, and local imports. Error Handling: Improve error messages to be more descriptive and helpful. Code Structure: Ensure consistent indentation and formatting. Remove any unused imports or commented-out code. Docstrings: Ensure all functions have detailed docstrings explaining their purpose, arguments, and return values. Optimization: Reduce repeated calls to fetch_and_parse_html by reusing the soup object.
This commit is contained in:
@@ -1,13 +1,24 @@
|
||||
import os
|
||||
import json
|
||||
import streamlit as st
|
||||
from tenacity import retry, stop_after_attempt, wait_random_exponential
|
||||
import cloudscraper
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import requests
|
||||
import csv
|
||||
import time
|
||||
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
|
||||
def fetch_and_parse_html(url):
|
||||
"""Fetches HTML content from the given URL using CloudScraper and parses it with BeautifulSoup."""
|
||||
"""
|
||||
Fetches HTML content from the given URL using CloudScraper and parses it with BeautifulSoup.
|
||||
|
||||
Args:
|
||||
url (str): The URL of the webpage to fetch.
|
||||
|
||||
Returns:
|
||||
BeautifulSoup: Parsed HTML content.
|
||||
"""
|
||||
try:
|
||||
scraper = cloudscraper.create_scraper()
|
||||
html = scraper.get(url)
|
||||
@@ -18,7 +29,15 @@ def fetch_and_parse_html(url):
|
||||
return None
|
||||
|
||||
def extract_meta_data(soup):
|
||||
"""Extracts meta data like title, description, and robots directives from the parsed HTML."""
|
||||
"""
|
||||
Extracts meta data like title, description, and robots directives from the parsed HTML.
|
||||
|
||||
Args:
|
||||
soup (BeautifulSoup): Parsed HTML content.
|
||||
|
||||
Returns:
|
||||
dict: Extracted meta data.
|
||||
"""
|
||||
try:
|
||||
metatitle = soup.find('title').get_text() if soup.find('title') else "Title not found"
|
||||
metadescription = soup.find('meta', attrs={'name': 'description'})["content"] if soup.find('meta', attrs={'name': 'description'}) else "Description not found"
|
||||
@@ -27,11 +46,10 @@ def extract_meta_data(soup):
|
||||
charset = soup.find('meta', attrs={'charset': True})["charset"] if soup.find('meta', attrs={'charset': True}) else "Charset not found"
|
||||
html_language = soup.find('html')["lang"] if soup.find('html') else "Language not found"
|
||||
|
||||
# Check for missing or long title/meta description
|
||||
title_length = len(metatitle) if metatitle != "Title not found" else 0
|
||||
description_length = len(metadescription) if metadescription != "Description not found" else 0
|
||||
title_message = "✅ Title length is good." if 30 <= title_length <= 60 else "⚠️ Title length should be between 30-60 characters. Aim for clear and concise titles that accurately reflect your page's content."
|
||||
description_message = "✅ Meta description length is good." if 70 <= description_length <= 160 else "⚠️ Meta description should be between 70-160 characters. Craft compelling descriptions that entice users to click your link."
|
||||
title_message = "✅ Title length is good." if 30 <= title_length <= 60 else "⚠️ Title length should be between 30-60 characters."
|
||||
description_message = "✅ Meta description length is good." if 70 <= description_length <= 160 else "⚠️ Meta description should be between 70-160 characters."
|
||||
|
||||
return {
|
||||
"metatitle": metatitle,
|
||||
@@ -48,15 +66,22 @@ def extract_meta_data(soup):
|
||||
return {}
|
||||
|
||||
def extract_alternates_and_canonicals(soup):
|
||||
"""Extracts canonical URL, hreflangs, and mobile alternate links from the parsed HTML."""
|
||||
"""
|
||||
Extracts canonical URL, hreflangs, and mobile alternate links from the parsed HTML.
|
||||
|
||||
Args:
|
||||
soup (BeautifulSoup): Parsed HTML content.
|
||||
|
||||
Returns:
|
||||
dict: Extracted alternates and canonicals.
|
||||
"""
|
||||
try:
|
||||
canonical = soup.find('link', attrs={'rel': 'canonical'})["href"] if soup.find('link', attrs={'rel': 'canonical'}) else "Canonical not found"
|
||||
list_hreflangs = [[a['href'], a["hreflang"]] for a in soup.find_all('link', href=True, hreflang=True)] if soup.find_all('link', href=True, hreflang=True) else []
|
||||
mobile_alternate = soup.find('link', attrs={'media': 'only screen and (max-width: 640px)'})["href"] if soup.find('link', attrs={'media': 'only screen and (max-width: 640px)'}) else "Mobile Alternate not found"
|
||||
|
||||
# Provide user-friendly insights
|
||||
canonical_message = "✅ Canonical tag found. Great! This helps avoid duplicate content issues." if canonical != "Canonical not found" else "⚠️ Consider adding a canonical tag to tell search engines which version of your page is the main one, preventing confusion and potential duplicate content penalties."
|
||||
hreflangs_message = "✅ Hreflang tags are implemented. Good job! This is crucial for international audiences." if list_hreflangs else "⚠️ Consider implementing hreflang tags to help search engines understand the language variations of your site. This is essential for international SEO and can lead to better search rankings in different regions."
|
||||
canonical_message = "✅ Canonical tag found. Great! This helps avoid duplicate content issues." if canonical != "Canonical not found" else "⚠️ Consider adding a canonical tag."
|
||||
hreflangs_message = "✅ Hreflang tags are implemented. Good job!" if list_hreflangs else "⚠️ Consider implementing hreflang tags."
|
||||
|
||||
return {
|
||||
"canonical": canonical,
|
||||
@@ -70,27 +95,44 @@ def extract_alternates_and_canonicals(soup):
|
||||
return {}
|
||||
|
||||
def extract_schema_markup(soup):
|
||||
"""Extracts schema markup data from the parsed HTML."""
|
||||
"""
|
||||
Extracts schema markup data from the parsed HTML.
|
||||
|
||||
Args:
|
||||
soup (BeautifulSoup): Parsed HTML content.
|
||||
|
||||
Returns:
|
||||
dict: Extracted schema markup data.
|
||||
"""
|
||||
try:
|
||||
json_schema = soup.find('script', attrs={'type': 'application/ld+json'})
|
||||
if json_schema:
|
||||
json_file = json.loads(json_schema.get_text())
|
||||
schema_types = [x['@type'] for x in json_file.get("@graph", [])] if "@graph" in json_file else [json_file["@type"]]
|
||||
schema_message = "✅ Schema markup found. Wonderful! This helps search engines better understand your content." if schema_types else "⚠️ No schema markup found. Consider adding structured data (like JSON-LD schema) to your pages. It can enhance search results by giving search engines more context about your content, potentially leading to richer snippets and improved visibility."
|
||||
schema_message = "✅ Schema markup found. Wonderful!" if schema_types else "⚠️ No schema markup found."
|
||||
return {
|
||||
"schema_types": schema_types,
|
||||
"schema_message": schema_message
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"schema_message": "⚠️ No schema markup found. Consider adding structured data (like JSON-LD schema) to your pages. It can enhance search results by giving search engines more context about your content, potentially leading to richer snippets and improved visibility."
|
||||
"schema_message": "⚠️ No schema markup found."
|
||||
}
|
||||
except Exception as e:
|
||||
st.warning(f"⚠️ Error extracting schema markup: {e}")
|
||||
return {}
|
||||
|
||||
def extract_content_data(soup, url):
|
||||
"""Extracts content data such as text length, headers, and insights about images and links."""
|
||||
"""
|
||||
Extracts content data such as text length, headers, and insights about images and links.
|
||||
|
||||
Args:
|
||||
soup (BeautifulSoup): Parsed HTML content.
|
||||
url (str): The URL of the webpage.
|
||||
|
||||
Returns:
|
||||
dict: Extracted content data.
|
||||
"""
|
||||
try:
|
||||
paragraph = [a.get_text() for a in soup.find_all('p')]
|
||||
text_length = sum([len(a) for a in paragraph])
|
||||
@@ -115,26 +157,18 @@ def extract_content_data(soup, url):
|
||||
else:
|
||||
external_links.append(href)
|
||||
|
||||
# Content length evaluation
|
||||
content_message = "✅ Content length is adequate." if text_length > 300 else "⚠️ Consider adding more content (minimum 300 words). Aim for thorough and engaging content that provides value to your audience. Longer content often ranks higher in search results."
|
||||
|
||||
# Header evaluation
|
||||
h1_message = "✅ H1 tag found. Good! It helps structure your content." if h1 else "⚠️ Missing H1 tag. An H1 tag (the most important heading) is crucial for content structure and SEO. Add an H1 tag clearly defining your page's main topic."
|
||||
|
||||
# Image alt text check
|
||||
content_message = "✅ Content length is adequate." if text_length > 300 else "⚠️ Consider adding more content (minimum 300 words)."
|
||||
h1_message = "✅ H1 tag found. Good!" if h1 else "⚠️ Missing H1 tag."
|
||||
missing_alt_texts = sum([1 for img in images if img[1] == "No alt text"])
|
||||
alt_text_message = "✅ All images have alt text. Great! This helps accessibility and SEO." if missing_alt_texts == 0 else f"⚠️ {missing_alt_texts} images are missing alt text. Consider adding descriptive alt text descriptions to all images. Alt text helps users with visual impairments understand the images, and search engines use it to better understand the context of the page."
|
||||
|
||||
# Links evaluation
|
||||
internal_links_message = f"✅ {len(internal_links)} internal links found. Good practice for website structure."
|
||||
external_links_message = f"✅ {len(external_links)} external links found. Links to high-quality external sources add value."
|
||||
alt_text_message = "✅ All images have alt text. Great!" if missing_alt_texts == 0 else f"⚠️ {missing_alt_texts} images are missing alt text."
|
||||
internal_links_message = f"✅ {len(internal_links)} internal links found."
|
||||
external_links_message = f"✅ {len(external_links)} external links found."
|
||||
|
||||
# Link Insights
|
||||
link_insights = []
|
||||
if internal_links:
|
||||
link_insights.append("✅ Internal links are present.")
|
||||
link_insights.append("✅ Internal links are present.")
|
||||
if external_links:
|
||||
link_insights.append("✅ External links are present.")
|
||||
link_insights.append("✅ External links are present.")
|
||||
|
||||
return {
|
||||
"text_length": text_length,
|
||||
@@ -145,17 +179,25 @@ def extract_content_data(soup, url):
|
||||
"alt_text_message": alt_text_message,
|
||||
"internal_links_message": internal_links_message,
|
||||
"external_links_message": external_links_message,
|
||||
"link_insights": link_insights # Added new key for link insights
|
||||
"link_insights": link_insights
|
||||
}
|
||||
except Exception as e:
|
||||
st.warning(f"⚠️ Error extracting content data: {e}")
|
||||
return {}
|
||||
|
||||
def extract_open_graph(soup):
|
||||
"""Extracts Open Graph data from the parsed HTML."""
|
||||
"""
|
||||
Extracts Open Graph data from the parsed HTML.
|
||||
|
||||
Args:
|
||||
soup (BeautifulSoup): Parsed HTML content.
|
||||
|
||||
Returns:
|
||||
dict: Extracted Open Graph data.
|
||||
"""
|
||||
try:
|
||||
open_graph = [[a["property"].replace("og:", ""), a["content"]] for a in soup.select("meta[property^=og]")]
|
||||
open_graph_message = "✅ Open Graph tags found. Awesome! These improve your social media sharing." if open_graph else "⚠️ No Open Graph tags found. Consider adding Open Graph tags. They help your content appear better when shared on social media, with clearer titles, descriptions, and images."
|
||||
open_graph_message = "✅ Open Graph tags found. Awesome!" if open_graph else "⚠️ No Open Graph tags found."
|
||||
return {
|
||||
"open_graph": open_graph,
|
||||
"open_graph_message": open_graph_message
|
||||
@@ -165,13 +207,21 @@ def extract_open_graph(soup):
|
||||
return {}
|
||||
|
||||
def extract_social_tags(soup):
|
||||
"""Extracts Twitter Card and Facebook Open Graph data from the parsed HTML."""
|
||||
"""
|
||||
Extracts Twitter Card and Facebook Open Graph data from the parsed HTML.
|
||||
|
||||
Args:
|
||||
soup (BeautifulSoup): Parsed HTML content.
|
||||
|
||||
Returns:
|
||||
dict: Extracted social tags.
|
||||
"""
|
||||
try:
|
||||
twitter_cards = [[a["name"].replace("twitter:", ""), a["content"]] for a in soup.select("meta[name^=twitter]")]
|
||||
facebook_open_graph = [[a["property"].replace("og:", ""), a["content"]] for a in soup.select("meta[property^=og]")]
|
||||
|
||||
twitter_message = "✅ Twitter Card tags found." if twitter_cards else "⚠️ No Twitter Card tags found. Consider adding them for better visibility on Twitter."
|
||||
facebook_message = "✅ Facebook Open Graph tags found." if facebook_open_graph else "⚠️ No Facebook Open Graph tags found. Consider adding them for better sharing on Facebook."
|
||||
twitter_message = "✅ Twitter Card tags found." if twitter_cards else "⚠️ No Twitter Card tags found."
|
||||
facebook_message = "✅ Facebook Open Graph tags found." if facebook_open_graph else "⚠️ No Facebook Open Graph tags found."
|
||||
|
||||
return {
|
||||
"twitter_cards": twitter_cards,
|
||||
@@ -184,7 +234,15 @@ def extract_social_tags(soup):
|
||||
return {}
|
||||
|
||||
def check_page_speed(url):
|
||||
"""Fetches and analyzes page speed metrics using Google PageSpeed Insights API."""
|
||||
"""
|
||||
Fetches and analyzes page speed metrics using Google PageSpeed Insights API.
|
||||
|
||||
Args:
|
||||
url (str): The URL of the webpage.
|
||||
|
||||
Returns:
|
||||
dict: Page speed data.
|
||||
"""
|
||||
try:
|
||||
api_key = "YOUR_GOOGLE_PAGESPEED_API_KEY"
|
||||
response = requests.get(f"https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url={url}&key={api_key}")
|
||||
@@ -200,10 +258,18 @@ def check_page_speed(url):
|
||||
return {}
|
||||
|
||||
def check_mobile_usability(soup):
|
||||
"""Checks if the website is mobile-friendly based on viewport and other elements."""
|
||||
"""
|
||||
Checks if the website is mobile-friendly based on viewport and other elements.
|
||||
|
||||
Args:
|
||||
soup (BeautifulSoup): Parsed HTML content.
|
||||
|
||||
Returns:
|
||||
dict: Mobile usability data.
|
||||
"""
|
||||
try:
|
||||
viewport = soup.find('meta', attrs={'name': 'viewport'})["content"] if soup.find('meta', attrs={'name': 'viewport'}) else ""
|
||||
mobile_message = "✅ Mobile viewport is set. Great! This indicates the site is designed to be responsive on different devices." if viewport else "⚠️ Mobile viewport meta tag is missing. Ensure your site is designed to work well on all devices, especially mobile. This is essential for a great user experience and improved SEO."
|
||||
mobile_message = "✅ Mobile viewport is set." if viewport else "⚠️ Mobile viewport meta tag is missing."
|
||||
return {
|
||||
"mobile_message": mobile_message
|
||||
}
|
||||
@@ -212,11 +278,19 @@ def check_mobile_usability(soup):
|
||||
return {}
|
||||
|
||||
def check_alt_text(soup):
|
||||
"""Checks if all images have alt text."""
|
||||
"""
|
||||
Checks if all images have alt text.
|
||||
|
||||
Args:
|
||||
soup (BeautifulSoup): Parsed HTML content.
|
||||
|
||||
Returns:
|
||||
dict: Alt text data.
|
||||
"""
|
||||
try:
|
||||
images = soup.find_all('img')
|
||||
missing_alt_texts = sum([1 for img in images if not img.get("alt")])
|
||||
alt_text_message = "✅ All images have alt text. Great! This helps accessibility and SEO." if missing_alt_texts == 0 else f"⚠️ {missing_alt_texts} images are missing alt text. Consider adding descriptive alt text for better accessibility. Alt text helps users with visual impairments understand images and improves SEO."
|
||||
alt_text_message = "✅ All images have alt text. Great!" if missing_alt_texts == 0 else f"⚠️ {missing_alt_texts} images are missing alt text."
|
||||
return {
|
||||
"alt_text_message": alt_text_message
|
||||
}
|
||||
@@ -225,7 +299,15 @@ def check_alt_text(soup):
|
||||
return {}
|
||||
|
||||
def fetch_seo_data(url):
|
||||
"""Fetches SEO-related data from the provided URL and returns a dictionary with results."""
|
||||
"""
|
||||
Fetches SEO-related data from the provided URL and returns a dictionary with results.
|
||||
|
||||
Args:
|
||||
url (str): The URL of the webpage to analyze.
|
||||
|
||||
Returns:
|
||||
dict: SEO data.
|
||||
"""
|
||||
soup = fetch_and_parse_html(url)
|
||||
if not soup:
|
||||
return {}
|
||||
@@ -245,7 +327,13 @@ def fetch_seo_data(url):
|
||||
}
|
||||
|
||||
def download_csv(data, filename='seo_data.csv'):
|
||||
"""Downloads the data as a CSV file."""
|
||||
"""
|
||||
Downloads the data as a CSV file.
|
||||
|
||||
Args:
|
||||
data (dict): SEO data to download.
|
||||
filename (str): Filename for the downloaded CSV file.
|
||||
"""
|
||||
with open(filename, 'w', newline='', encoding='utf-8') as file:
|
||||
writer = csv.writer(file)
|
||||
for key, value in data.items():
|
||||
@@ -256,6 +344,9 @@ def download_csv(data, filename='seo_data.csv'):
|
||||
st.success(f"Data exported to {filename}")
|
||||
|
||||
def analyze_onpage_seo():
|
||||
"""
|
||||
Main function to analyze on-page SEO using Streamlit.
|
||||
"""
|
||||
st.title("ALwrity On Page SEO Analyzer")
|
||||
|
||||
url = st.text_input("Enter URL to Analyze", "")
|
||||
@@ -298,7 +389,6 @@ def analyze_onpage_seo():
|
||||
st.write(results['content_data']['content_message'])
|
||||
st.write(results['content_data']['alt_text_message'])
|
||||
|
||||
# Display link insights in a bullet point format
|
||||
for insight in results['content_data']['link_insights']:
|
||||
st.write(f"- {insight}")
|
||||
|
||||
@@ -324,6 +414,5 @@ def analyze_onpage_seo():
|
||||
st.subheader("Accessibility")
|
||||
st.write(alt_text['alt_text_message'])
|
||||
|
||||
# Option to download results
|
||||
if st.button("Download CSV"):
|
||||
download_csv(results)
|
||||
|
||||
Reference in New Issue
Block a user