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

لماذا نستخدم Selenium في استخراج البيانات؟
توجد واجهات برمجة تطبيقات وحزم برمجية كثيرة تجعل جمع البيانات المالية أسهل من تنفيذ عملية استخراج يدوي من صفحات الويب. لكن الهدف هنا هو فهم كيفية الاستفادة من Selenium في السيناريوهات العامة، خاصة عندما:
- تكون البيانات معروضة بعد تنفيذ
JavaScript. - تحتاج الصفحة إلى تفاعل قبل ظهور النتائج.
- يلزم تحديد نطاق زمني أو خيارات بحث مخصصة.
- تريد أتمتة الخطوات البشرية المتكررة داخل المتصفح.
فهم الصفحة المستهدفة قبل كتابة الكود
قبل تطوير أي أداة استخراج بيانات، من الضروري فهم بنية الصفحة. المثال هنا يعتمد على صفحة البيانات التاريخية لسعر صرف الدولار مقابل اليورو. تعرض الصفحة جدولاً للبيانات مع خيار لتحديد فترة زمنية مخصصة.
إذا أردت جلب بيانات عملة أخرى مقابل الدولار، فيمكن تعديل رمز العملة داخل الرابط. وإذا كنت تريد تغيير العملة الأساسية نفسها، فيمكنك تعديل جزء usd في عنوان الصفحة.
المكتبات المطلوبة في Python
سنبدأ باستيراد الأدوات اللازمة. نحتاج إلى مكوّنات من Selenium للتحكم في المتصفح، وإلى الدالة sleep() لإضافة فواصل زمنية، وإلى مكتبة 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
إنشاء دالة لاستخراج بيانات العملات
سنكتب دالة باسم get_currencies() تستقبل العناصر التالية:
- قائمة من رموز العملات.
- تاريخ بداية.
- تاريخ نهاية.
- متغير منطقي يحدد ما إذا كان يجب تصدير النتائج إلى ملف
.csv.
سننشئ أيضاً قائمة فارغة باسم frames لتخزين النتائج الخاصة بكل عملة.
def get_currencies(currencies, start, end, export_csv=False):
frames = []
الدوران على قائمة العملات وبناء الرابط
بما أن الدالة تستقبل أكثر من عملة، فمن المنطقي تنفيذ حلقة تمر على كل عنصر في قائمة currencies. لكل عملة، سيتم إنشاء رابط الصفحة، ثم فتحه باستخدام المتصفح الذي يديره Selenium.
في حال كان الخيار option.headless مضبوطاً على False، ستتمكن من مشاهدة كل الخطوات بصرياً داخل نافذة المتصفح. أما إذا كان مضبوطاً على True، فسيعمل البوت في الخلفية دون واجهة مرئية.
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()
التعامل مع النطاق الزمني داخل الصفحة
عند فتح الصفحة، غالباً ستجد أن الموقع يعرض فترة قصيرة افتراضياً. لذلك نحتاج إلى تعديل تاريخ البداية وتاريخ النهاية يدوياً عبر واجهة الصفحة نفسها. هنا تظهر أهمية Selenium، لأنه يستطيع النقر على عناصر الصفحة وملء الحقول كما يفعل المستخدم الحقيقي.
لضمان استقرار التنفيذ، سنستخدم:
WebDriverWaitللانتظار حتى تصبح العناصر جاهزة.EC.element_to_be_clickableللتأكد من أن العنصر قابل للنقر.By.XPATHلتحديد مواقع العناصر داخل الصفحة.
النقر على زر اختيار التاريخ
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()
إدخال تاريخ البداية
بعد فتح نافذة اختيار التاريخ، نحدد حقل البداية، ثم نستخدم 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، ثم نضيف مهلة قصيرة باستخدام sleep() حتى يكتمل تحميل البيانات الجديدة.
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)
قراءة الجداول من الصفحة باستخدام pandas
بعد تحديث الصفحة، يمكننا الاستفادة من الدالة pandas.read_html() لقراءة جميع الجداول الموجودة داخل مصدر الصفحة. ثم نغلق المتصفح لتحرير الموارد.
dataframes = pd.read_html(driver.page_source)
driver.quit()
print(f'{currency} scraped.')
معالجة الأخطاء والاستثناءات في Selenium
من المهم أن تعرف أن Selenium قد يتعثر أحياناً بسبب بطء الشبكة أو تغيّر سلوك الصفحة أو تأخر ظهور عنصر معين. لهذا السبب، من الأفضل وضع العملية كاملة داخل كتلة try ضمن حلقة تكرار مستمرة.
إذا نجحت العملية، نخرج من الحلقة باستخدام break. وإذا حدث خطأ، تُنفذ كتلة except بحيث:
- يتم إغلاق المتصفح عبر
driver.quit(). - تُطبع رسالة توضّح فشل المحاولة.
- ينتظر البرنامج
30ثانية. - تبدأ المحاولة من جديد.
هذا الأسلوب مفيد جداً عند التعامل مع مواقع غير مستقرة أو بطيئة الاستجابة.
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 quitting
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
تحديد الجدول الصحيح من بين الجداول المستخرجة
الدالة read_html() تعيد قائمة من الجداول بصيغة DataFrame. لذلك نحتاج إلى فحص كل جدول والتأكد من أن أسماء أعمدته تطابق الجدول المطلوب، مثل Date وPrice وOpen وغيرها.
بمجرد العثور على الجدول الصحيح، نضيفه إلى القائمة frames.
for dataframe in dataframes:
if dataframe.columns.tolist() == ['Date', 'Price', 'Open', 'High', 'Low', 'Change%']:
df = dataframe
break
frames.append(df)
تصدير النتائج إلى ملف CSV
إذا كانت قيمة export_csv تساوي True، فيمكن حفظ الجدول بسهولة باستخدام الدالة DataFrame.to_csv(). بعد ذلك، تعيد الدالة في النهاية قائمة الجداول كاملة.
if export_csv:
df.to_csv('currency.csv', index=False)
print(f'{currency}.csv exported.')
return frames
الكود الكامل للخطوات الأخيرة
# 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()لتحديث البيانات القديمة حتى التاريخ الحالي. - استخدام المنهجية نفسها لاستخراج بيانات الأسهم، والمؤشرات، والسلع، والعقود المستقبلية.
- إضافة فواصل زمنية أكبر بين الطلبات لتقليل الضغط على خادم الموقع.
- الاستفادة من
proxyموثوق عند العمل على نطاق واسع أو لفترات طويلة.
استخدامات أخرى لـ Selenium
لا يقتصر دور Selenium على استخراج البيانات فقط، بل يمكن الاعتماد عليه أيضاً في مهام أخرى مثل:
- تسجيل الدخول إلى المواقع.
- ملء النماذج تلقائياً.
- اختيار عناصر من القوائم المنسدلة.
- اختبار سلوك الصفحات التفاعلية.
- تنفيذ سيناريوهات أتمتة متقدمة داخل المتصفح.
ورغم أنه ليس الحل الوحيد لهذه المهام، فإنه يظل خياراً عملياً جداً عندما تكون الحاجة إلى محاكاة التفاعل البشري داخل الصفحة أمراً أساسياً.
الخلاصة التقنية
إذا كنت تتعامل مع صفحات ديناميكية لا تكفي معها أدوات الاستخراج التقليدية، فإن Selenium مع Python يوفّر حلاً مرناً وفعالاً. قوته الحقيقية تظهر عندما تحتاج إلى التفاعل مع عناصر الصفحة قبل جمع البيانات. ومع أن هذا الأسلوب أبطأ من بعض البدائل المعتمدة على API، فإنه يمنحك قدرة أكبر على الوصول إلى البيانات المحمية خلف واجهات تفاعلية. للحصول على نتائج مستقرة، احرص على إدارة الاستثناءات جيداً، وإغلاق المتصفح بعد كل عملية، واحترام موارد الموقع المستهدف.