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

لماذا يُستخدم Selenium في استخراج البيانات؟
أدوات الاستخراج التقليدية تناسب الصفحات الثابتة، لكنها قد تفشل مع المواقع التي تُحمّل البيانات بعد فتح الصفحة أو بعد تنفيذ تفاعل معين مثل الضغط على زر أو إدخال تاريخ. هنا يظهر دور Selenium، لأنه يتحكم في المتصفح كما يفعل المستخدم تماماً.
- يتعامل مع المحتوى الديناميكي الذي يعتمد على
JavaScript. - يسمح بمحاكاة النقر والكتابة واختيار القيم من النماذج.
- يساعد على الوصول إلى الجداول أو النتائج التي لا تظهر إلا بعد تنفيذ إجراء معين.
- يُعد مناسباً للمواقع المالية، ولوحات البيانات، وصفحات البحث التفاعلية.
فكرة المشروع العملي
الهدف هنا هو استخراج البيانات التاريخية لسعر صرف الدولار مقابل عملة أو عدة عملات من موقع مالي يعرض جدولاً زمنياً مع إمكانية تحديد فترة مخصصة. بدلاً من الاعتماد على واجهة برمجية جاهزة API، سنستخدم بوتاً يتفاعل مع الصفحة، يحدد تاريخ البداية والنهاية، ثم يجلب الجدول المطلوب.
هذه الفكرة مفيدة تعليمياً لأنها تمنحك نموذجاً عاماً يمكن إعادة استخدامه مع صفحات أخرى مشابهة، مثل صفحات الأسهم والسلع والمؤشرات.
فهم الصفحة المستهدفة قبل البرمجة
قبل كتابة أي سطر برمجي، من المهم فهم بنية الصفحة التي ستتعامل معها. الصفحة المستهدفة تعرض:
- جدولاً يحتوي على البيانات التاريخية.
- أداة لاختيار النطاق الزمني.
- رابطاً يمكن تعديل جزء منه لتغيير رمز العملة.
على سبيل المثال، إذا كان الرابط يحتوي على الرمز eur، فيمكن استبداله برمز عملة أخرى للحصول على بياناتها. وبالمثل، يمكن تعديل الجزء الخاص بـ usd إذا كنت تريد زوج عملات مختلفاً.
المكتبات المطلوبة في مشروع Python
سنبدأ باستيراد الأدوات الأساسية اللازمة لتشغيل المتصفح، الانتظار حتى تصبح العناصر قابلة للنقر، إضافة فواصل زمنية قصيرة، وقراءة الجداول عبر Pandas.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from time import sleep
import pandas as pd
كل مكتبة هنا لها دور واضح:
webdriverلتشغيل المتصفح والتحكم به.Optionsلضبط إعدادات المتصفح مثل وضعheadless.WebDriverWaitوECللانتظار الذكي حتى تصبح العناصر جاهزة.Byلتحديد طريقة الوصول إلى العناصر.sleepلإضافة تأخير عند الحاجة.pandasلتحويل الجداول إلىDataFrameومعالجتها.
إنشاء دالة استخراج مرنة
تحديد مدخلات الدالة
من الأفضل تصميم دالة تقبل أكثر من عملة، إضافة إلى تاريخ البداية والنهاية، مع خيار تصدير النتائج إلى ملف .csv. بهذه الطريقة تصبح الدالة قابلة لإعادة الاستخدام في مشاريع مختلفة.
def get_currencies(currencies, start, end, export_csv=False):
frames = []
المعاملات السابقة تؤدي الأدوار التالية:
currencies: قائمة برموز العملات المطلوب جمع بياناتها.start: تاريخ بداية الفترة الزمنية.end: تاريخ نهاية الفترة الزمنية.export_csv: متغير منطقي لتحديد ما إذا كنت تريد تصدير النتائج.
المرور على قائمة العملات
بما أن الدالة مصممة للتعامل مع عدة عملات، فسنستخدم حلقة تمر على كل عنصر في القائمة، وتُنشئ رابط الصفحة المناسب لكل عملة، ثم تفتح الصفحة عبر المتصفح.
for currency in currencies:
my_url = f'https://br.investing.com/currencies/usd-{currency.lower()}-historical-data'
option = Options()
option.headless = False
driver = webdriver.Chrome(options=option)
driver.get(my_url)
driver.maximize_window()
إذا كانت قيمة option.headless هي False، فستشاهد المتصفح أثناء العمل. أما إذا جعلتها True، فسيعمل البوت في الخلفية دون إظهار نافذة المتصفح.
التفاعل مع الصفحة لاختيار النطاق الزمني
بعد فتح الصفحة، لن تكون كل البيانات التاريخية ظاهرة افتراضياً. غالباً سترى نطاقاً زمنياً محدوداً فقط. لذلك نحتاج إلى التفاعل مع واجهة اختيار التاريخ لعرض الفترة التي نريدها.
النقر على زر التاريخ
أول خطوة هي الانتظار حتى يصبح زر اختيار التاريخ قابلاً للنقر، ثم تنفيذ عملية الضغط عليه.
date_button = WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.XPATH, "/html/body/div[5]/section/div[8]/div[3]/div/div[2]/span"))
)
date_button.click()
الاعتماد على WebDriverWait هنا يحمي الكود من الأعطال الناتجة عن محاولة التفاعل مع عنصر لم يكتمل تحميله بعد.
إدخال تاريخ البداية
بعد فتح نافذة التاريخ، نحدد حقل البداية، ثم نمسح القيمة الافتراضية باستخدام clear()، وبعدها نُدخل التاريخ المطلوب عبر send_keys().
start_bar = WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[1]"))
)
start_bar.clear()
start_bar.send_keys(start)
إدخال تاريخ النهاية
الخطوة التالية مماثلة تماماً، ولكن لحقل تاريخ النهاية.
end_bar = WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[2]"))
)
end_bar.clear()
end_bar.send_keys(end)
تطبيق التغييرات وانتظار تحميل البيانات
بعد إدخال التاريخين، نضغط زر Apply ثم نمنح الصفحة بضع ثوانٍ لإعادة تحميل الجدول.
apply_button = WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[5]/a"))
)
apply_button.click()
sleep(5)
استخدام sleep(5) هنا ليس الخيار الأكثر ذكاءً دائماً، لكنه عملي في بعض الحالات السريعة. في المشاريع الأكبر، يفضل استبداله بانتظار موجه يعتمد على ظهور عنصر جديد أو تحديث الجدول.
قراءة الجدول من الصفحة باستخدام Pandas
بعد أن يتم تحديث الصفحة وظهور البيانات المطلوبة، يمكن استخدام الدالة pandas.read_html() لقراءة جميع الجداول الموجودة في مصدر الصفحة.
dataframes = pd.read_html(driver.page_source)
driver.quit()
print(f'{currency} scraped.')
هذه الخطوة تنتج قائمة من الجداول في هيئة كائنات DataFrame. ولأن الصفحة قد تحتوي على أكثر من جدول، نحتاج لاحقاً إلى تحديد الجدول الصحيح فقط.
معالجة الأخطاء في Selenium بطريقة عملية
من المعروف أن أدوات الأتمتة قد تواجه مشاكل مؤقتة، مثل بطء التحميل أو فشل عنصر ما في الظهور في الوقت المناسب. لذلك من الأفضل وضع عملية الاستخراج داخل كتلة try مع إعادة المحاولة تلقائياً عند الفشل.
الفكرة هنا بسيطة:
- تشغيل المحاولة داخل حلقة
while True. - إذا نجحت العملية، يتم كسر الحلقة باستخدام
break. - إذا وقع خطأ، يتم إغلاق المتصفح والانتظار ثم إعادة المحاولة.
for currency in currencies:
while True:
try:
# Opening the connection and grabbing the page
my_url = f'https://br.investing.com/currencies/usd-{currency.lower()}-historical-data'
option = Options()
option.headless = False
driver = webdriver.Chrome(options=option)
driver.get(my_url)
driver.maximize_window()
# Clicking on the date button
date_button = WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.XPATH, "/html/body/div[5]/section/div[8]/div[3]/div/div[2]/span"))
)
date_button.click()
# Sending the start date
start_bar = WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[1]"))
)
start_bar.clear()
start_bar.send_keys(start)
# Sending the end date
end_bar = WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[2]"))
)
end_bar.clear()
end_bar.send_keys(end)
# Clicking on the apply button
apply_button = WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[5]/a"))
)
apply_button.click()
sleep(5)
# Getting the tables on the page and quiting
dataframes = pd.read_html(driver.page_source)
driver.quit()
print(f'{currency} scraped.')
break
except:
driver.quit()
print(f'Failed to scrape {currency}. Trying again in 30 seconds.')
sleep(30)
continue
هذا الأسلوب مفيد جداً في مهام الاستخراج الطويلة، لأنه يقلل الحاجة إلى التدخل اليدوي كلما حدث خلل مؤقت.
تحديد الجدول الصحيح من بين الجداول المستخرجة
بما أن pd.read_html() قد تُرجع أكثر من جدول، يجب التحقق من أسماء الأعمدة لاختيار الجدول الذي يحتوي فعلياً على البيانات التاريخية المطلوبة.
for dataframe in dataframes:
if dataframe.columns.tolist() == ['Date', 'Price', 'Open', 'High', 'Low', 'Change%']:
df = dataframe
break
frames.append(df)
هذه الطريقة عملية عندما تكون بنية الجدول مستقرة. أما إذا كانت أسماء الأعمدة تختلف حسب اللغة أو نسخة الصفحة، فقد تحتاج إلى معيار أكثر مرونة.
تصدير النتائج إلى ملف CSV
إذا كنت تريد حفظ النتائج خارج البرنامج، فيمكن استخدام الدالة to_csv() الموجودة في Pandas بسهولة.
if export_csv:
df.to_csv('currency.csv', index=False)
print(f'{currency}.csv exported.')
return frames
بهذا الشكل ستحصل على قائمة من كائنات DataFrame، مع إمكانية إنشاء ملفات .csv عند الحاجة.
النسخة الكاملة للجزء الأخير من المنطق البرمجي
# Selecting the correct table
for dataframe in dataframes:
if dataframe.columns.tolist() == ['Date', 'Price', 'Open', 'High', 'Low', 'Change%']:
df = dataframe
break
frames.append(df)
# Exporting the .csv file
if export_csv:
df.to_csv('currency.csv', index=False)
print(f'{currency}.csv exported.')
return frames
أفضل الممارسات عند بناء بوت استخراج بيانات
تجنب الضغط على الخادم
إذا كنت تخطط لاستخراج عدد كبير من الصفحات، فمن المهم إضافة فواصل زمنية مدروسة بين الطلبات حتى لا تُرهق الخادم أو تُصنَّف كحركة غير طبيعية.
استخدام البنية القابلة للتوسعة
يمكن تطوير هذا السكربت ليُرجع DataFrame واحداً موحداً يضم بيانات كل العملات، بدلاً من قائمة منفصلة. كما يمكن إنشاء دالة update() لتحديث البيانات التاريخية تلقائياً حتى تاريخ اليوم.
إعادة استخدام المنطق نفسه في مجالات أخرى
المنهجية نفسها لا تقتصر على العملات فقط، بل يمكن تطبيقها على:
- الأسهم.
- المؤشرات.
- السلع.
- العقود المستقبلية.
- الصفحات التي تتطلب تسجيل دخول أو تعبئة نماذج.
مقارنة سريعة بين Selenium وأدوات الاستخراج التقليدية
| الأداة | أفضل استخدام | التعامل مع JavaScript |
التفاعل مع العناصر |
|---|---|---|---|
BeautifulSoup |
الصفحات الثابتة | ضعيف أو غير مباشر | غير متاح |
Selenium |
الصفحات الديناميكية | ممتاز | متاح بالكامل |
requests |
جلب HTML الخام أو API |
لا يدعم التنفيذ | غير متاح |
نصائح تقنية لتحسين جودة السكربت
- استخدم محددات أكثر استقراراً من
XPathالمطلق متى أمكن، مثلidأوclassأوCSS Selector. - أضف سجلاً للأخطاء لتتبع أسباب الفشل المتكرر.
- اجعل أسماء ملفات
CSVمرتبطة باسم العملة لتجنب الاستبدال. - فعّل وضع
headlessعند تشغيل السكربت على خادم. - اختبر الصفحة يدوياً بعد أي تحديث في تصميم الموقع، لأن تغيّر العناصر قد يكسر السكربت.
الخلاصة التقنية
يُظهر هذا المثال أن Selenium ليس مجرد أداة اختبار، بل حل عملي لاستخراج البيانات من الصفحات الديناميكية التي تتطلب تفاعلاً حقيقياً قبل الوصول إلى النتائج. من الناحية التقنية، الجمع بين Selenium وPandas يمنحك مرونة كبيرة في جمع البيانات وتحويلها بسرعة إلى صيغة قابلة للتحليل. ومع الالتزام بإدارة الأخطاء، واحترام موارد المواقع، وتحسين اختيار العناصر، يمكنك بناء بوت موثوق يخدم مشاريع تحليل البيانات والأعمال بكفاءة عالية.