استخراج بيانات صفحات الويب باستخدام Python وBeautifulSoup
قد تحتاج إلى قراءة عناوين أو حقول من صفحة HTML لا تملك واجهة API لها. توفر BeautifulSoup أدوات للبحث داخل الشجرة وتحليل العناصر، لكن استخراج البيانات ليس مجرد كتابة selector. يجب احترام شروط الموقع، وتجنب الضغط على الخادم بطلبات متكررة، وعدم جمع بيانات شخصية أو تجاوز وسائل الحماية. سنستخدم HTML محلياً في المثال حتى يكون السلوك قابلاً للتجربة، ثم نوضح كيف تتغير الخطوات عند قراءة ملف أو استجابة مصرح بها. هذا النهج يجمع بين الفائدة التقنية والمسؤولية في استخدام الأتمتة.
الفكرة الأساسية
يمثل parser صفحة HTML على شكل شجرة من العناصر. تستطيع البحث باسم الوسم أو class أو selector، ثم قراءة النص عبر get_text. لا تفترض أن كل صفحة تستخدم البنية نفسها؛ اختبر وجود العنصر قبل القراءة وضع قيمة بديلة عند غيابه. عند التعامل مع خدمة خارجية استخدم مهلة زمنية، واقرأ robots.txt وشروط الاستخدام، ولا ترسل طلبات متوازية بكثافة. إذا قدم الموقع API رسمية، فهي غالباً أوضح وأقل عرضة للكسر من تحليل HTML المرئي.
مثال برمجي عملي
from bs4 import BeautifulSoup
html = """
<ul class="articles">
<li><a href="/python">تعلم Python</a></li>
<li><a href="/databases">أساسيات قواعد البيانات</a></li>
</ul>
"""
soup = BeautifulSoup(html, "html.parser")
items = []
for link in soup.select("ul.articles a"):
title = link.get_text(" ", strip=True)
href = link.get("href", "")
if title and href:
items.append({"title": title, "url": href})
for item in items:
print(item)
شرح المثال
ينشئ المثال شجرة من نص HTML، ثم يستخدم CSS selector للعثور على الروابط داخل قائمة articles. get_text يزيل الفراغات الزائدة، بينما get يعيد href أو قيمة فارغة إذا غاب. لا يحاول الكود قراءة عناصر لا وجود لها، ولا يفترض أن الرابط يحتوي نصاً. عند استخدام URL حقيقي، يمكن تمرير response.text إلى BeautifulSoup بعد التحقق من رمز الاستجابة والترميز. احفظ النتائج في بنية واضحة قبل تصديرها إلى JSON أو CSV.
ابدأ بملف HTML صغير يمثل الحالات التي تريدها: عنصر كامل، عنصر ناقص، ونص يحتوي مسافات. اكتب parser يعيد نتيجة ثابتة، ثم أضف طلب الشبكة لاحقاً. استخدم Session وtimeout ومعدل طلب معقولاً، وخزن نسخة من الاستجابة أثناء التطوير حتى لا تطلب الصفحة في كل اختبار. راقب تغير البنية، واكتب سجلاً يوضح عدد العناصر التي عثرت عليها، لكن لا تسجل بيانات شخصية بلا حاجة.
طريقة العمل قبل كتابة الكود
قبل فتح محرر النصوص، اكتب النتيجة التي تريد الوصول إليها وحدد المدخلات والمخرجات. يساعد هذا التمرين على كشف الحالات الغامضة مبكراً، مثل قيمة ناقصة أو قائمة فارغة أو طلب لا يصل إلى الخادم. لا تحتاج إلى مخطط كبير للمثال التعليمي، لكنك تحتاج إلى أسماء واضحة وخطوات يمكن اختبارها واحدة بعد أخرى. عندما تتغير الفكرة أثناء الكتابة، عدل التصميم قبل إضافة شروط متفرعة يصعب تتبعها.
قسّم المشكلة إلى أجزاء صغيرة، واجعل كل جزء مسؤولاً عن قرار واحد قدر الإمكان. يمكن أن تكون الأجزاء دوال، أو مكونات، أو طبقات منفصلة بحسب التقنية. لا يعني التقسيم إنشاء ملفات كثيرة، بل يعني أن تعرف أين تبحث عندما يحدث الخطأ. سجّل الافتراضات المهمة بجملة قصيرة، مثل أن القائمة مرتبة أو أن السعر غير سالب، ثم تحقق منها في المكان المناسب بدلاً من الاعتماد على الذاكرة.
اختبار المثال في حالات مختلفة
لا تختبر المسار الطبيعي فقط. جرّب مدخلاً فارغاً، وقيمة أكبر من المتوقع، وعنصراً غير موجود، وطلباً يصل من دون البيانات المطلوبة. الحالات الحدية تكشف غالباً مشكلات الفهارس والأنواع وترتيب التنفيذ. اكتب النتيجة المتوقعة قبل تشغيل الكود، ثم قارنها بالنتيجة الفعلية. إذا كان الاختبار يمر بالصدفة، أضف شرطاً أو اختباراً أوضح، ولا تكتفِ بطباعة قيمة تبدو منطقية.
- تحقق من المدخلات قبل استخدامها في الحساب أو التخزين.
- أعد رسالة مفهومة ورمزاً مناسباً عند وقوع الخطأ.
- اجعل الاختبارات قابلة للإعادة من دون اعتماد على شبكة أو وقت متغير.
- راجع أثر التغيير على الأجزاء التي تستدعي الدالة أو المكون.
ملاحظات تتعلق بجودة الكود
تظهر جودة الحل في التفاصيل الصغيرة: اسم يشرح الغرض، ودالة لا تجمع مهاماً بعيدة، ورسالة خطأ لا تترك القارئ في حيرة. لا تحاول اختصار كل سطر، فالكود المقروء أفضل من تعبير قصير يحتاج إلى شرح طويل. وفي الوقت نفسه، لا تكرر القاعدة نفسها في أماكن كثيرة؛ انقلها إلى موضع واحد عندما يكون ذلك أوضح. راجع الملف بعد أن يعمل، لأن أول نسخة تركز عادةً على الوصول إلى النتيجة أكثر من قابلية الصيانة.
احتفظ بالإعدادات التي تختلف بين جهاز وآخر خارج الكود، ولا تضع كلمات مرور أو مفاتيح خاصة في المستودع. استخدم سجلات مناسبة أثناء التطوير، ثم راجع ما ينبغي حجبه في بيئة التشغيل. إذا تعامل البرنامج مع بيانات المستخدم، فافصل بين ما يحتاجه التطبيق وما يمكن الاحتفاظ به. هذه الممارسات لا تخص لغة واحدة، بل تقلل المشكلات عندما يكبر المشروع أو يعمل عليه أكثر من شخص.
متى تعرف أن الحل يحتاج إلى تطوير؟
يحتاج المثال التعليمي إلى طبقات إضافية عندما يدخل في نظام حقيقي: قاعدة بيانات، مستخدمون متعددون، مراقبة، اختبارات، وصلاحيات. لا تضف هذه الأجزاء قبل معرفة المشكلة التي تحلها، لكن لا تنقل الكود التجريبي إلى الإنتاج كما هو. راقب حجم البيانات، وعدد الطلبات، ومصدر المدخلات، وما إذا كان الفشل يجب أن يعيد العملية أو يوقفها. اكتب قرارك في مستند صغير أو تعليق يشرح السبب، حتى لا يضطر الفريق إلى تخمينه لاحقاً.
إذا وجدت أن الخطأ يتكرر في أكثر من مكان، فابحث عن قاعدة مشتركة. وإذا أصبح التعديل في ملف صغير يؤثر في ملفات كثيرة، فراجع حدود المسؤوليات. لا توجد بنية واحدة صحيحة لكل مشروع، لكن توجد أسئلة تساعدك على اختيار بنية مناسبة: من يملك البيانات؟ من يغيرها؟ ماذا يحدث عند الفشل؟ وكيف يمكن اختبار الجزء من دون تشغيل النظام كله؟
أخطاء ينبغي تجنبها
من الأخطاء استخدام regex لتحليل HTML المعقد، أو اختيار class منسق بصرياً قد يتغير مع التصميم. لا تتجاوز captcha أو تسجيل الدخول، ولا تجمع محتوى محمياً بطريقة تخالف الشروط. قد تكون الصفحة مبنية بجافاسكربت ولا تحتوي البيانات في HTML الأولي، وعندها لا يعني غيابها أن selector خاطئ. ابحث عن واجهة مصرح بها أو طريقة يتيحها الموقع، ولا تكرر الطلب حتى تظهر النتيجة.
خطوة تالية مناسبة
أضف اختباراً لعنصر مفقود، ثم حول المسارات النسبية إلى عناوين كاملة باستخدام urllib.parse عند الحاجة. إذا كبرت العملية، خزّن آخر وقت طلب واحترم التخزين المؤقت. الهدف من الأداة أن توفر بيانات مسموحاً لك باستخدامها، وأن تفشل بهدوء عندما تتغير الصفحة بدلاً من الضغط المستمر على الموقع.
احترم الموقع الذي تقرأ منه
قبل كتابة أداة استخراج، راجع شروط الاستخدام وسياسة robots.txt، وتأكد من أن جمع البيانات مسموح للغرض الذي تريده. لا ترسل طلبات كثيرة في وقت قصير، وضع فاصلاً زمنياً مناسباً وتعامل مع أخطاء الشبكة. احفظ نسخة من الصفحة أثناء التطوير بدلاً من إعادة طلب الموقع في كل تجربة. كما ينبغي أن تتوقع تغير بنية HTML، ولذلك اجعل المحددات واضحة وسجل الصفحات التي لم تجد فيها العنصر المتوقع. لا تجمع بيانات شخصية لا تحتاج إليها، ونظف النصوص قبل تخزينها. الأداة الجيدة لا تقيس نجاحها بعدد الصفحات فقط، بل بسلامة المصدر واحترامه واستقرار النتائج.
تنظيف النص المستخرج
بعد الحصول على النص، أزل المسافات الزائدة والعناصر غير المفيدة، لكن احتفظ بالمعلومة التي يحتاجها المستخدم. لا تفترض أن كل صفحة تستخدم العناوين نفسها أو أن المحتوى موجود في عنصر واحد. ضع قيمة افتراضية عندما يغيب حقل، وسجل الصفحة للمراجعة بدلاً من إيقاف العملية كلها عند أول اختلاف.
الخلاصة
أضف اختباراً لعنصر مفقود، ثم حول المسارات النسبية إلى عناوين كاملة باستخدام urllib.parse عند الحاجة. إذا كبرت العملية، خزّن آخر وقت طلب واحترم التخزين المؤقت. الهدف من الأداة أن توفر بيانات مسموحاً لك باستخدامها، وأن تفشل بهدوء عندما تتغير الصفحة بدلاً من الضغط المستمر على الموقع. يوضح هذا الموضوع كيف يتحول مفهوم نظري إلى خطوات يمكن تشغيلها وفحصها.
ابدأ بتطبيق المثال على ملف صغير، ثم غيّر مدخلاً واحداً وراقب النتيجة. بعد ذلك أضف حالة فشل واكتب اختباراً لها، ثم انقل الفكرة إلى مشروعك الحقيقي بحذر. عندما تفهم سبب كل خطوة، ستستطيع تغيير الأدوات أو اللغة من دون فقدان المفهوم. البرمجة تتحسن بالمحاولات القصيرة والمراجعة المستمرة، لا بنسخ كود طويل من دون معرفة ما الذي يحميه أو ما الذي قد يكسره.