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

دقائق القراءة: 6

مقدمة

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

لهذا السبب يعتمد المتخصصون في السيو التقني على Python لبناء أدوات مرنة وقابلة للتوسع. هذا النهج ينسجم مع ما شرحناه سابقاً في مدخل إلى عالم أتمتة الـ SEO: لماذا الآن؟ لأن الأتمتة هنا لا توفر الوقت فقط، بل تمنحك دقة أعلى وتحكماً كاملاً في منطق الفحص، التصفية، والتقارير.

في هذا المقال سنبني تصوراً عملياً لسكربت احترافي يزحف داخل الموقع، يستخرج الروابط من الصفحات، يختبر حالتها، ويفصل بين الأخطاء الحقيقية والمؤقتة. كما سنناقش تحسين الأداء للمواقع الكبيرة، وربط النتائج لاحقاً مع التقارير أو الجداول.

لماذا تمثل الروابط المعطلة مشكلة حقيقية في المواقع الكبيرة؟

الرابط المعطل ليس مجرد صفحة مفقودة. في مواقع المحتوى، المتاجر، أو الأدلة الضخمة، قد يعني وجود عدد كبير من أخطاء 404 أن هناك مشكلة في النشر، إعادة الهيكلة، أو الهجرة بين الروابط.

أثرها على السيو وتجربة المستخدم

  • إضعاف تجربة التصفح عندما يصل الزائر إلى صفحة غير موجودة.
  • تضييع قيمة الروابط الداخلية بين الصفحات المهمة.
  • إرباك فرق التحرير عند تحديث المحتوى القديم.
  • هدر وقت فرق السيو في التدقيق اليدوي بدلاً من المعالجة المنهجية.

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

الفكرة المعمارية للسكربت

المنهج الأكثر فعالية هو التعامل مع المهمة كخط أنابيب واضح: نجلب صفحة، نستخرج الروابط، نطبعها إلى صيغة موحدة، ثم نتحقق من حالتها. هذا قريب من مفهوم منطق البرمجة المعتمد على المهام (Task-Oriented Programming) حيث نقسم العمل إلى وحدات صغيرة يسهل اختبارها وتطويرها.

المكونات الأساسية

  1. قائمة انتظار للصفحات التي يجب زحفها.
  2. مجموعة visited لمنع التكرار.
  3. مكتبة Requests أو بديل مشابه لإرسال الطلبات.
  4. مكتبة BeautifulSoup لاستخراج الروابط من عناصر a.
  5. آلية تطبيع للروابط باستخدام urljoin وurlparse.
  6. تخزين النتائج في CSV أو JSON، وهنا يفيد الرجوع إلى أساسيات التعامل مع ملفات JSON (لغة التفاهم بين الأنظمة).

التحضير العملي قبل تشغيل الفحص

إذا لم تكن بيئة العمل جاهزة، فالأفضل مراجعة تهيئة بيئة العمل: تثبيت Python والمكتبات الأساسية. ستحتاج عادة إلى تثبيت المكتبات التالية:

  • requests
  • beautifulsoup4

ومن الأفضل أيضاً ضبط User-Agent واضح، وتحديد مهلة timeout مناسبة حتى لا يتوقف السكربت عند صفحة بطيئة.

import csv
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from collections import deque

START_URL = "https://example.com"
DOMAIN = urlparse(START_URL).netloc

headers = {
    "User-Agent": "Mozilla/5.0 (compatible; SEO-Broken-Link-Audit/1.0)"
}

visited_pages = set()
checked_links = set()
queue = deque([START_URL])
broken_links = []

def is_internal(url):
    return urlparse(url).netloc == DOMAIN or urlparse(url).netloc == ""

def normalize_url(base_url, link):
    absolute = urljoin(base_url, link)
    parsed = urlparse(absolute)
    clean_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
    return clean_url.rstrip("/")

def get_links_from_page(page_url):
    try:
        response = requests.get(page_url, headers=headers, timeout=10)
        response.raise_for_status()
        soup = BeautifulSoup(response.text, "html.parser")
        return [a.get("href") for a in soup.find_all("a", href=True)]
    except requests.RequestException:
        return []

def check_link_status(url):
    try:
        response = requests.get(url, headers=headers, timeout=10, allow_redirects=True)
        return response.status_code
    except requests.RequestException:
        return "REQUEST_FAILED"

while queue:
    current_page = queue.popleft()

    if current_page in visited_pages:
        continue

    visited_pages.add(current_page)
    links = get_links_from_page(current_page)

    for raw_link in links:
        normalized = normalize_url(current_page, raw_link)

        if normalized in checked_links:
            continue

        checked_links.add(normalized)
        status = check_link_status(normalized)

        if status == 404:
            broken_links.append({
                "source_page": current_page,
                "broken_url": normalized,
                "status_code": status
            })

        if is_internal(normalized) and normalized not in visited_pages:
            queue.append(normalized)

with open("broken_links_report.csv", "w", newline="", encoding="utf-8-sig") as f:
    writer = csv.DictWriter(f, fieldnames=["source_page", "broken_url", "status_code"])
    writer.writeheader()
    writer.writerows(broken_links)

print(f"Done. Found {len(broken_links)} broken links.")

كيف يعمل السكربت فعلياً؟

1) الزحف الداخلي المنضبط

استخدمنا بنية deque لإنشاء طابور صفحات. كل صفحة يتم فحصها مرة واحدة فقط، وهذا يحمي الأداء عند التعامل مع مواقع ذات بنية ربط دائرية.

2) تطبيع الروابط

بدون تطبيع، قد تعتبر الأداة أن الرابط نفسه مختلف بسبب الشرطة المائلة الأخيرة أو المعاملات مثل utm_source. لذلك قمنا بإزالة الاستعلامات والتركيز على العنوان الأساسي.

3) فصل الصفحات عن الروابط

هناك فرق مهم بين الصفحة التي نزحفها والرابط الذي نختبره. أحياناً تكون صفحة المصدر سليمة لكن تحتوي على رابط قديم معطل. لهذا نحتفظ بحقل source_page داخل التقرير.

تحسينات ضرورية للمواقع الكبيرة جداً

النسخة السابقة مناسبة كبداية، لكن المواقع الكبيرة تحتاج تحسينات إضافية حتى يكون الفحص عملياً في بيئة إنتاجية.

استخدم طلبات متوازية بحذر

بدلاً من الفحص التسلسلي، يمكن استخدام ThreadPoolExecutor لتسريع اختبار الروابط. لكن يجب احترام الخادم، وتحديد عدد معقول من العمال لتجنب الضغط المفرط أو الحظر.

تجاهل الروابط غير المفيدة

  • روابط mailto:
  • روابط tel:
  • الروابط التي تبدأ بـ #
  • الملفات الثابتة إذا لم تكن ضمن نطاق التدقيق المطلوب.

لا تعتمد على 404 فقط

في كثير من الحالات، ستجد مشكلات مهمة أيضاً في أكواد 410 و500 و503. لذلك الأفضل بناء تقرير كامل للحالات بدلاً من حصره في الخطأ الأشهر فقط.

للاستخدام المهني، شغّل الفحص أولاً على عينة من الأقسام المهمة في الموقع، ثم وسّع النطاق تدريجياً. هذا يقلل الضوضاء، ويساعدك على اختبار المنطق قبل استهلاك وقت الخادم والموارد.

ربط النتائج مع التقارير والأنظمة الأخرى

بعد استخراج التقرير، يمكنك نقله إلى Google Sheets أو نظام تنبيهات داخلي. إذا كنت تعمل ضمن بيئة تعتمد على الجداول، فستجد ربط النتائج قريباً من الأفكار التي تناولناها في كيفية ربط Google Sheets بالعالم الخارجي عبر Script، كما أن المبدأ الأساسي لحالة الروابط تمهّد له مقالة أول مشروع: كود بسيط لجلب حالة أي رابط (Status Code) إلى Sheet.

أما إذا كنت ستطوّر الأداة لتتعامل مع خدمات خارجية أو لوحات مراقبة، ففهم مفهوم الـ API: كيف نطلب البيانات من Google وOpenAI سيكون مفيداً لتوسيع الأداة من سكربت محلي إلى خدمة دورية قابلة للتكامل.

اعتبارات الأمان والجودة

عند تشغيل السكربت في بيئات إنتاجية أو سيرفرات مجدولة، لا تضع مفاتيح الوصول أو بيانات الاعتماد مباشرة داخل الكود. هذا مهم خصوصاً إذا أضفت لاحقاً تكاملاً مع أدوات خارجية أو لوحات بيانات، وهنا يفضّل مراجعة الحماية والأمان: كيف تخفي مفاتيحك السرية في الكود؟.

ومن منظور جودة المحتوى والتحسين المستمر، لا تجعل تقرير الروابط المعطلة هدفاً منفصلاً. الأفضل ربطه بدورة صيانة المحتوى، تحديث الروابط الداخلية، وتحسين الصفحات التي تستقبل زيارات عضوية عالية حتى تحقق الأداة أثراً حقيقياً على الأداء.

خاتمة

استخدام Python لفحص الروابط المعطلة في المواقع الكبيرة يمنحك أكثر من مجرد قائمة أخطاء. أنت تبني نظام تدقيق قابل للتخصيص، يربط بين الزحف، التحقق، والتقرير النهائي بطريقة تناسب بنية موقعك وأهداف فريق السيو.

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

3 comments

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *