تجريف عالم البرمجة عبر مكتبة Selenium

يُقدم المقال فكرة عامة عن مكتبة Selenium في البايثون ويشرح كيفية استخدام المكتبة في جمع بيانات أسئلة البايثون من قسم سؤال وجواب في عالم البرمجة

ابراهيم البحيصيمنذ 5 سنوات

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

ما هي مكتبة سيلينيوم؟

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

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

لا تحتوي سيلينيوم على متصفح خاص بها، ولكي تعمل، يلزم لذلك وجود متصفح خاص من نوع Firefox أو Chrome، وإذا كنت ترغب بأن يعمل برنامجك في الخلفية، فمن الممكن أن تستخدم متصفح PhantomJS (يدعى بـ headless browser)، حيث يقوم بتحميل الموقع في الذاكرة، ويُمَكِنُك من تنفيذ كافة العمليات المتاحة بالمتصفحات العادية ولكن دون إظهار أي رسومات للمستخدم.

أهمية سيلينيوم

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

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

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

نستطيع تلخيص أهمية سيلينيوم فيما يلي:

  • توفير الوقت والجهد.
  • سهولة الاستخدام.
  • التوافق مع متصفحات مختلفة.
  • العمل في بيئات تشغيل عديدة.
  • تُستخدم في الفحص، التشغيل الآلي ومحاكاة سلوك المستخدم.

Drivers

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

إن عدم وجود هذا المتصفح يؤدي لظهور خطأ من نوعselenium.common.exceptions.WebDriverException الذي يُفيد بعدم توفر المتصفح المطلوب.

الجدول التالي يحتوي على الروابط اللازمة لتنزيل المتصفحات حسب نوعها (المصدر):

Chrome: https://sites.google.com/a/chromium.org/chromedriver/downloads
Edge: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
Firefox: https://github.com/mozilla/geckodriver/releases
Safari: https://webkit.org/blog/6900/webdriver-support-in-safari-10/

مثال بسيط

المثال التالي يوضح كيفية استخدام سيلينيوم في تشغيل متصفح chrome ثم الذهاب لمحرك البحث جوجل والبحث عن كلمة “عالم البرمجة”. هذا المثال يتضمن محاكاة لتشغيل المتصفح، والذهاب لموقع معين، وتعبئة بيانات في خانة البحث ثم الضغط على زر.

from selenium import webdriver
import time

driver = webdriver.Chrome()
driver.implicitly_wait(30)
driver.maximize_window()

driver.get("https://www.google.ps")

search_box = driver.find_element_by_name('q')
search_box.clear()

search_box.send_keys("عالم البرمجة")
search_box.submit()

time.sleep(5)
driver.quit()

قبل تشغيل المثال، يجب تنزيل المتصفح الخاص بـ chrome من الرابط الموجود في الجدول بالأعلى، ووضعه في نفس مسار ملف المثال، أو في مسار يراه مُفسر البايثون المُستخدم.

في البداية نقوم باستيراد الوحدة webdriver التي تحتوي على كافة الدوال والأصناف (classes) التي تُمثل أغلب المتصفحات.  للحصول على نُسخة (instance) من متصفح chrome نستدعي الدالة البانية لهذا المتصفح باستخدام webdriver.Chrome ومن ثم نضبط بعض خصائص هذه النسخة مثل تحديد implicit wait للمتصفح وضبط خاصية maximize window. خاصية implicit wait تعني أنه في حال لم يظهر العنصر الذي نبحث عنه في الصفحة، فيجب على المتصفح الانتظار المدة المحددة، وفي حالة انقضاء هذه المدة يُطلق الاستثناء،ElementNotVisibleException، يُمكنك الاطلاع على المرجع رقم 3 والمرجع رقم 4 لمعرفة المزيد.

للذهاب لصفحة محرك البحث جوجل نستخدم الدالة get ونُمرر لها عنوان المحرك كاملًا. للحصول على نُسخة تُمثل مربع البحث الذي نكتب فيه ما نريد البحث عنه نستخدم الدالةfind_element_by_name ونُمرر لها اسم مربع البحث والذي هو ‘q’ (حصلنا عليه من شيفرة html للصفحة بواسطة الضغط على الزر اليمين للفأرة ومن ثم inspect على مربع البحث).

تُستخدم الدالة clear لمسح أية بيانات مُدخلة سابقًا في مربع البحث. لكتابة وإدخال النص للمربع نستخدم الدالة send_keys ونُمرر لها كلمة “عالم البرمجة” التي نريد البحث عنها. بعد أن أدخلنا الكلمة، قمنا بمحاكاة الضغط على Enter (عملية submit) بعد الانتهاء من كتابة ما نريد البحث عنه. في نهاية الأمر، ننتظر مدة 5 ثواني ثم نخرج من المتصفح ونغلقه بواسطة الدالة quit.

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

عندما نطلب صفحة ما باستخدام أحد المتصفحات، فإن المتصفح يقوم بإرسال طلب request الى الخادم server ويقوم الخادم بإرسال رد يحتوي على مكونات الصفحة. يحتوي الرد على مستندhtml وجميع ما يتعلق من شيفرات CSS و JavaScript وصور.

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

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

لمزيد من المعلومات عن DevTools يمكنك الذهاب للرابط التالي:

https://developers.google.com/web/tools/chrome-devtools/

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

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

  • إيجاد العنصر بواسطة مُعرف id.
  • إيجاد العنصر بواسطة اسم العنصر name.
  • إيجاد العنصر بواسطة مسار xpath.
  • إيجاد العنصر بواسطة link text أو جزء منه.
  • إيجاد العنصر بواسطة اسم الوسم tag name.
  • إيجاد العنصر بواسطة اسم الفئة/الصنف class name.
  • إيجاد العنصر بواسطة مُحدد CSS.

لن نستطيع تغطية كافة الطرق السابقة بالتفصيل في مثالنا، ولكن بمقدورك الذهاب الى الرابط التالي للتعرف أكثر عن هذه الطرق:

http://selenium-python.readthedocs.io/locating-elements.html

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

المثال الأول

سنقوم في هذا المثال بكتابة برنامج بايثون يعمل على جلب عناوين الأسئلة وعدد إجاباتها من قسم "سؤال وجواب" في موقع عالم البرمجة مع إمكانية تحديد الوسم الذي نستهدفه مثل php، java، python .. الخ، والهدف من هذا المثال هو توضيح قدرة مكبتة سيلينيوم في جمع البيانات من موقع مُعين.

نلاحظ كما هو موجود في صفحة سؤال وجواب في عالم البرمجة، أن الصفحة تحتوي على عنصر رئيسي هو جدول الأسئلة. كل سؤال في الجدول يتكون من 5 أجزاء كتالي:

1- أيقونة السؤال، في حال لم تتم إجابة السؤال بعد، ستظهر الإيقونة على شكل علامة استفهام.

2- عنوان السؤال، وهو عبارة عن رابط يُشير لصفحة السؤال.

3- المبرمج الذي قام بطرح السؤال.

4- عُمر السؤال ومنذ متى تم إضافته.

5- عدد الإجابات.

عناصر السؤال في صفحة سؤال وجواب

 

الخطوات العملية للمثال الأول

أولًا: استيراد المكتبات والوحدات اللازمة للعمل وإعداد نُسخة/متغير المتصفح:

في بداية الشيفرة الخاصة بالمثال الأول، نستورد المكتبات اللازمة للعمل، ونقوم بإعداد المتصفح من نوع كروم كتالي:

from selenium import webdriver
import sys
import time

driver = webdriver.Chrome()
driver.implicitly_wait(30)
driver.maximize_window()

ثانيًا: الذهاب لصفحة سؤال وجواب في موقع عالم البرمجة:

عند تشغيل برنامج البايثون من الطرفية وفي حال مررنا معه اسم الوسم الذي نريد الذهاب إليه، سيقوم المتصفح بالذهاب لصفحة الوسم الذي مررناه، واذا لم نُمرر أي وسم فسيذهب المتصفح للصفحة الرئيسية لسؤال وجواب:

if len(sys.argv) > 1:
    tag = sys.argv[1]
    driver.get("https://3alam.pro/questions?tag=" + tag)
else:
    driver.get("https://3alam.pro/questions")

ملاحظة/ طريقة تمرير المتغيرات المذكورة هنا ليست احترافية ولكننا استخدمناها بغرض الشرح فقط.

ثالثًا: الحصول على عنصر الجدول

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

بسبب أن الجدول ليس له id أو name فسنستخدم الدالة find_element_by_xpath للوصول إليه. نستطيع الحصول على xpath الجدول من خلال الضغط على inspect في متصفح كروم أو ما يماثله في المتصفحات الإخرى، ثم نتأكد من أننا نقف على الجدول ثم نقوم بعمل نسخ لمسار xpath.

الصورة التالية توضح ذلك.

الحصول على عنصر الجدول

 

سنجد أن xpath الخاص بالجدول هو:

'//*[@id="main-container"]/div[2]/div/div/div[1]/div/div/table/tbody'

نُمرر xpath للدالة find_element_by_xpath ونحفظ النتيجة في مُتغير:

questions_table = driver.find_element_by_xpath('//*[@id="main-container"]/div[2]/div/div/div[1]/div/div/table/tbody')

رابعًا: الحصول على صفوف الجدول

كل صف في الجدول الذي حصلنا عليه يُمثل سؤالًا مطروحًا في الصفحة، لذا سنستخدم أحد الدوال للحصول على قائمة الصفوف وهي الدالة find_elements_by_tag_name ونُمرر لها إسم وسم html الذي يُمثل الصفوف وهو tr.

نُعرف متغير من نوع Dictionary باسم data وسنستخدمه لحفظ بيانات الأسئلة، وسيكون كل عنصر في هذا المتغير عبارة عن Tuple يتكون من عنوان السؤال، عدد الإجابات، ورابط السؤال. مفتاح العنصر هو رقم السؤال حسب ترتيب ظهوره في الجدول.

trs = questions_table.find_elements_by_tag_name('tr')
data = {}
counter = 0

خامسًا: المرور على صفوف الجدول ومعالجتها

كل صف يحتوي على خلايا، وهذه الخلايا هي التي تحتوي البيانات التي نبحث عنها. لذا سنتعامل بشكل مباشر مع هذه الخلايا بعد الحصول عليها، لمعرفتنا المُسبقة -باستخدام أدوات المتصفح- أن الخلية الأولى تحتوي على أيقونة السؤال، والثانية تحتوي على عنوان السؤال واسم الكاتب وعمر السؤال، والخلية الثالثة تحتوي على عدد الإجابات.

for tr in trs:
    counter += 1
    tds = tr.find_elements_by_tag_name('td')
    question_title = tds[1].text.split("\n")[0]
    question_link = tds[1].find_element_by_tag_name('a').get_attribute("href")
    question_answers = tds[2].text
    data[counter] = (question_title, question_answers, question_link)

سادسًا: المرور على البيانات وطباعتها

for key, value in data.items():
    print(value)
time.sleep(5)
driver.quit()

في نهاية البرنامج نقوم بالخروج من المتصفح (الانتظار 5 ثواني هو من باب إعطاءك فرصة لمشاهدة ما يحدث قبل إغلاق المتصفح).

المثال كاملًا على موقع github:

http://bit.ly/2xT6Tdo

المثال الثاني

سنعمل على بناء مثال يُحاكي عملية ارسال رسالة لمدونة بايثونات من خلال صفحة راسلنا، حيث يقوم المثال عند تنفيذه بالضغط على زر “تواصل معي” في الصفحة الرئيسية ويملئ نموذج التواصل ببيانات معينة ويرسلها لصاحب المدونة.

الهدف من هذا المثال هو توضيح قدرة المكتبة على محاكاة استخدام صفحة والتفاعل معها كمستخدم حقيقي.

في البداية يجب علينا إيجاد جميع المكونات التي سنتعامل معها خلال العملية وهي زر تواصل معي والخانات التي سنمرر لها بيانات التواصل وزر الارسال ورسالة الإعلام أن الرسالة قد أرسلت.

افتح متصفح كروم واذهب للصفحة الرئيسية لمدونة بايثونات، وباستخدام الزر اليمين من الفأرة اضغط على “تواصل معي” في أعلى الصفحة، واختر inspect كما هو موضح في الصورة التالية:

بعد الضغط على inspect سيتغير ترتيب المتصفح ليصبح من جزئين، الجزء الأول يحتوي على الصفحة كما هي، الجزء الثاني يحتوي على أدوات وخيارات DevTools كما هو موضح في الصورة التالية:

chrome devtools

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

مكونات الصفحة التي سنتعامل معها هي كالتالي:

  • زر “تواصل معي” في الصفحة الرئيسية وهو عبارة عن عنصر في قائمة ويمتلك خاصية id=”menu-item-262″.
  • خانة الاسم في صفحة تواصل معي وهي عبارة عن عنصر إدخال ويمتلك خاصية id=”g260″ وخاصية name=”g260″ أيضًا.
  • خانة البريد الإلكتروني في صفحة تواصل معي وهي عبارة عن عنصر إدخال ويمتلك خاصية id=”g260-1″ وخاصية name=”g260-1″ أيضًا.
  • خانة الرسالة في صفحة تواصل معي وهي عبارة عن عنصر إدخال ويمتلك خاصية name=”g260-3″.
  • زر أرسل في صفحة تواصل معي ويندرج تحت صنف class=”pushbutton-wide”.
  • رسالة “تم ارسال الرسالة (الرجوع للخلف)” والتي ستظهر بعد ادخال البيانات والضغط على زر ارسال، ولكن هذه الرسالة ليس لديها أي خاصية يمكن الاستناد عليها للوصول لها.

بعد أن حددنا العناصر التي سنتعامل معها في مثالنا، يجب علينا تحديد سيناريو المحاكاة، وهو بسيطٌ في حالتنا هذه كالتالي:

  • تشغيل المتصفح.
  • الذهاب للصفحة الرئيسية لمدونة بايثونات.
  • الضغط على زر تواصل معي.
  • ادخال اسم المُرسل في خانة الاسم.
  • ادخال البريد الالكتروني.
  • ادخال الرسالة المُراد ارسالها لصاحب المدونة.
  • الضغط على زر ارسال.
  • فحص وجود رسالة “تم ارسال الرسالة” في الصفحة الحالية.
  • الخروج من المتصفح.

الخطوات العملية للمثال الثاني

أولًا: استيراد المكتبات والوحدات اللازمة للعمل وإعداد نُسخة/متغير المتصفح

from selenium import webdriver
import time
driver = webdriver.Chrome()
implicitly_wait(5)
maximize_window()

ثانيًا: الذهاب لصفحة بايثونات

get("https://3alakivyblog.wordpress.com")

ثالثًا: إيجاد زر “تواصل معي” والضغط عليه

contact_me = driver.find_element_by_id("menu-item-262")
contact_me.click()

رابعًا: إيجاد خانة “الاسم” وإدخال نص بداخلها

name = driver.find_element_by_id("g260")
clear()
send_keys("مكتبة سيلينيوم")

خامسًا: إيجاد خانة “البريد الإلكتروني” وإدخال بريد الكتروني بداخلها

email = driver.find_element_by_id("g260-1")
clear()
send_keys("[email protected]")

سادسًا: إيجاد خانة “الرسالة” وإدخال رسالة بداخلها

message = driver.find_element_by_name("g260-3")
clear()
send_keys("شكرًا لك على هذا المقال استمر في قيادة متصفحك")

لاحظ معي أننا هذه المرة لم نبحث عن الخانة باستخدام خاصية id لعدم وجودها للعنصر، واستخدمنا خاصية name للوصول للعنصر.

سابعًا: إيجاد زر “أرسل” والضغط عليه

send_msg = driver.find_element_by_class_name("pushbutton-wide")
submit()

هذه المرة لا يوجد لدينا أي خاصية من id او name، واستخدمنا خاصية class للوصول للعنصر.

ثامنًا: فحص وجود رسالة “تم ارسال الرسالة” في الصفحة الحالية

done_msg = driver.find_element_by_xpath('//*[@id="contact-form-260"]/h3')
if done_msg.text == "تم إرسال الرسالة (الرجوع للخلف)":
print("Message has been sent successfully")
else:
print("Error in sending message")

في حال لم يكن للعنصر الذي نبحث عنه خاصية id او name أو class، سنلجأ حينها للوصول إليه بواسطة ما يُسمى xpath والذي هو عبارة عن طريقة للوصول إلى عناصر مستند xml، وبما أننا نتعامل مع html التي تُشبه xml من حيث الهيكلية، فإننا نستخدم xpath هنا للوصول الى عناصر الصفحة.

لمزيد من المعلومات عن xpath تستطيع الذهاب للرابط التالي:

https://www.w3schools.com/xml/xpath_intro.asp

تاسعًا: الخروج من المتصفح

time.sleep(5)
driver.quit()

في نهاية البرنامج نقوم بالخروج من المتصفح (الانتظار 5 ثواني هو من باب إعطاءك فرصة لمشاهدة ما يحدث قبل إغلاق المتصفح).

من الممكن أن نُطور على هذا المثال بحيث نُمرر البيانات المستخدمة وألا تكون ثابتة في الشيفرة البرمجية، ويكون ذلك باستخدام مكتبة argparser المُضمنة في بايثون وهذا تحسينٌ بسيطٌ على المثال. من الممكن أيضًا أن ندمج هذا المثال في بوت آلي يقوم بإرسال رسالة الشكر مع كل مقال جديد يظهر على المدونة (فكرة جميلة).

المثال كاملا على github:

http://bit.ly/2Qdjujc

لا تنسى قبل تشغيل المثالين أن تكون سيلينيوم قد ثُبتت على نظامك وأن تكون حَملت المتصفح الخاص لكروم من الرابط الموجود في الجدول أعلى المقال.

خاتمة

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

مراجع

1 https://www.linkedin.com/pulse/why-selenium-webdriver-rakesh-singh
2 http://selenium-python.readthedocs.io/
3 https://www.guru99.com/implicit-explicit-waits-selenium.html
4 https://www.seleniumhq.org/docs/04_webdriver_advanced.jsp
5 http://selenium-python.readthedocs.io/locating-elements.html
6 https://www.w3schools.com/xml/xpath_intro.asp
7

https://pythonat.com/

 

 

كلمات دليلية: python selenium
4
إعجاب
11524
مشاهدات
1
مشاركة
2
متابع
متميز
محتوى رهيب

التعليقات (2)

xl33tx:

شكرا أستاذ ابراهيم مقال أكثر من رائع 

Ali Hamza:

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

C:\Users\Hulk\PycharmProjects\new1\venv\Scripts\python.exe C:/Users/Hulk/PycharmProjects/new1/test.py
Traceback (most recent call last):
  File "C:/Users/Hulk/PycharmProjects/new1/test.py", line 1, in <module>
    from selenium import webdriver
ModuleNotFoundError: No module named 'selenium'

Process finished with exit code 1

 

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !