استخدام Python لفحص الروابط المعطلة (404) في المواقع الكبيرة


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

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

لماذا نفحص الروابط المعطلة؟

الروابط المعطلة (Broken Links) أو صفحات الخطأ 404 هي مشكلة شائعة في المواقع، خاصة الكبيرة منها ذات المحتوى المتجدد. تؤثر هذه الروابط سلباً على:

  • تجربة المستخدم: تؤدي إلى إحباط الزوار وخروجهم من الموقع.
  • تصنيف SEO: تعتبر محركات البحث مثل جوجل الروابط المعطلة علامة على سوء الصيانة، مما قد يؤثر على ترتيب الموقع.
  • ميزانية الزحف (Crawl Budget): تستهلك برامج الزحف الخاصة بمحركات البحث وقتاً وموارد في محاولة الوصول لصفحات غير موجودة.

الأدوات والمكتبات المطلوبة

سنحتاج إلى تثبيت المكتبات التالية في بيئة بايثون الخاصة بك:

  • requests: لإجراء طلبات HTTP والحصول على استجابات الصفحات.
  • BeautifulSoup4: لتحليل محتوى HTML واستخراج الروابط.
  • pandas: لتنظيم البيانات وتصديرها إلى ملف CSV أو Excel.

يمكنك تثبيتها باستخدام pip:


pip install requests beautifulsoup4 pandas

الخطوة 1: جمع الروابط من صفحة البداية

سنبدأ بجمع جميع الروابط الموجودة في صفحة البداية للموقع المستهدف.


import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse

def get_all_links_from_url(url):
    try:
        response = requests.get(url, timeout=10)
        response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
    except requests.exceptions.RequestException as e:
        print(f"Error fetching {url}: {e}")
        return set()

    soup = BeautifulSoup(response.text, 'html.parser')
    links = set()
    for a_tag in soup.find_all('a', href=True):
        href = a_tag['href']
        full_url = urljoin(url, href) # Convert relative URLs to absolute
        links.add(full_url)
    return links

ملاحظة تقنية: استخدام urljoin ضروري لتحويل الروابط النسبية (مثل /about-us) إلى روابط مطلقة كاملة (مثل https://example.com/about-us) لضمان إمكانية فحصها بشكل صحيح.

الخطوة 2: الزحف للموقع وجمع كل الروابط الداخلية

سنقوم بتوسيع الوظيفة السابقة لزحف الموقع بأكمله وجمع جميع الروابط الداخلية. سنستخدم قائمة انتظار (queue) لتتبع الروابط التي لم تتم زيارتها بعد، ومجموعة (set) لتتبع الروابط التي تمت زيارتها لتجنب التكرار والزحف اللانهائي.


def crawl_site_for_internal_links(base_url):
    internal_links = set()
    visited_links = set()
    to_visit = [base_url]
    
    base_domain = urlparse(base_url).netloc

    while to_visit:
        current_url = to_visit.pop(0) # BFS approach
        if current_url in visited_links:
            continue
        
        visited_links.add(current_url)
        internal_links.add(current_url) # Add to our list of internal links to check

        print(f"Crawling: {current_url}")

        try:
            response = requests.get(current_url, timeout=10)
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            print(f"Error fetching {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(current_url, href)
            
            # Normalize URL (remove fragments like #section)
            parsed_full_url = urlparse(full_url)
            clean_full_url = parsed_full_url.scheme + "://" + parsed_full_url.netloc + parsed_full_url.path
            
            if parsed_full_url.netloc == base_domain and clean_full_url not in visited_links:
                to_visit.append(clean_full_url)
    
    return list(internal_links)

ملاحظة تقنية: لضمان كفاءة الزحف، يجب احترام ملف robots.txt الخاص بالموقع وتجنب الزحف المفرط الذي قد يؤدي إلى حظر عنوان IP الخاص بك. كما أن استخدام User-Agent محدد قد يكون مفيداً.

الخطوة 3: فحص حالة كل رابط

الآن بعد أن جمعنا قائمة بالروابط الداخلية، سنقوم بفحص حالة HTTP لكل رابط لتحديد ما إذا كان معطلاً (404) أو غير ذلك.


def check_link_status(url):
    try:
        # Use HEAD request for efficiency as we only need the status code
        response = requests.head(url, timeout=10, allow_redirects=True) 
        return response.status_code
    except requests.exceptions.Timeout:
        return "Timeout"
    except requests.exceptions.ConnectionError:
        return "Connection Error"
    except requests.exceptions.RequestException as e:
        return f"Error: {e}"

ملاحظة تقنية: استخدام requests.head() بدلاً من requests.get() أكثر كفاءة لأنه لا يقوم بتنزيل المحتوى الكامل للصفحة، مما يوفر النطاق الترددي والوقت. allow_redirects=True يضمن متابعة عمليات إعادة التوجيه (مثل 301، 302) والحصول على الحالة النهائية.

الخطوة 4: تجميع النتائج وتصديرها

سنجمع كل الوظائف السابقة في نص برمجي واحد، ونقوم بتخزين النتائج في قائمة، ثم نحولها إلى DataFrame باستخدام pandas ونصدرها إلى ملف CSV.


import pandas as pd
import time # For rate limiting

def main_broken_link_checker(start_url, output_filename="broken_links.csv"):
    print(f"Starting crawl for: {start_url}")
    all_internal_links = crawl_site_for_internal_links(start_url)
    print(f"Found {len(all_internal_links)} internal links. Checking status...")

    results = []
    for i, link in enumerate(all_internal_links):
        status_code = check_link_status(link)
        print(f"Checking {i+1}/{len(all_internal_links)}: {link} -> Status: {status_code}")
        results.append({"URL": link, "Status Code": status_code})
        time.sleep(0.1) # Small delay to avoid overwhelming the server

    df = pd.DataFrame(results)
    
    # Filter for non-2xx/3xx status codes (broken, errors, timeouts, connection issues)
    non_ok_links_df = df[~df['Status Code'].astype(str).str.startswith('2') & 
                         ~df['Status Code'].astype(str).str.startswith('3')]

    print(f"Found {len(non_ok_links_df)} non-2xx/3xx links.")
    non_ok_links_df.to_csv(output_filename, index=False, encoding='utf-8')
    print(f"Results saved to {output_filename}")

if __name__ == "__main__":
    # Replace with the target website URL
    target_website = "https://www.example.com" 
    main_broken_link_checker(target_website)

ملاحظات هامة لتطبيقات المواقع الكبيرة

لفحص المواقع الكبيرة جداً (مئات الآلاف أو الملايين من الصفحات)، يجب أخذ الاعتبارات التالية في الحسبان:

  • التحكم في معدل الطلبات (Rate Limiting): أضف تأخيرات بين الطلبات (كما في time.sleep()) لتجنب إغراق الخادم أو حظرك. قد تحتاج إلى استخدام مكتبات مثل tenacity لإعادة المحاولة.
  • الطلبات غير المتزامنة (Asynchronous Requests): استخدم مكتبات مثل httpx مع asyncio أو aiohttp لإجراء طلبات متعددة بالتوازي، مما يسرع العملية بشكل كبير دون زيادة الحمل على الخادم بشكل لحظي.
  • استخدام الوكلاء (Proxies): إذا كنت تقوم بفحص عدد كبير جداً من المواقع أو من نفس عنوان IP، فقد تحتاج إلى استخدام شبكة من الوكلاء لتوزيع الطلبات.
  • احترام robots.txt: قبل الزحف، تحقق من ملف robots.txt للموقع لتحديد الصفحات التي لا ينبغي الزحف إليها.
  • حفظ التقدم: للمواقع الكبيرة، قد تحتاج إلى حفظ التقدم بشكل دوري (مثل كل 1000 رابط تم فحصه) لتجنب فقدان البيانات في حالة توقف البرنامج.
  • معالجة الأخطاء الشاملة: تعامل مع جميع أنواع الأخطاء المحتملة (مثل انقطاع الاتصال، أخطاء DNS، أخطاء SSL) لضمان استمرارية الزحف.

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

بعد تشغيل النص البرمجي، سيقوم بايثون بالزحف إلى الموقع المستهدف وجمع جميع الروابط الداخلية. سيقوم بعد ذلك بفحص حالة HTTP لكل رابط. في النهاية، سيتم إنشاء ملف CSV باسم broken_links.csv (أو الاسم الذي تحدده) في نفس مجلد تنفيذ النص البرمجي. سيحتوي هذا الملف على قائمة بجميع الروابط التي لم تعد متاحة (مثل 404) أو التي واجهت أخطاء في الاتصال أو مهلات، بالإضافة إلى أي روابط أخرى لا تعطي رمز حالة 2xx أو 3xx، مما يسهل عليك تحديدها وإصلاحها.