كود لاكتشاف الصفحات "اليتيمة" (Orphan Pages) التي لا يراها جوجل


ماذا سنبني اليوم؟

اليوم، سنقوم ببناء أداة قوية باستخدام بايثون تساعدنا في اكتشاف الصفحات اليتيمة (Orphan Pages) على أي موقع ويب. الصفحات اليتيمة هي تلك الصفحات التي توجد على الموقع ولكن لا يوجد أي رابط داخلي يشير إليها من أي صفحة أخرى. هذا يجعلها "غير مرئية" لمحركات البحث مثل جوجل، مما يقلل من فرص ظهورها في نتائج البحث ويؤثر سلباً على تحسين محركات البحث (SEO) للموقع.

لماذا الصفحات اليتيمة مشكلة في SEO؟

  • صعوبة الاكتشاف: لا تستطيع محركات البحث اكتشاف هذه الصفحات وزحفها بشكل فعال إذا لم يتم ربطها داخلياً.
  • فقدان قيمة الروابط (Link Equity): لا تستفيد هذه الصفحات من تدفق "عصارة الروابط" (link equity) من الصفحات الأخرى، مما يضعف سلطتها في نظر محركات البحث.
  • تجربة المستخدم: قد تكون الصفحات المهمة مخفية عن المستخدمين أيضاً إذا لم يتم ربطها بشكل صحيح.

المنهجية

ستتبع أداتنا الخطوات التالية لاكتشاف الصفحات اليتيمة:

  1. زحف الروابط الداخلية: سنقوم بزحف الموقع بدءاً من الصفحة الرئيسية لجمع جميع الروابط الداخلية التي يمكن الوصول إليها. هذا يحاكي سلوك زاحف جوجل لاكتشاف الصفحات عبر الروابط.
  2. تحليل ملف Sitemap.xml: سنقوم بتحليل ملف sitemap.xml الخاص بالموقع لجمع قائمة بجميع الصفحات التي يخبر مالك الموقع محركات البحث بوجودها.
  3. المقارنة والتحديد: سنقارن بين قائمة الصفحات المكتشفة عبر الزحف الداخلي وقائمة الصفحات الموجودة في ملف sitemap.xml. أي صفحة موجودة في sitemap.xml ولكن لم يتم اكتشافها عبر الزحف الداخلي هي صفحة يتيمة محتملة.

المتطلبات المسبقة

للتشغيل، ستحتاج إلى:

  • تثبيت بايثون 3.
  • تثبيت المكتبات التالية:
    • requests: لعمل طلبات HTTP.
    • BeautifulSoup4: لتحليل HTML واستخراج الروابط.
    • lxml: لتحليل XML (يستخدم بواسطة BeautifulSoup ويدعم تحليل Sitemap).

    يمكنك تثبيتها باستخدام الأمر التالي في سطر الأوامر:

    pip install requests beautifulsoup4 lxml

فهم الكود

يتكون الكود من ثلاث وظائف رئيسية:

  • get_all_internal_links(base_url): تقوم هذه الوظيفة بزحف الموقع بدءاً من base_url وتجمع جميع الروابط الداخلية التي تجدها. تستخدم قائمة انتظار (queue) لزيارة الصفحات بشكل منهجي وتتجنب الزحف اللانهائي للصفحات المكررة.
  • get_sitemap_urls(sitemap_url): تقوم هذه الوظيفة بجلب وتحليل ملف sitemap.xml (أو فهرس خريطة الموقع) وتستخرج جميع عناوين URL المذكورة فيه. تدعم الزحف المتكرر لملفات sitemap الفرعية.
  • find_orphan_pages(base_url): هي الوظيفة الرئيسية التي تجمع النتائج من الوظيفتين السابقتين وتقارن بينها لتحديد الصفحات اليتيمة.
ملاحظة تقنية هامة: الكود يقوم بتطبيع عناوين URL (URL normalization) عن طريق إزالة أجزاء الاستعلام (query parameters) وقطع التجزئة (fragments) لضمان مقارنة دقيقة للصفحات. قد تحتاج إلى تعديل هذا السلوك إذا كانت معلمات الاستعلام جزءاً أساسياً من تعريف صفحة فريدة في موقعك.
ملاحظة حول ملف robots.txt: هذا الكود لا يحترم ملف robots.txt بشكل افتراضي. في بيئات الإنتاج، قد تحتاج إلى دمج مكتبة لتحليل robots.txt (مثل robotexclusionrulesparser) لضمان الامتثال لقواعد الزحف المحددة من قبل الموقع. لأغراض هذا الدرس، نركز على المنطق الأساسي لاكتشاف الصفحات اليتيمة.

الكود العملي

قم بنسخ الكود التالي وحفظه في ملف بايثون (مثلاً: orphan_finder.py).

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import xml.etree.ElementTree as ET

def get_all_internal_links(base_url):
    """Crawls the website starting from base_url to find all internally linked pages."""
    domain = urlparse(base_url).netloc
    visited_urls = set()
    urls_to_visit = {base_url} # Start with the base URL
    
    print(f"بدء الزحف الداخلي من: {base_url}")

    while urls_to_visit:
        current_url = urls_to_visit.pop()
        
        # Normalize current_url before checking visited_urls
        parsed_current_url = urlparse(current_url)
        clean_current_url = urljoin(current_url, parsed_current_url.path) # Removes query and fragment

        if clean_current_url in visited_urls:
            continue

        print(f"يزحف: {clean_current_url}")
        visited_urls.add(clean_current_url) # Mark as visited

        try:
            response = requests.get(clean_current_url, timeout=5)
            response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
        except requests.exceptions.RequestException as e:
            print(f"خطأ في جلب {clean_current_url}: {e}")
            continue

        soup = BeautifulSoup(response.text, 'html.parser')
        for a_tag in soup.find_all('a', href=True):
            href = a_tag['href']
            full_url = urljoin(clean_current_url, href)
            parsed_full_url = urlparse(full_url)

            # Ensure it's an HTTP/HTTPS link and within the same domain
            if parsed_full_url.scheme in ('http', 'https') and parsed_full_url.netloc == domain:
                clean_link_url = urljoin(full_url, parsed_full_url.path) # Normalize URL
                if clean_link_url not in visited_urls: # Only add to queue if not yet visited
                    urls_to_visit.add(clean_link_url)
    
    return visited_urls # All URLs that were successfully visited and thus internally linked

def get_sitemap_urls(sitemap_url):
    """Parses a sitemap.xml (or sitemap index) to extract all URLs."""
    sitemap_urls = set()
    print(f"جلب الروابط من خريطة الموقع: {sitemap_url}")
    try:
        response = requests.get(sitemap_url, timeout=10) # Increased timeout for sitemap
        response.raise_for_status()
        root = ET.fromstring(response.content)

        # Namespace handling for sitemaps
        namespaces = {'sitemap': 'http://www.sitemaps.org/schemas/sitemap/0.9'}

        # Check if it's a sitemap index (contains <sitemap> tags)
        is_sitemap_index = False
        for sitemap_tag in root.findall('sitemap:sitemap', namespaces):
            is_sitemap_index = True
            loc = sitemap_tag.find('sitemap:loc', namespaces)
            if loc is not None and loc.text:
                sitemap_urls.update(get_sitemap_urls(loc.text)) # Recursively fetch sub-sitemaps

        # If it's not a sitemap index, it's a regular sitemap (contains <url> tags)
        if not is_sitemap_index:
            for url_tag in root.findall('sitemap:url', namespaces):
                loc = url_tag.find('sitemap:loc', namespaces)
                if loc is not None and loc.text:
                    clean_url = urljoin(loc.text, urlparse(loc.text).path) # Normalize URL
                    sitemap_urls.add(clean_url)

    except requests.exceptions.RequestException as e:
        print(f"خطأ في جلب أو تحليل خريطة الموقع {sitemap_url}: {e}")
    except ET.ParseError as e:
        print(f"خطأ في تحليل XML لخريطة الموقع {sitemap_url}: {e}")
    except Exception as e:
        print(f"حدث خطأ غير متوقع أثناء معالجة خريطة الموقع {sitemap_url}: {e}")
    return sitemap_urls

def find_orphan_pages(base_url):
    """Finds orphan pages by comparing internal links with sitemap links."""
    domain = urlparse(base_url).netloc
    sitemap_url = urljoin(base_url, '/sitemap.xml')

    internal_links = get_all_internal_links(base_url)
    sitemap_links = get_sitemap_urls(sitemap_url)

    # Orphan pages are in sitemap but not found by internal crawl
    orphan_pages = sitemap_links - internal_links

    print("\n--- تحليل الصفحات اليتيمة اكتمل ---")
    if orphan_pages:
        print(f"تم العثور على {len(orphan_pages)} صفحة يتيمة محتملة:")
        for page in sorted(list(orphan_pages)):
            print(page)
    else:
        print("لم يتم العثور على صفحات يتيمة. عمل رائع!")

    print(f"\nعدد الروابط الداخلية المكتشفة: {len(internal_links)}")
    print(f"عدد الروابط في خريطة الموقع: {len(sitemap_links)}")
    return orphan_pages

if __name__ == "__main__":
    # Example Usage: Replace with the actual URL of the website you want to analyze
    website_url = "https://www.example.com" # <--- Replace this with your target website!

    # A quick check to make sure the URL is valid
    parsed_url = urlparse(website_url)
    if not all([parsed_url.scheme, parsed_url.netloc]):
        print("الرجاء إدخال رابط موقع ويب صالح (مثال: https://www.example.com)")
    else:
        find_orphan_pages(website_url)

كيفية التشغيل

  1. احفظ الكود أعلاه في ملف باسم orphan_finder.py.
  2. افتح الملف باستخدام محرر نصوص وقم بتغيير قيمة المتغير website_url إلى رابط الموقع الذي تريد تحليله (مثلاً: https://www.yourwebsite.com). لا تنسَ إضافة https:// أو http://.
  3. افتح سطر الأوامر (Terminal أو Command Prompt) وانتقل إلى المجلد الذي حفظت فيه الملف.
  4. قم بتشغيل السكريبت باستخدام الأمر التالي:
    python orphan_finder.py

النتيجة النهائية المتوقعة

بعد تشغيل السكريبت، ستشاهد مخرجات في سطر الأوامر تصف عملية الزحف. في النهاية، سيقوم السكريبت بإبلاغك بعدد الصفحات اليتيمة المحتملة التي تم العثور عليها وسيسرد كل رابط من هذه الصفحات. إذا لم يتم العثور على أي صفحات يتيمة، فستظهر رسالة تفيد بذلك. ستوفر لك هذه القائمة رؤى قيمة لتحسين بنية روابط موقعك الداخلية وضمان اكتشاف جميع صفحاتك المهمة بواسطة جوجل.