برمجة Web Scraping باستخدام BeautifulSoup وSelenium

برمجة Web Scraping باستخدام BeautifulSoup وSelenium

في عالم الويب الحديث، أصبحت البيانات هي المادة الخام الأكثر قيمة تقريبًا، تمامًا كما كانت المصانع القديمة تعتمد على الحديد والخشب والوقود، تعتمد المشاريع الرقمية اليوم على البيانات الدقيقة والمحدثة. ومع ازدياد المواقع التي تعرض معلوماتها بشكل منظم أو ديناميكي، ظهرت الحاجة إلى تقنية تساعد المطور على جمع هذه البيانات وتحويلها إلى شكل قابل للتحليل أو الأتمتة أو التغذية الراجعة داخل التطبيقات. هنا يأتي Web Scraping كأحد أهم الأدوات التي يحتاجها المطور أو المحلل أو حتى صاحب المشروع الذي يريد فهم السوق أو متابعة الأسعار أو جمع الأخبار أو بناء قاعدة بيانات خاصة به. وعندما نتحدث عن بايثون تحديدًا، فهناك اسمان يلمعان دائمًا في هذا المجال: BeautifulSoup وSelenium. الأول رائع في قراءة وتحليل صفحات HTML الثابتة، والثاني يفتح الباب أمام المواقع الديناميكية التي لا تكشف محتواها إلا بعد تنفيذ JavaScript أو التفاعل مع الصفحة كما يفعل المستخدم الحقيقي.

قد يبدو Web Scraping في البداية كأنه مجرد “نسخ بيانات من صفحة ويب”، لكن الحقيقة أعمق من ذلك بكثير. فالموضوع يشبه إلى حد كبير عملية استكشاف ذكية: أنت لا تريد الصفحة نفسها، بل تريد المعنى الكامن فيها؛ تريد أن تقرأ العناوين، الأسعار، المواعيد، أسماء المنتجات، التقييمات، المقالات، أو أي معلومة أخرى تظهر على الواجهة. ثم تقوم بتنظيفها، تنظيمها، تخزينها، وربما استخدامها لاحقًا في لوحة تحكم، أو نظام تنبيهات، أو أداة مقارنة أسعار، أو نموذج تعلم آلي. هذه الرحلة من “صفحة HTML” إلى “بيانات مفهومة” هي بالضبط ما يجعل Web Scraping فنًا تقنيًا ممتعًا ومفيدًا جدًا.

ما هو Web Scraping ولماذا يحتاجه المطور؟

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

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

ومع ذلك، ليس كل موقع يسمح بالجمع الآلي للبيانات، لذلك يجب أن نذكر منذ البداية نقطة مهمة جدًا: Web Scraping مسؤولية قبل أن يكون مهارة. عليك احترام شروط الاستخدام، وعدم إرسال عدد مهول من الطلبات في وقت قصير، وعدم جمع بيانات خاصة أو محمية، وعدم تحميل الموقع فوق طاقته. العمل الجيد في هذا المجال ليس فقط أن “السكربت يعمل”، بل أن السكربت يعمل باحترام، وبطريقة مستقرة، وبسلوك قريب من السلوك الطبيعي للمستخدم، مع وعي كامل بالقوانين والأخلاقيات.

الفرق بين BeautifulSoup وSelenium

الكثير من المبتدئين يخلطون بين BeautifulSoup وSelenium، رغم أن كل واحد منهما له دور مختلف تمامًا. BeautifulSoup ليست أداة لفتح المواقع أو تنفيذ JavaScript، بل هي مكتبة لتحليل HTML وXML بعد أن تحصل على محتوى الصفحة. بمعنى آخر، هي ممتازة عندما تكون الصفحة جاهزة كنص HTML، وتريد أن تبحث داخلها عن العناصر والعناوين والروابط والجداول والبطاقات والمحتوى. الجميل فيها أنها خفيفة وسريعة وسهلة التعلم، وتجعل التعامل مع HTML أكثر راحة بكثير من معالجة النص الخام يدوياً.

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

إذا أردنا تبسيط الصورة أكثر، فـ BeautifulSoup هو “عدسة مكبرة” لفهم HTML، بينما Selenium هو “سائق متصفح” قادر على التنقل داخل الموقع. وفي كثير من المشاريع الاحترافية، يبدأ المطور بـBeautifulSoup لأنه أسرع وأبسط، ثم ينتقل إلى Selenium عندما يكتشف أن الموقع يعتمد على التحميل الديناميكي أو على خطوات تفاعلية قبل إظهار البيانات.

تجهيز بيئة العمل

قبل أن نكتب أول سطر كود، نحتاج إلى إعداد بيئة مناسبة. بايثون هو الخيار الطبيعي هنا، ومعه بعض المكتبات الأساسية. في الغالب ستحتاج إلى requests لجلب محتوى الصفحات، وbeautifulsoup4 لتحليل الـHTML، وselenium لتشغيل المتصفح، وربما pandas لتخزين البيانات، وlxml كـ parser أسرع وأكثر مرونة في بعض الحالات.

pip install requests beautifulsoup4 selenium pandas lxml

في مشاريع Selenium، ستحتاج أيضًا إلى متصفح مثبت على جهازك مثل Chrome أو Firefox، بالإضافة إلى Driver مناسب إن كنت تستخدم الأسلوب التقليدي. في كثير من البيئات الحديثة يمكن إدارة هذا الأمر بشكل أسهل عبر أدوات مثل webdriver-manager، لكن حتى لو لم تستخدمها، فإن الفكرة الأساسية واحدة: Selenium يحتاج وسيطًا يستطيع من خلاله التحكم في المتصفح.

pip install webdriver-manager

من الجميل أن تبدأ مشروعك بملف منظم، مثلًا:

scraping_project/
├── main.py
├── data/
├── logs/
└── requirements.txt

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

أول مثال عملي مع BeautifulSoup

لنبدأ بأبسط سيناريو: صفحة HTML ثابتة. الفكرة هنا أننا سنستخدم requests لجلب الصفحة، ثم BeautifulSoup لتحليلها. افترض أن لدينا صفحة تحتوي على عناوين مقالات وروابطها، ونريد استخراجها.

import requests
from bs4 import BeautifulSoup

url = "https://example.com/articles"
headers = {
    "User-Agent": "Mozilla/5.0"
}

response = requests.get(url, headers=headers)
response.raise_for_status()

soup = BeautifulSoup(response.text, "html.parser")

articles = soup.find_all("article")

for article in articles:
    title = article.find("h2").get_text(strip=True)
    link = article.find("a")["href"]
    print(title, link)

في هذا المثال البسيط تظهر قوة BeautifulSoup بوضوح. نحن لم نحتج إلى متصفح كامل ولا إلى خطوات تفاعلية، فقط طلب HTTP عادي ثم تحليل النص. في كثير من المشاريع، هذا النوع من الأسلوب يكفي تمامًا، بل هو الأفضل لأنه أخف وأسرع. وأكثر شيء يهم هنا هو فهم بنية HTML. فـBeautifulSoup لا “تفهم” الموقع كإنسان، وإنما تبحث في شجرة العناصر DOM: tags, attributes, children, siblings. لذلك كلما فهمت بنية الصفحة أكثر، أصبحت عملية الاستخراج أسهل.

لنأخذ مثالًا آخر. إذا أردت استخراج كل الروابط من صفحة ما:

links = soup.find_all("a")

for link in links:
    href = link.get("href")
    text = link.get_text(strip=True)
    if href:
        print(text, href)

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

أساسيات البحث داخل HTML: find و find_all و select

أحد الأسرار الصغيرة التي تجعل BeautifulSoup ممتعة هو أنك تستطيع البحث داخل الصفحة بعدة طرق. find تعطيك أول عنصر مطابق، بينما find_all تعطيك جميع العناصر المطابقة. أما select وselect_one فتعتمد على CSS selectors، وهي جميلة جدًا لمن يحب التفكير بأسلوب الواجهات الأمامية.

title = soup.find("h1")
items = soup.find_all("div", class_="card")
first_card = soup.select_one(".card")
all_prices = soup.select(".price")

ميزة select أنها تسمح لك بالتعبير عن الفكرة بطريقة قريبة من CSS:

products = soup.select("div.product-list > div.product-card span.price")

هذه القوة تجعل BeautifulSoup مناسبة جدًا عندما تكون الصفحة منظمة جيدًا. ومع ذلك، لا توجد صيغة سحرية واحدة تناسب كل المواقع. أحيانًا تحتاج إلى البحث حسب id، وأحيانًا حسب class، وأحيانًا حسب ترتيب العنصر داخل الصفحة، وأحيانًا حسب وجود سمة معينة مثل data-*. لذلك يجب أن تكون مرنًا، وأن تنظر إلى HTML نفسه قبل كتابة الكود. كثير من أخطاء المبتدئين لا تكون في بايثون، بل في سوء قراءة بنية الصفحة.

مثلاً:

for product in soup.select("div.product-card"):
    name = product.select_one("h3.name")
    price = product.select_one("span.price")
    image = product.select_one("img")

    print({
        "name": name.get_text(strip=True) if name else None,
        "price": price.get_text(strip=True) if price else None,
        "image": image.get("src") if image else None
    })

لاحظ هنا أننا لا نفترض أن كل عنصر موجود. هذا مهم جدًا، لأن المواقع تتغير باستمرار، وقد يكون هناك منتج بدون صورة، أو عنصر لم يُحمّل بالكامل، أو بطاقة تحتوي حقلاً ناقصًا. التعامل الآمن مع None ليس مجرد أسلوب جيد، بل هو ضرورة حقيقية في Web Scraping.

متى نحتاج Selenium؟

إذا كانت الصفحة تظهر البيانات مباشرة داخل HTML، فغالبًا BeautifulSoup تكفي. لكن إذا كانت الصفحة تستخدم JavaScript لجلب البيانات بعد التحميل، أو إذا كان المحتوى لا يظهر إلا بعد النقر أو التمرير أو تسجيل الدخول، فهنا يظهر دور Selenium.

على سبيل المثال، كثير من المواقع الحديثة لا تضع البيانات النهائية في الصفحة الأولى. قد تجد الصفحة فارغة تقريبًا ثم يظهر المحتوى بعد ثوانٍ، أو بعد تنفيذ API call داخلي عبر JavaScript. في هذه الحالة، requests قد تجلب لك HTML فارغًا أو ناقصًا، بينما Selenium يفتح الصفحة داخل متصفح حقيقي، فينفذ JavaScript ويمنحك النتيجة النهائية التي يراها المستخدم.

Selenium مفيد أيضًا إذا كنت تحتاج إلى:

  • النقر على زر “تحميل المزيد”.

  • تمرير الصفحة للأسفل لتحميل محتوى جديد.

  • ملء نموذج بحث.

  • تسجيل الدخول باستخدام اسم مستخدم وكلمة مرور.

  • استخراج بيانات تظهر في dropdown أو modal أو tabs.

  • التعامل مع صفحات تعتمد على التفاعل قبل إظهار النتائج.

مثال أول مع Selenium

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

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

options = Options()
options.add_argument("--headless")
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")

driver = webdriver.Chrome(
    service=Service(ChromeDriverManager().install()),
    options=options
)

driver.get("https://example.com")
print(driver.title)

driver.quit()

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

استخراج العناصر من صفحة باستخدام Selenium

بعد فتح الصفحة، يمكنك استخدام Selenium للعثور على العناصر وقراءة النصوص والروابط.

from selenium.webdriver.common.by import By

driver.get("https://example.com/products")

cards = driver.find_elements(By.CSS_SELECTOR, ".product-card")

for card in cards:
    name = card.find_element(By.CSS_SELECTOR, ".name").text
    price = card.find_element(By.CSS_SELECTOR, ".price").text
    print(name, price)

ورغم أن هذا يبدو بسيطًا، إلا أن هناك فارقًا مهمًا بين Selenium وBeautifulSoup: Selenium يعمل مع العناصر كما هي داخل المتصفح الحي، لذلك قد تحتاج إلى الانتظار حتى يكتمل التحميل. وهنا نصل إلى واحدة من أهم النقاط في Scraping الديناميكي: الانتظار الذكي.

أهمية الانتظار Waits

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

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

driver.get("https://example.com/products")

wait = WebDriverWait(driver, 10)
cards = wait.until(
    EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".product-card"))
)

for card in cards:
    print(card.text)

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

يمكنك أيضًا انتظار عنصر قابل للنقر:

button = wait.until(
    EC.element_to_be_clickable((By.ID, "load-more"))
)
button.click()

أو انتظار اختفاء مؤشر تحميل:

wait.until(
    EC.invisibility_of_element_located((By.CLASS_NAME, "spinner"))
)

الدمج بين Selenium وBeautifulSoup

من أكثر الأساليب العملية شيوعًا أن تستخدم Selenium لجلب الصفحة بعد تنفيذ JavaScript، ثم تمرر الـHTML الناتج إلى BeautifulSoup لتحليل أسهل. هذا المزيج رائع لأنه يجمع بين قوة المتصفح وسهولة التحليل.

from selenium import webdriver
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup

driver = webdriver.Chrome()
driver.get("https://example.com/products")

html = driver.page_source
soup = BeautifulSoup(html, "html.parser")

for item in soup.select(".product-card"):
    name = item.select_one(".name").get_text(strip=True)
    price = item.select_one(".price").get_text(strip=True)
    print(name, price)

driver.quit()

في هذا المثال، Selenium يقوم بالمهمة الثقيلة: تحميل الصفحة وتنفيذ JavaScript. ثم BeautifulSoup يتولى المهمة اللطيفة: قراءة الـHTML المستقر واستخراج البيانات. هذا الأسلوب مفيد جدًا عندما يكون HTML النهائي كبيرًا ومعقدًا، لأن BeautifulSoup أكثر راحة في التنقل بين العناصر مقارنةً بالبحث داخل Selenium في كل خطوة.

التعامل مع الصفحات التي تحتوي على Pagination

كثير من المواقع لا تعرض كل البيانات في صفحة واحدة، بل توزعها على عدة صفحات. هنا تحتاج إلى التنقل بين الصفحات خطوة خطوة. أحيانًا يكون التنقل عبر روابط page=2 أو offset، وأحيانًا عبر زر “Next”. والطريقة تختلف حسب بنية الموقع.

مثال باستخدام BeautifulSoup مع صفحات متسلسلة:

import requests
from bs4 import BeautifulSoup

base_url = "https://example.com/articles?page={}"

all_articles = []

for page in range(1, 6):
    url = base_url.format(page)
    response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
    soup = BeautifulSoup(response.text, "html.parser")

    for article in soup.select(".article-card"):
        title = article.select_one("h2").get_text(strip=True)
        link = article.select_one("a")["href"]
        all_articles.append({
            "title": title,
            "link": link
        })

print(len(all_articles))

أما إذا كان هناك زر “Next” يمكن النقر عليه باستخدام Selenium:

from selenium.webdriver.common.by import By

while True:
    cards = driver.find_elements(By.CSS_SELECTOR, ".product-card")
    for card in cards:
        print(card.text)

    try:
        next_button = driver.find_element(By.CSS_SELECTOR, "a.next")
        if "disabled" in next_button.get_attribute("class"):
            break
        next_button.click()
    except:
        break

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

جمع البيانات من الصفحات الديناميكية التي تعتمد على التمرير

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

import time
from selenium import webdriver

driver = webdriver.Chrome()
driver.get("https://example.com/feed")

last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(2)

    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height

print("Finished scrolling")
driver.quit()

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

تخزين النتائج في CSV وJSON

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

مثال لحفظ البيانات في CSV باستخدام pandas:

import pandas as pd

data = [
    {"name": "Product A", "price": "$10", "link": "https://example.com/a"},
    {"name": "Product B", "price": "$20", "link": "https://example.com/b"},
]

df = pd.DataFrame(data)
df.to_csv("data/products.csv", index=False, encoding="utf-8-sig")

أما JSON:

import json

with open("data/products.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

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

بناء سكربت احترافي منظم

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

يمكنك مثلًا تقسيمه بهذه الطريقة:

import requests
from bs4 import BeautifulSoup
import pandas as pd

def fetch_page(url):
    headers = {"User-Agent": "Mozilla/5.0"}
    response = requests.get(url, headers=headers, timeout=20)
    response.raise_for_status()
    return response.text

def parse_products(html):
    soup = BeautifulSoup(html, "html.parser")
    products = []

    for card in soup.select(".product-card"):
        name_tag = card.select_one(".name")
        price_tag = card.select_one(".price")
        link_tag = card.select_one("a")

        products.append({
            "name": name_tag.get_text(strip=True) if name_tag else "",
            "price": price_tag.get_text(strip=True) if price_tag else "",
            "link": link_tag.get("href") if link_tag else ""
        })

    return products

def save_to_csv(data, filename="products.csv"):
    df = pd.DataFrame(data)
    df.to_csv(filename, index=False, encoding="utf-8-sig")

def main():
    url = "https://example.com/products"
    html = fetch_page(url)
    products = parse_products(html)
    save_to_csv(products)

if __name__ == "__main__":
    main()

هذا الشكل البسيط يجعل المشروع أكثر وضوحًا بكثير. وإذا أردت لاحقًا دعم Selenium بدل requests، أو تغيير صيغة الحفظ، أو إضافة تسجيل Logs، فلن تضطر إلى هدم المشروع وإعادة بنائه من الصفر. وستشعر أن الكود “نظيف” لأن كل جزء يعرف مهمته جيدًا.

تنظيف البيانات بعد استخراجها

البيانات التي تستخرجها من الويب ليست دائمًا جاهزة كما هي. كثيرًا ما تحتاج إلى تنظيفها: إزالة المسافات، تحويل العملة، توحيد التواريخ، حذف الرموز الزائدة، أو تحويل النص إلى أرقام. وهذا جزء مهم جدًا من العمل، لأن “الاستخراج” وحده لا يكفي. القيمة الحقيقية تأتي من القدرة على تحويل النص الخام إلى بيانات قابلة للاستخدام.

مثال:

def clean_price(price_text):
    if not price_text:
        return None
    cleaned = price_text.replace("$", "").replace(",", "").strip()
    try:
        return float(cleaned)
    except ValueError:
        return None

أو تنظيف النصوص:

def clean_text(text):
    if not text:
        return ""
    return " ".join(text.split())

ومن الجميل أن تجعل التنظيف جزءًا مستقلًا من مشروعك. فغالبًا ستكتشف أن كل موقع له “شخصية” مختلفة: موقع يضع السعر مع العملة في نفس السطر، وآخر يضعه في data-price، وثالث يستخدم الفواصل بطريقة غير متوقعة. لذلك المرونة هنا ليست ترفًا، بل ضرورة.

التعامل مع الأخطاء والحماية من الانهيار

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

try:
    html = fetch_page(url)
    products = parse_products(html)
except requests.exceptions.RequestException as e:
    print("Network error:", e)
except Exception as e:
    print("Unexpected error:", e)

وفي Selenium، قد تحتاج إلى التقاط أخطاء مثل NoSuchElementException أو TimeoutException. وهذا لا يعني أن عليك إخفاء الأخطاء، بل يعني أن عليك التعامل معها بوعي.

from selenium.common.exceptions import NoSuchElementException, TimeoutException

try:
    button = driver.find_element(By.CSS_SELECTOR, ".load-more")
    button.click()
except NoSuchElementException:
    print("Button not found")
except TimeoutException:
    print("Page took too long to load")

البرمجة الجيدة في scraping تشبه القيادة في طريق طويل: لا تكفي السرعة، بل تحتاج إلى انتباه وتوقع للمطبات.

كيف تتعامل مع المواقع التي تحاول منعك؟

كثير من المواقع تستخدم تقنيات لمنع scraping المكثف أو غير المسؤول. قد ترى CAPTCHA، أو تحديدًا لمعدل الطلبات، أو تغيّرًا متكررًا في بنية الصفحة، أو بيانات لا تظهر إلا بعد تسجيل الدخول. بدلًا من التفكير في “كسر” الموقع، فكر في الحلول الشرعية والمستقرة: هل يوجد API رسمي؟ هل الصفحة تسمح بالاستخدام؟ هل يمكن تقليل معدل الطلبات؟ هل يمكن تحسين الكود ليكون أقل عبئًا على الموقع؟

من أفضل الممارسات:

  • استخدام User-Agent واضح ومعقول.

  • تقليل عدد الطلبات.

  • إضافة فواصل زمنية بين الطلبات.

  • تجنب التكرار غير الضروري.

  • حفظ النتائج محليًا بدل إعادة جلبها كل مرة.

  • احترام robots.txt وشروط الاستخدام.

import time
import random

for url in urls:
    html = fetch_page(url)
    data = parse_page(html)
    time.sleep(random.uniform(1.5, 3.5))

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

مثال تطبيقي كامل: استخراج منتجات من متجر وهمي

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

import requests
from bs4 import BeautifulSoup
import pandas as pd

BASE_URL = "https://example.com/products"

def get_html(url):
    headers = {
        "User-Agent": "Mozilla/5.0"
    }
    response = requests.get(url, headers=headers, timeout=20)
    response.raise_for_status()
    return response.text

def extract_products(html):
    soup = BeautifulSoup(html, "html.parser")
    results = []

    for card in soup.select(".product-card"):
        name_el = card.select_one(".product-title")
        price_el = card.select_one(".product-price")
        rating_el = card.select_one(".product-rating")
        link_el = card.select_one("a")

        results.append({
            "name": name_el.get_text(strip=True) if name_el else None,
            "price": price_el.get_text(strip=True) if price_el else None,
            "rating": rating_el.get_text(strip=True) if rating_el else None,
            "url": link_el.get("href") if link_el else None
        })

    return results

def main():
    html = get_html(BASE_URL)
    products = extract_products(html)

    df = pd.DataFrame(products)
    df.to_csv("products.csv", index=False, encoding="utf-8-sig")

    print(f"Extracted {len(products)} products")

if __name__ == "__main__":
    main()

وإذا كانت الصفحة ديناميكية وتحتاج Selenium، يمكن تعديل الأسلوب:

from selenium import webdriver
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
import pandas as pd

def get_html_with_selenium(url):
    driver = webdriver.Chrome()
    driver.get(url)
    html = driver.page_source
    driver.quit()
    return html

def main():
    html = get_html_with_selenium("https://example.com/products")
    soup = BeautifulSoup(html, "html.parser")

    products = []
    for card in soup.select(".product-card"):
        products.append({
            "name": card.select_one(".product-title").get_text(strip=True) if card.select_one(".product-title") else None,
            "price": card.select_one(".product-price").get_text(strip=True) if card.select_one(".product-price") else None
        })

    pd.DataFrame(products).to_csv("products.csv", index=False, encoding="utf-8-sig")
    print("Done")

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

نصائح عملية لتصبح أفضل في Web Scraping

الخبرة في scraping لا تأتي من حفظ الدوال فقط، بل من كثرة التعامل مع صفحات متنوعة. لذلك احرص على أن تتعلم قراءة HTML جيدًا، وأن تستخدم أدوات المطور في المتصفح، وأن تتفحص DOM قبل كتابة السطر الأول. لا تبدأ بالكود مباشرة، بل افهم الشكل أولًا. انظر إلى العنصر الذي تريد استخراجه، ما اسمه، ما صفاته، هل هو داخل div، هل فيه span، هل يوجد data-*، هل الرابط نسبي أم مطلق. كل هذه التفاصيل الصغيرة تصنع فارقًا كبيرًا.

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

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

الجانب الأخلاقي والقانوني

لا يمكن الحديث عن Web Scraping بشكل ناضج دون التطرق إلى الجانب الأخلاقي. ليس كل ما تستطيع فعله يجب أن تفعله. بعض المواقع تضع شروطًا تمنع الجمع الآلي، وبعضها يحدد طريقة استخدام البيانات، وبعضها يوفر API رسميًا أسهل وأفضل بكثير من scraping المباشر. لذلك قبل أن تبدأ مشروعًا حقيقيًا، اسأل نفسك: هل هذا الاستخدام مسموح؟ هل يوجد API؟ هل أحمّل الموقع أكثر من اللازم؟ هل البيانات شخصية أو حساسة؟ هل ما أفعله يحترم الخصوصية والملكية؟

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

خاتمة

برمجة Web Scraping باستخدام BeautifulSoup وSelenium ليست مجرد مهارة تقنية جانبية، بل هي أداة قوية تساعدك على تحويل الويب من واجهة عرض إلى مصدر بيانات حيّ. BeautifulSoup يمنحك البساطة والسرعة في تحليل HTML، بينما Selenium يفتح لك الباب أمام الصفحات الديناميكية والتفاعلية. ومع الجمع بينهما، يمكنك بناء سكربتات مرنة، عملية، وقابلة للتوسع، سواء كنت تريد جمع أخبار، أسعار، وظائف، منتجات، أو أي نوع آخر من البيانات.

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

#Meta Keywords: Web Scraping #BeautifulSoup #Selenium #Python Scraping #استخراج البيانات من المواقع #برمجة scraping #Python web automation #requests #parsing HTML #scraping examples #pandas #CSV #JSON #dynamic websites #browser automation

اشترك في نشرتنا البريدية

12k+

المشتركون

أسبوعيًا

التكرار

مجاني

دائمًا