أتمتة الويب الحديث باستخدام بايثون وSelenium

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

من خلال تنفيذ نمط تصميم نموذج كائن الصفحة (POM)، يمكنك إنشاء نصوص أتمتة نظيفة وقابلة للتطوير وسهلة القراءة والصيانة.

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

خلال هذه الرحلة، ستتعلم أفضل الممارسات الحديثة، مثل تطبيق نموذج كائن الصفحة (POM)، الذي يُفهم المشروع والنهج ساعد في الحفاظ على نصوص الأتمتة نظيفة وقابلة للاختبار والصيانة. هل أنت مستعد للبدء؟ تفضل بزيارة bandcamp.com/discover وشغّل بعض الموسيقى المتاحة لتتعرف على الموقع الإلكتروني وتحفز نفسك على هذا المشروع!

فهم المشروع والنهج

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

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

مشروع Selenium

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

  • قم بتشغيل متصفح بدون واجهة مستخدم أو مرئي مثل Firefox أو Chrome باستخدام برنامج تشغيل الويب.
  • قم بزيارة عناوين URL والتنقل بين الصفحات تمامًا كما يفعل المستخدم الحقيقي.
  • قم بتحديد العناصر باستخدام محددات CSS أو XPath أو محددات مماثلة.
  • التفاعل مع العناصر عن طريق النقر أو الكتابة أو السحب أو الانتظار حتى تتغير.

بمجرد تثبيت برنامج التشغيل المناسب للمتصفح الخاص بك، يمكنك التحكم في المتصفح من خلال البرنامج النصي باستخدام Selenium.

سيلينيوم نفسه مكتوب بلغة جافا، ولكنه يحتوي على روابط للغات برمجة مختلفة. في بايثون، يُوزّع على PyPI كحزمة واحدة تُسمى سيلينيوم، والتي يُمكن تثبيتها باستخدام pip.

غالبًا ما يتم استخدام Selenium للاختبار الآلي، ولكنه مفيد بنفس القدر لأتمتة الويب العامة، وهو ما سيركز عليه هذا البرنامج التعليمي.

ملاحظة: ربما تتساءل كيف يختلف Selenium عن الأدوات الأخرى للتفاعلات النصية على الويب، مثل Beautiful Soup، أو Scrapy، أو Requests.

أحد الفروقات الجوهرية هو أن هذه الأدوات ممتازة في التعامل مع البيانات الثابتة، بينما يتيح لك Selenium محاكاة سلوك المستخدم على مستوى JavaScript. هذا يعني أنه يمكنك التفاعل مع محتوى ويب مُولّد ديناميكيًا باستخدام Selenium.

قبل الخوض في أساسيات Selenium، من المفيد أن تكون لديك فكرة واضحة عما ستبنيه بنهاية هذا البرنامج التعليمي. كما ذكرنا، ستنشئ مشغل موسيقى متكامل الوظائف، يعمل على منصة الألعاب، ويتفاعل مع صفحة “اكتشف” على Bandcamp باستخدام متصفح Firefox بدون واجهة رسومية.

Bandcamp اكتشف مشغل الموسيقى الخاص بك

Bandcamp هو متجر تسجيلات شهير عبر الإنترنت ومجتمع موسيقي حيث يمكنك بث الأغاني واستكشاف الفنانين واكتشاف ألبومات جديدة.

يتيح لك Selenium أتمتة التفاعلات المباشرة مع واجهة الويب الخاصة بـ Bandcamp – كما لو كنت تقوم بالنقر والتمرير بنفسك!

سيتم فتح مشروعك النهائي على صفحة Bandcamp Discover في الخلفية، مما يعني أنك لن تتمكن من رؤية أي من أعمال الألبوم الفنية الرائعة:

صفحة Bandcamp Discover

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

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

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

  • play: تشغيل أو استئناف تشغيل مسار، بشكل اختياري من خلال رقم المسار المحدد
  • pause: يوقف مؤقتًا المسار الذي يتم تشغيله حاليًا
  • tracks: تسرد المسارات المحملة حاليًا
  • more: تحميل المزيد من المسارات من صفحة Discover على Bandcamp
  • exit: يقوم بإيقاف تشغيل البرنامج وإغلاق مثيل المتصفح بدون رأس

باستخدام هذه الأوامر، ستتمكن من الاستماع إلى الموسيقى المتوفرة حاليًا على صفحة Discover الخاصة بـ Bandcamp:

Type: play [<track number>] | pause | tracks | more | exit
> play
Track(album='Carrie & Lowell (10th Anniversary Edition)',
      artist='by Sufjan Stevens',
      genre='folk',
      url='https://music.sufjan.com/album/carrie-lowell-10th-anniversary-edition')

Type: play [<track number>] | pause | tracks | more | exit
> tracks
#     Album                          Artist                         Genre
--------------------------------------------------------------------------------
1     Carrie & Lowell (10th Anniv... by Sufjan Stevens              folk
2     moisturizer                    by Wet Leg                     alternative
3     Outpost (DiN11)                by Robert Rich & Ian Boddy     electronic
4     Obscure Power                  by Quest Master                ambient
5     Hex; Or Printing In The Inf... by Earth                       experimental
...

آمل أن يبدو هذا مشروعًا ممتعًا! تحويل واجهة المستخدم الملونة بصور الألبومات إلى تفاعل نصي هو حلم كل مبرمج!

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

يدمج هذا المشروع أيضًا فكرة الاختبار الوظيفي – التأكد من أن الواجهة تعمل كما هو مقصود – مع إمكانية جمع البيانات أو تنفيذ المهام تلقائيًا.

نمط تصميم POM

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

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

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

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

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

في هذا الدرس

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

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

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

إعداد البيئة

لتشغيل Selenium في بايثون، ستحتاج إلى إصدار حديث من بايثون، وحزمة Selenium، ومتصفح، وبرنامج تشغيل متصفح متوافق مع المتصفح الذي اخترته. في هذا الدرس، ستستخدم Firefox مع GeckoDriver. ولكن يمكنك اختيار برنامج تشغيل آخر، مثل ChromeDriver، إذا كنت تفضل استخدام Chrome.

تثبيت Selenium

لمتابعة الخطوات، ثبّت بايثون 3.10 أو أحدث. يمكنك التحقق من تثبيتك بفتح نافذة طرفية والتحقق من إصدار بايثون:

$ python --version
Python 3.13.2

قد يختلف رقم الإصدار الدقيق لديك، ولكن يجب أن يكون 3.10 أو أعلى حتى تتمكن من الاستفادة من مطابقة النمط الهيكلي في Python عند إنشاء واجهة سطر الأوامر (CLI) لمشغل الموسيقى الخاص بك.

بمجرد تثبيت إصدار متوافق من Python، قم بإنشاء بيئة افتراضية بحيث لا تتسبب تثبيتات Selenium وأي تبعيات أخرى في إرباك إعداد Python العالمي الخاص بك.

انتقل إلى مجلد المشروع الخاص بك وقم بإنشاء البيئة باستخدام venv:

PS> python -m venv venv\
PS> .\venv\Scripts\activate
(venv) PS> python --version
Python 3.13.2

ستلاحظ (venv) في موجه shell الخاص بك، مما يعني أن البيئة الافتراضية الخاصة بك نشطة.

بعد ذلك، قم بتثبيت Selenium في هذه البيئة الافتراضية باستخدام pip:

(venv) $ python -m pip install selenium

إن ارتباطات Python الخاصة بـ Selenium التي تحصل عليها باستخدام هذا الأمر هي التبعيات المباشرة الوحيدة لهذا المشروع والتي ستقوم بتثبيتها باستخدام pip.

إذا ثبّتت Selenium مباشرةً، فسيقوم pip بجلب أحدث إصدار مستقر من سيلينيوم من PyPI.

إعداد GeckoDriver لمتصفح Firefox

يتفاعل Selenium مع متصفح حقيقي. قبل المتابعة، تأكد من تثبيت Firefox على جهاز الكمبيوتر.

للتواصل مع فايرفوكس، تحتاج إلى ملف geckodriver الثنائي. نزّل الإصدار الصحيح من صفحة إصدارات Mozilla geckodriver، واختر الإصدار المناسب لنظام التشغيل لديك.

بعد فك الضغط، ضع ملف geckodriver الثنائي في مكان يسهل على نظامك الوصول إليه. على سبيل المثال، في نظامي macOS أو Linux، يمكنك نقله إلى المسار /usr/local/bin. أما في نظام Windows، فيمكنك إضافة المجلد الذي يحتوي على geckodriver.exe إلى مسار نظامك.

تأكد من توفر برنامج التشغيل عن طريق الكتابة:

(venv) $ geckodriver --version
geckodriver 0.36.0 (a3d508507022 2025-02-24 15:57 +0000)
...

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

التحقق من إعداداتك

لتتأكد من أن كل شيء يعمل، افتح Python REPL داخل بيئتك الافتراضية وقم بتجربة المتصفح الخاص بك:

>>> from selenium import webdriver
>>> from selenium.webdriver.firefox.options import Options

>>> options = Options()
>>> options.add_argument("--headless")
>>> driver = webdriver.Firefox(options=options)
>>> driver.get("https://www.python.org")
>>> driver.title
'Welcome to Python.org'
>>> driver.quit()

إذا ظهرت لك رسالة “مرحبًا بك في Python.org”، فتهانينا! لقد شغّل Selenium متصفح Firefox في وضع عدم الواجهة، وانتقل إلى الصفحة الرئيسية لـ Python، وجلب عنوان الصفحة. لقد تأكدت من إعداد بيئتك بشكل صحيح.

إذا كنت ترغب في أن يُدير Selenium متصفحك مع الحفاظ على أدائه، فحاول تشغيل نفس الكود دون إضافة أي خيارات. ستظهر نافذة متصفح، انتقل إلى python.org، وأخيرًا، أغلقها مرة أخرى عند استدعاء الدالة .quit().

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

  • عدم تطابق الإصدار: إذا فشل تثبيت WebDriver، فتأكد من توافق إصدار متصفحك مع الإصدار المطلوب. عادةً، تكون تحديثات GeckoDriver متوافقة مع العديد من إصدارات Firefox، ولكن قد تُسبب الإصدارات القديمة جدًا أو الحديثة جدًا مشكلة.
  • مشكلات الأذونات: في أنظمة UNIX، تحتاج إلى التأكد من أن ملف geckodriver لديه أذونات التنفيذ.
  • مشكلات PATH: تأكد من أن الدليل الذي يحتوي على geckodriver موجود في PATH الخاص بك، حتى يتمكن نظام التشغيل الخاص بك من معرفة مكان العثور عليه.
  • حجب جدار الحماية أو برامج الأمان: في بعض الأحيان، قد تمنع برامج مكافحة الفيروسات شديدة الصرامة حركة مرور WebDriver. قد يُساعد تعطيل أو إضافة استثناءات مؤقتًا.

كانت هذ بعض النصائح السريعة التي قد تقودك في الاتجاه الصحيح إذا لم يكن إعدادك يعمل بعد.

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

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

التنقل عبر صفحة الويب باستخدام Python و Selenium

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

  • تحميل الصفحة وانتظار انتهاء التحميل
  • تحديد موقع العناصر بواسطة محدد CSS أو معرف HTML
  • قراءة قيم السمات أو النص من العناصر

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

يعتمد كل صفحة ويب على نموذج كائن المستند (DOM)، الذي يُمثل عناصر HTML في هيكلية شجرية. وظيفة Selenium هي السماح لك بالاستعلام عن أجزاء من نموذج كائن المستند (DOM) والتعامل معها.

فهم هيكل DOM الخاص بـ Bandcamp

الخطوة الأولى للتفاعل البرمجي مع أي موقع إلكتروني هي التفاعل معه يدويًا. قبل كتابة الكود، عليك فهم الصفحة التي تريد العمل عليها. لذا، افتح متصفحك على https://bandcamp.com/discover.

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

استمع إلى أغنية أو اثنتين، ثم اضغط على زر التشغيل والإيقاف المؤقت عدة مرات، ثم قم بتحميل المزيد من الأغاني لعرضها:

بعد ذلك، افتح أدوات المطور بالنقر بزر الماوس الأيمن على الصفحة واختيار “فحص”. تحتوي صفحة الاكتشاف في Bandcamp على حاوية كبيرة تحتوي على عناصر المسار، ولكل مسار زر مرتبط به لبدء الصوت أو إيقافه مؤقتًا.

من خلال فحص هذه العناصر، يمكنك تحديد الفئات أو السمات ذات الصلة التي ستستخدمها لاحقًا في البرنامج النصي الخاص بك:

<div class="results-grid">
  <ul class="items">
    <li class="results-grid-item">
      <section  class="image-carousel">
        ...
        <button class="play-pause-button" aria-label="Play"></button>
        ...
      </section>
      <div class="meta">
        <p>
          <a href="https://artist-name.bandcamp.com/album/album-name?from=discover_page" >
            <strong>Album Name</strong>
            <span>by Artist Name</span>
          </a>
        </p>
        <p class="genre">genre</p>
      </div>
    </li>
    ...
  </ul>
</div>

إن HTML الفعلي أطول وأكثر تعقيدًا، وقد يتغير أيضًا عندما يقوم Bandcamp بتحديث بنية موقعه.

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

قم بتشغيل متصفح بدون رأس وانتقل إلى عنوان URL

بعد فتح الموقع يدويًا، ستفتح الآن صفحة Discover على Bandcamp باستخدام شفرة بايثون. ابدأ بإنشاء مثيل webdriver.Firefox، ثم انتقل إلى رابط صفحة Discover:

from selenium import webdriver
from selenium.webdriver.firefox.options import Options

options = Options()
options.add_argument("--headless")
driver = webdriver.Firefox(options=options)
driver.implicitly_wait(5)

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

driver.quit()

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

ملاحظة: يُعدّ استدعاء .quit() على مثيلات WebDriver أمرًا مهمًا لتجنب ظهور مثيلات المتصفح غير المرئية بدون واجهة رسومية في الجزء الخلفي من نظامك، مما يستهلك طاقة المعالجة وذاكرة الوصول العشوائي (RAM). هل يبدو الأمر مخيفًا؟ إنه كذلك بالفعل، لذا تذكر إغلاق المثيلات التي تستدعيها!

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

تحديد العناصر في DOM

بعد تحميل الصفحة، ستحتاج إلى تحديد موقع عناصر معينة. قد تبحث عن شريط بحث أو زر تسجيل دخول، أو حتى مقطوعة موسيقية! يدعم Selenium عدة استراتيجيات لتحديد المواقع، منها:

  • حسب الهوية
  • بواسطة محدد CSS
  • بواسطة XPath
  • حسب نص الرابط أو اسم العلامة أو اسم الفصل

في الإصدارات الحديثة من Selenium، النهج الموصى به هو استخدام فئة By مع .find_element() أو .find_elements():

from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.by import By

options = Options()
options.add_argument("--headless")
driver = webdriver.Firefox(options=options)
driver.implicitly_wait(5)

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

pagination_button = driver.find_element(By.ID, "view-more")
print(pagination_button.accessible_name)

tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
print(len(tracks))
print(tracks[0].text)

driver.quit()

في هذا المثال، يمكنك استخدام استراتيجيتين مختلفتين لتحديد الموقع:

  • يمكنك البحث عن عنصر واحد باستخدام .find_element() مع المحدد By.ID للعثور على زر عرض المزيد من النتائج.
  • ابحث عن جميع عناصر المسار المرئية حاليًا على الصفحة باستخدام دالة .find_elements() مع المحدد By.CLASS_NAME. سيؤدي هذا إلى إرجاع قائمة بكائنات WebElement.

ثم قم بطباعة بعض النتائج المجمعة على الطرفية.

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

# ...

track_1 = tracks[0]
album = track_1.find_element(By.CSS_SELECTOR, "div.meta a strong")
print(album.text)

في هذه الحالة، يشير محدد CSS “div.meta a strong” إلى عنصر HTML الذي يحتوي على اسم الألبوم.

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

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

قد تتوقف أي محددات مواقع تختارها عن العمل عند تغيير بنية الموقع. قد يكون الأمر محبطًا بالتأكيد عند كتابة محددات مواقع تتوافق مع لغة HTML الحالية، ولكنها تتعطل عند تغيير تصميم الصفحة – وهذا سيحدث في النهاية!

على سبيل المثال، ضع في اعتبارك تعبير XPath التالي الذي يستهدف عنصرًا بشكل لا لبس فيه:

"/html/body/div[2]/span[1]/a[3]"

تعبير XPath هذا هو مسار مطلق يبدأ من عنصر الجذر <html> وينتقل لأسفل شجرة DOM لتحديد عنصر محدد. ينتقل إلى <body>، ثم إلى <div>
الثاني داخل النص، متبوعًا بـ <span> الأول داخل <div>. وأخيرًا، يحدد عنصر الرابط الثالث داخل <span>.

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

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

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

في هذه المرحلة، ستعرف كيفية بدء تشغيل Selenium، والانتقال إلى عنوان URL، وتحديد العناصر الرئيسية في DOM.

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

التفاعل مع عناصر الويب

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

انقر على الأزرار والروابط

للنقر على زر باستخدام Selenium، تحتاج إلى تحديد موقع عنصر الزر واستدعاء .click() عليه:

button = driver.find_element(By.ID, "submit-button")
button.click()

يؤدي استدعاء .click() إلى توجيه Selenium لمحاكاة نقرة الفأرة. يضمن Selenium ضمنيًا أن يكون العنصر مرئيًا وقابلًا للنقر. إذا كان محجوبًا، فقد يظهر استثناء يفيد بعدم إمكانية التفاعل مع العنصر. قد تحتاج أحيانًا إلى التمرير أو انتظار انتهاء الرسوم المتحركة.

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

from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.by import By

options = Options()
options.add_argument("--headless")
driver = webdriver.Firefox(options=options)
driver.implicitly_wait(5)

driver.get("https://bandcamp.com/discover/")

tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
print(len(tracks))

driver.quit()

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

الآن، أضف الكود للعثور على زر عرض المزيد من النتائج كما فعلت من قبل، وهذه المرة انقر فوقه أيضًا:

import time

# ...

pagination_button = driver.find_element(By.ID, "view-more")
pagination_button.click()

time.sleep(0.5)

tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
print(len(tracks))

driver.quit()

لقد حددتَ زر الترقيم ونقرتَ عليه. ولإتاحة الفرصة للموقع لتحميل النتائج الجديدة، أضفتَ استدعاءً بسيطًا إلى دالة ()time.sleep يُوقف التنفيذ مؤقتًا لمدة نصف ثانية. هناك طرق أفضل للقيام بذلك داخل Selenium، وستتعرف عليها لاحقًا.

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

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

في العديد من صفحات الويب القياسية، إذا كان العنصر موجودًا في DOM، فستعمل .click() بشكل مثالي – ولكن انتبه إلى سلوكيات JavaScript الخاصة والتراكبات.

إرسال ضغطات المفاتيح وإدخال النص

إذا كنت تتعامل مع حقول الإدخال، فيمكنك كتابة نص فيها باستخدام .send_keys():

search_box = driver.find_element(By.TAG_NAME, "input")
search_box.send_keys("Search for this")
search_box.submit()

هنا، نجد عنصر الإدخال من خلال اسم وسم HTML الخاص به، ونكتب استعلامًا، ثم نرسل النموذج. تُعادل دالة .submit() الضغط على مفتاح الإدخال عند التركيز على حقل نموذج، ولكن يمكنك أيضًا توجيه المتصفح صراحةً للضغط على مفتاح الإدخال:

from selenium.webdriver.common.keys import Keys

# ...

search_box.send_keys(Keys.ENTER)

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

نظرًا لأنه من الممتع رؤية نوع الكود الخاص بك على موقع ويب، قم بتشغيل البرنامج النصي التالي دون استخدام الوضع بدون رأس:

import time
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()  # Run in normal mode
driver.implicitly_wait(5)

driver.get("https://bandcamp.com/discover/")

# Accept cookies, if required
try:
    cookie_accept_button = driver.find_element(
        By.CSS_SELECTOR,
        "#cookie-control-dialog button.g-button.outline",
    )
    cookie_accept_button.click()
except NoSuchElementException:
    pass

time.sleep(0.5)

search = driver.find_element(By.CLASS_NAME, "site-search-form")
search_field = search.find_element(By.TAG_NAME, "input")
search_field.send_keys("selenium")
search_field.submit()

time.sleep(5)

driver.quit()

عند تشغيل هذا البرنامج النصي، ستشاهد كيف يفتح Python نافذة المتصفح، ويحدد موقع مربع البحث في الأعلى، ويدخل النص الذي مررته إلى .send_keys()، ويرسل النموذج.

يتضمن الكود استدعاءً لـ time.sleep() لإعطائك لحظة للنظر إلى الصفحة المحملة حديثًا قبل إغلاق مثيل المتصفح باستخدام .quit().

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

التعامل مع العناصر المخفية أو المتراكبة

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

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

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

# ...

cookie_accept_button = driver.find_element(
    By.CSS_SELECTOR,
    "#cookie-control-dialog button.g-button.outline",
)
cookie_accept_button.click()

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

نظرًا لأن بعض النوافذ المنبثقة HTML، مثل نماذج موافقة ملفات تعريف الارتباط، قد تستهدف مناطق جغرافية معينة فقط، فقد يكون من الجيد مراعاة ذلك عن طريق تغليف المنطق في كتلة try…except:

from selenium.common.exceptions import NoSuchElementException

# ...

try:
    cookie_accept_button = driver.find_element(
        By.CSS_SELECTOR,
        "#cookie-control-dialog button.g-button.outline",
    )
    cookie_accept_button.click()
except NoSuchElementException:
    pass

بتغليف منطق موافقة ملفات تعريف الارتباط في كتلة try…except، ستجعل شيفرتك أكثر متانة وتنوعًا. باتباع EAFP، تطلب أولًا من Selenium العثور على زر موافقة ملفات تعريف الارتباط. إذا كان الزر موجودًا، فاطلب من Selenium النقر عليه. إذا لم يكن موجودًا، يُطلق إطار العمل استثناء NoSuchElementException، والذي تلتقطه وتتابعه بعبارة pass لمواصلة التنفيذ العادي.

هناك طريقة أخرى يمكنك اتباعها وهي استخدام JavaScript مباشرةً:

driver.execute_script("arguments[0].click();", overlay_element)

يُنفِّذ هذا المقتطف من الكود دالة جافا سكريبت قصيرة تُفعِّل الصفحة. قد يُساعد هذا على إزالة بعض التراكبات التي لا تحتاج إلى استهدافها. ولكن توخَّ الحذر، فتجاوز تفاعلات المستخدم القياسية قد يُؤدِّي إلى خرق شروط الاختبار الفعلية.

بالنسبة للمشروع النهائي المستند إلى Bandcamp، لن تحتاج إلى مثل هذه الحلول البديلة. لقد حددتَ نموذج موافقة ملفات تعريف الارتباط، وعناصر تتبع الموقع سهلة التفاعل.

استخدم التمرير والسحب والإفلات والإيماءات الأكثر تعقيدًا

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

from selenium.webdriver import ActionChains

# ...

menu = driver.find_element(By.CSS_SELECTOR, ".menu")
submenu = driver.find_element(By.CSS_SELECTOR, ".menu #submenu")

actions = ActionChains(driver)
actions.move_to_element(menu)
actions.click(submenu)

actions.perform()

ابدأ بتحديد العنصرين المعنيين، ثم أنشئ مثيلاً لـ ActionChains. ثم استخدم الدالة .move_to_element() لتنفيذ عملية تمرير الماوس على عنصر القائمة. تُفعّل هذه العملية قائمة منسدلة تتيح لك تحديد عنصر في قائمة فرعية. ولأن القائمة الفرعية أصبحت الآن مفتوحة للتفاعل، يمكنك استدعاء الدالة .click() على هذا العنصر الفرعي.

في هذا المثال، تقوم بتكديس إجراءات متعددة، ثم تُنفّذها بالترتيب المُحدّد باستخدام دالة .perform(). تُعد إمكانية جمع الإجراءات قبل تنفيذها معًا ميزةً مفيدةً في ActionChains.

مع أن تطبيق Bandcamp النهائي لن يتطلب مثل هذه الإيماءات المتقدمة، إلا أنه من الجيد معرفة أن Selenium قادر على التعامل معها. إذا كنت ترغب في مرجع شامل، فراجع Action Chains في وثائق Selenium.

إرسال النماذج

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

signup_form = driver.find_element(By.ID, "signup-form")

email_input = signup_form.find_element(By.NAME, "email")
password_input = signup_form.find_element(By.NAME, "password")

email_input.send_keys("user@example.com")
password_input.send_keys("MySecurePassword123")

signup_form.submit()

تمامًا كما هو الحال عند التفاعل مع نموذج يدويًا، يمكنك ملء حقول إدخال متعددة ثم إرسال النموذج بالكامل دفعة واحدة.

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

مع أن بعض مقتطفات الشيفرة البرمجية في هذا القسم عامة نوعًا ما، إلا أن معظمها يُضاف إلى مشروعك النهائي. ستُطبّق قريبًا نقرات الأزرار لتشغيل المسارات وإيقافها مؤقتًا، وتحميل مسارات إضافية بالضغط على زر “عرض المزيد من النتائج”. معرفة كيفية تحديد مواقع العناصر وتفاعلات الإصدار باستخدام .click() أمرٌ أساسيٌّ لتحقيق ذلك.

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

التعامل مع المحتوى الديناميكي

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

لقد استخدمت ()time.sleep في الأمثلة السابقة، ولكن Selenium لديه حلول مدمجة أكثر مرونة وقوة للتعامل مع هذا التحدي.

فهم الانتظارات الضمنية والصريحة والطلاقة

يوفر Selenium آليات انتظار مدمجة مختلفة تمنحك قدرًا كبيرًا من المرونة في كيفية انتظار موقع لتمثيل الحالة التي تحتاجها للتفاعل.

يميز تنفيذ Java لـ Selenium بين ثلاثة أنواع من الانتظار:

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

في Java، الانتظار الصريح هو مجرد انتظار سلس مع تطبيق قيود افتراضية معينة.

تتخطى روابط بايثون لسيلينيوم إعداد نوع ثالث من الانتظار، وتُظهر الانتظارات الضمنية والصريحة. في بايثون، تتمتع الانتظارات الصريحة بنفس المرونة التي يوفرها انتظار جافا السلس.

إعداد انتظار ضمني

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

driver.implicitly_wait(5)

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

إضافة انتظار ضمني قد يكون حلاً سريعًا في حال تعطل البرنامج النصي بسبب مشاكل في وقت التحميل، ويشكل عمومًا حماية جيدة. يُعد هذا بالتأكيد أفضل من استدعاء دالة ()time.sleep عدة مرات في الكود! ومع ذلك، فهو انتظار عام غير مرتبط بأي شروط، ولا يقتصر على عنصر أو تفاعل محدد.

استخدم الانتظارات الصريحة للانتظار المستهدف

إن أهم ما يميز انتظار المحتوى الديناميكي عند العمل مع Selenium هو الانتظار الصريح. يستخدم الانتظار الصريح كائن WebDriverWait مع شروط محددة مسبقًا. هذا أكثر مرونة بكثير!

انسخ الكود الذي كتبته في interaction.py لتحميل مسارات إضافية في ملف جديد يمكنك تسميته observation.py:

import time
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.by import By

options = Options()
options.add_argument("--headless")
driver = webdriver.Firefox(options=options)
driver.implicitly_wait(5)

driver.get("https://bandcamp.com/discover/")

tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
print(len(tracks))

try:
    cookie_accept_button = driver.find_element(
        By.CSS_SELECTOR,
        "#cookie-control-dialog button.g-button.outline",
    )
    cookie_accept_button.click()
except NoSuchElementException:
    pass

pagination_button = driver.find_element(By.ID, "view-more")
pagination_button.click()

time.sleep(0.5)

tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
print(len(tracks))

driver.quit()

هنا، استخدمتَ الدالة time.sleep(0.5) لمنح Bandcamp وقتًا لتحميل المسارات المطلوبة. قد يكون هذا مناسبًا لك، لكن مدة وضع الكود في وضع السكون تعتمد على عوامل مثل سرعة الإنترنت، والتي لا يمكنك التنبؤ بها بدقة.

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

لذلك، يمكنك استخدام انتظار صريح للانتظار بالضبط بقدر ما هو ضروري:

# ...

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# ...

wait = WebDriverWait(driver, 10)
wait.until(
    EC.element_to_be_clickable((By.ID, "view-more"))
)

tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
print(len(tracks))

driver.quit()

لقد حذفتَ time.sleep(0.5) واستبدلتَه بشرط انتظار صريح. تحديدًا، طلبتَ من Selenium الانتظار حتى يصبح العنصر ذو المعرف view-more، وهو زر الترقيم، قابلًا للنقر.

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

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

إن استدعاء .until() على كائن WebDriverWait يعيد العنصر الذي كان Selenium ينتظره، لذا فإن الطريقة الشائعة هي الانتظار حتى يصبح الزر قابلاً للنقر، ثم النقر فوقه مثل هذا:

wait = WebDriverWait(driver, 10)

pagination_button = wait.until(
    EC.element_to_be_clickable((By.ID, "view-more"))
)

pagination_button.click()

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

اختر من بين الشروط المتوقعة الشائعة

تحتوي وحدة expected_conditions على العديد من الشروط التي يمكن لكائن WebDriverWait الخاص بك الانتظار عليها، مثل:

الدالةالوصف
presence_of_element_located()ينتظر حتى يصبح العنصر موجودًا في DOM.
visibility_of_element_located()ينتظر حتى يصبح العنصر موجودًا ومرئيًا.
element_to_be_clickable()ينتظر حتى يصبح العنصر مرئيًا ومُمكّنًا للنقر فوقه.
alert_is_present()ينتظر ظهور تنبيه JavaScript.
title_is() / title_contains()يتحقق ما إذا كان عنوان الصفحة يتطابق تمامًا أو يحتوي على سلسلة فرعية.
url_to_be() / url_contains()يتحقق من أن عنوان URL الحالي يتطابق تمامًا مع سلسلة فرعية أو يحتوي عليها.

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

على سبيل المثال، قد ترغب في الانتظار حتى يحتوي مسار واحد على الأقل من مسارات Bandcamp على خاصية نصية غير فارغة. يمكنك تحقيق ذلك بتعريف دالة باستخدام دالة ()any في بايثون وتعبير مولد:

wait = WebDriverWait(driver, 10)

def tracks_loaded(driver):
    track_cards = driver.find_elements(By.CLASS_NAME, "results-grid-item")
    return any(card.text.strip() for card in track_cards)

wait.until(tracks_loaded)

بعد تعريف الدالة ()tracks_loaded، يمكنك تمريرها إلى .until() كحجة. في هذه الحالة، تعمل الدالة Selenium عندما تُرجع الدالة ()tracks_loaded قيمة صحيحة.

بالإضافة إلى ذلك، يدعم WebDriverWait أيضًا المعلمات لتعيين شروط انتظار أكثر تقدمًا تتوافق مع الانتظار السلس في Java:

  • timeout: تحدد عدد الثواني التي يجب انتظارها قبل انتهاء المهلة الزمنية
  • poll_frequency: يحدد المدة التي يجب الانتظار فيها بين الإستدعاءات ويتم تعيينها افتراضيًا على نصف ثانية
  • ignore_exceptions: يحدد الاستثناءات التي يجب تجاهلها أثناء الانتظار ويتم تعيينها افتراضيًا على NoSuchElementException فقط

في المثال أعلاه، قمت بإنشاء كائن WebDriverWait بمهلة زمنية قدرها 10. وهذا يعني أن Selenium سينتظر حتى يتم استيفاء الشرط المتوقع أو مرور عشر ثوانٍ، أيهما يحدث أولاً.

وبالمثل، يمكنك أيضًا تمرير قيم لـ poll_frequency و ignore_exceptions لتخصيص الانتظار الصريح بشكل أكبر.

في معظم الحالات، من الجيد العمل مع الانتظارات الصريحة لأنها تسمح لك بتحديد الحالة التي تنتظرها بدقة.

معالجة مشكلات المزامنة

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

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

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

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

قد تُشغّل المواقع أيضًا تنبيهات JavaScript. يمكنك استخدام وظيفة Selenium المُدمجة للتبديل إلى هذه التنبيهات وتجاهلها. إذا كان ظهور التنبيه غير مُؤكّد، يُمكنك معالجة ذلك باستخدام كتلة try…except وNoAlertPresentException:

from selenium.common.exceptions import NoAlertPresentException

try:
    alert = driver.switch_to.alert
    alert.dismiss()  # Or alert.accept()
except NoAlertPresentException:
    pass

يستخدم هذا البناء خاصية .switch_to في Selenium. سيتجاهل تنبيه JavaScript إذا فعّله الموقع. إذا لم يكن هناك تنبيه وحاولت التبديل إليه، فسيُطلق Selenium استثناء NoAlertPresentException، وسيُكمل الكود عمله بشكل طبيعي.

لن تحتاج إلى التعامل مع هذه الأنواع من التنبيهات لأتمتة مشغل الموسيقى الخاص بك.

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

في القسم التالي، ستتعلم كيف يساعد دليل العمليات (POM) في عزل تفاعلاتك مع صفحة “اكتشف” عن منطق عملك الذي يُحدد المسار الذي سيتم تشغيله. ستُعرّف فئات بايثون لتمثيل الصفحات والمكونات. يُحافظ هذا النهج على شيفرتك معيارية وقابلة للاختبار وقابلة للصيانة بشكل أفضل، حتى لو غيّر Bandcamp تصميمه.

تنفيذ نموذج كائن الصفحة (POM)

كما رأيت، يُمكن لـ Selenium التعامل مع أي تفاعل ويب تقريبًا. ولكن إذا واصلتَ تجميع جميع أكوادك البرمجية في ملف واحد، فستحصل على فوضى من مُحددات المواقع، وعبارات الانتظار، والمنطق المُكرر. وهنا يبرز نموذج كائن الصفحة (POM) بفصله بنية الصفحة عن منطق العمل.

فهم نمط تصميم POM

POM هو نمط تصميم يُمثِّل فيه كل مكون مهم في الصفحة بفئة مُخصَّصة في الكود. تعرف هذه الفئة كيفية تحديد العناصر في تلك المنطقة المُحدَّدة والتفاعل معها. يستخدم باقي تطبيقك هذه الفئات دون القلق بشأن المُحدِّدات الأساسية أو منطق الانتظار الخاص بذلك العنصر.

يؤدي تنفيذ POM لتطبيقات Selenium الخاصة بك – سواء كان ذلك للاختبار الآلي أو لمهام أتمتة الويب الأخرى – إلى جعل الكود الخاص بك أكثر قابلية للصيانة واستقرارًا:

للبدء في بناء مشروع سيلينيوم باتباع دليل عمليات المشروع (POM)، عليك فهم صفحة الويب، أو صفحات الويب، التي ستتفاعل معها. الخطوة الأولى البسيطة هي إنشاء فئة جديدة لكل صفحة ويب تتعامل معها. يمكنك إضافة هذه الفئات إلى وحدة باسم pages.py.

ومع ذلك، يقترح POM أن تركز بشكل أساسي على عناصر الصفحة، أو الألواح، أكثر من التركيز على الصفحات الكاملة:

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

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

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

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

عند اتباع هذه الاقتراحات، ستحصل في النهاية على بنية ملف أساسية تتكون من أربع وحدات وملف __init__.py:

bandcamp/
├── __init__.py
├── base.py
├── elements.py
├── locators.py
└── pages.py

إضافة ملف __init__.py يُعَدّ bandcamp حزمة بايثون عادية يُمكنك الرجوع إليها عند الاستيراد. لا حاجة لإضافة أي محتوى إلى الملف ليعمل هذا.

بعد إعداد هذه البنية الأساسية، يمكنك البدء بممارسة التفكير من منظور POM. أحضر قلمًا وورقة، وافتح صفحة “اكتشف” على Bandcamp، وشغّل بعض الموسيقى الجديدة إذا أردت، وارسم مكونات الصفحة المهمة التي ستتفاعل معها لبناء مشغل الموسيقى.

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

إنشاء قاعدة للصفحات والعناصر

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

يجب أن تتعامل الفئات الأساسية مع منطق الإعداد الذي ينطبق على جميع العناصر الفرعية، مثل تعيين حجم منفذ العرض وتهيئة كائن WebDriverWait:

from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.wait import WebDriverWait

MAX_WAIT_SECONDS = 10.0
DEFAULT_WINDOW_SIZE = (1920, 3000)

class WebPage:
    def __init__(self, driver: WebDriver) -> None:
        self._driver = driver
        self._driver.set_window_size(*DEFAULT_WINDOW_SIZE)
        self._driver.implicitly_wait(5)
        self._wait = WebDriverWait(driver, MAX_WAIT_SECONDS)

class WebComponent(WebPage):
    def __init__(self, parent: WebElement, driver: WebDriver) -> None:
        super().__init__(driver)
        self._parent = parent

في هذا الإصدار الأولي من base.py، يمكنك تعريف فئتين: WebPage وWebComponent.

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

يحتفظ الصنف الثاني، WebComponent، بمرجع إلى عنصر WebElement أساسي، بالإضافة إلى وراثة نفس كائني التشغيل والانتظار من صفحة الويب. أنت تستورد صنف WebElement من webelement لتتمكن من كتابة تلميح للمعلمة الأساسية بشكل صحيح. لتجنب الالتباس مع هذا الصنف المُقدم من Selenium، سمِّ صنفك الأساسي لعناصر الويب WebComponent.

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

وصف صفحة الويب الخاصة بك ككائن صفحة

الآن حان وقت التفكير في بنية الصفحة المحددة التي ترغب في اختبارها أو أتمتتها. في حالتك، هذه هي صفحة “اكتشف” على Bandcamp. لكنك لن تحتاج إلى كل ما هو موجود على الموقع. ألقِ نظرة أخرى على الصفحة المباشرة وفكّر فيما تحتاج حقًا إلى نمذجته في هذا الكائن عالي المستوى الذي يمثل صفحة “اكتشف” بأكملها.

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

from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.remote.webdriver import WebDriver

from bandcamp.base import WebPage
from bandcamp.elements import TrackListElement
from bandcamp.locators import DiscoverPageLocator

class DiscoverPage(WebPage):
    """Model the relevant parts of the Bandcamp Discover page."""

    def __init__(self, driver: WebDriver) -> None:
        super().__init__(driver)
        self._accept_cookie_consent()
        self.discover_tracklist = TrackListElement(
            self._driver.find_element(*DiscoverPageLocator.DISCOVER_RESULTS),
            self._driver,
        )

    def _accept_cookie_consent(self) -> None:
        """Accept the necessary cookie consent."""
        try:
            self._driver.find_element(
                *DiscoverPageLocator.COOKIE_ACCEPT_NECESSARY
            ).click()
        except NoSuchElementException:
            pass

أعد استخدام فئة WebPage الأساسية التي أعددتها في base.py، واسمح لصفحة DiscoverPage الجديدة بالوراثة منها. هذا يتيح لك الوصول إلى برنامج تشغيل ويب كـ self._driver وانتظار صريح كـ self._wait.

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

نظرًا لأن قائمة المسارات تعد عنصرًا مهمًا في صفحة Discover، فهي تستحق كائن الصفحة الخاص بها.

ستقوم بإنشاء نموذج لعناصر الصفحة في ملف مخصص، elements.py، لذا فإن الاستيراد على السطر 5 بالإضافة إلى إنشاء TrackListElement على الأسطر من 14 إلى 17 هما مجرد وعود بالكود الذي سيتم تقديمه في الوقت الحالي.

وبالمثل، ينطبق الأمر نفسه على مراجع DiscoverPageLocator في وحدة تحديد المواقع واستخدامها في الدالة المساعدة ._accept_cookie_consent(). ولكن لا تقلق، ستُعالج كل هذا قريبًا!

في الوقت الحالي، يمكنك الاستمتاع بالإنجاز المتمثل في وصف أجزاء صفحة Discover في Bandcamp ذات الصلة بمشغل الموسيقى الخاص بك ككائن صفحة حقيقي!

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

ننتقل الآن إلى ملف elements.py، حيث تُجرى معظم عمليات التجريد المتعلقة بـ POM في مشروع Selenium هذا. مرة أخرى، يمكنك التوقف قليلاً والتفكير في عناصر الصفحة المهمة للتفاعل. يبرز عنصران:

  • يقوم TrackListElement بإنشاء نموذج لعنصر الحاوية الذي يحتوي على جميع مسارات الموسيقى، والزر لتحميل المزيد من النتائج.
  • يقوم TrackElement بإنشاء مسار موسيقي واحد ويتيح لك تشغيل المسارات وإيقافها مؤقتًا، بالإضافة إلى جلب معلومات المسار.

بدءًا من TrackListElement، يجب أن يتذكر المسارات المتاحة، ويحتاج إلى وظيفة لتحميل مسارات إضافية:

from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as EC

from bandcamp.base import WebComponent
from bandcamp.locators import TrackListLocator

class TrackListElement(WebComponent):
    """Model the track list on Bandcamp's Discover page."""

    def __init__(self, parent: WebElement, driver: WebDriver = None) -> None:
        super().__init__(parent, driver)
        self.available_tracks = self._get_available_tracks()

    def load_more(self) -> None:
        """Load additional tracks."""
        view_more_button = self._driver.find_element(
            *TrackListLocator.PAGINATION_BUTTON
        )
        view_more_button.click()
        # The button is disabled until all new tracks are loaded.
        self._wait.until(
            EC.element_to_be_clickable(TrackListLocator.PAGINATION_BUTTON)
        )
        self.available_tracks = self._get_available_tracks()

    def _get_available_tracks(self) -> list:
        """Find all currently available tracks."""
        self._wait.until(
            self._track_text_loaded,
            message="Timeout waiting for track text to load",
        )

        all_tracks = self._driver.find_elements(*TrackListLocator.ITEM)

        # Filter tracks that are displayed and have text.
        return [
            TrackElement(track, self._driver)
            for track in all_tracks
            if track.is_displayed() and track.text.strip()
        ]

    def _track_text_loaded(self, driver):
        """Check if the track text has loaded."""
        return any(
            e.is_displayed() and e.text.strip()
            for e in driver.find_elements(*TrackListLocator.ITEM)
        )

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

قد يبدو هذا كمًّا هائلًا من التعليمات البرمجية، إلا أنك سبق أن واجهتَ وفكّرتَ في الكثير منها! في الأقسام السابقة، طبّقتَ نفس الوظيفة لتحميل مسارات إضافية إلى العرض، والتي دمجتَها الآن في دالة .load_more(). بعد تحديد موقع الزر الأيمن أولًا، انقر عليه، ثم استخدم خاصية الانتظار الصريح للتوقف مؤقتًا حتى يتم تحميل جميع المسارات الجديدة.

في السابق، بحثتَ أيضًا عن المسارات. الآن، أضفتَ بعض التحقق للتأكد من وجود زر تشغيل لهذه المسارات، وعرضها، واحتوائها على نص على الأقل. لاحظتَ أيضًا شيفرةً مشابهةً في شرط الانتظار المخصص لـ ._track_text_loaded() عند التعرّف على الشروط المتوقعة. من الرائع رؤية كل ذلك يتبلور!

بعد إعداد TrackListElement، تكون قد قطعت نصف الطريق. بعد ذلك، ستُعدّ TrackElement الناقص لنمذجة لوحات المسارات الفردية. في فئات POM، تُعرّف دوال تُمثّل إجراءات المستخدم. على سبيل المثال، قد يحتوي TrackElement على دوال .play() و.pause():

# ...

from bandcamp.locators import TrackListLocator, TrackLocator

# ...

class TrackElement(WebComponent):
    """Model a playable track on Bandcamp's Discover page."""

    def play(self) -> None:
        """Play the track."""
        if not self.is_playing:
            self._get_play_button().click()

    def pause(self) -> None:
        """Pause the track."""
        if self.is_playing:
            self._get_play_button().click()

    @property
    def is_playing(self) -> bool:
        return "Pause" in self._get_play_button().get_attribute("aria-label")

    def _get_play_button(self):
        return self._parent.find_element(*TrackLocator.PLAY_BUTTON)

بإضافة هذا الكود، يمكنك إنشاء نموذج لعنصر مسار وعرض أهم التفاعلات معه – الضغط على زر التشغيل لبدء الأغنية، والضغط على زر الإيقاف المؤقت لإيقافها. لإكمال المنطق، أضف ._is_playing كخاصية، و._get_play_button() كدالة مساعدة لتحديد موقع زر التشغيل وإرجاعه في عنصر مسار.

لاحظ أن دالة ._get_play_button() تستخدم دالة ()self._parent.find_element. ولأن TrackElement يرث من WebComponent، فإنه يحتوي على سمة ._parent، ويكون العنصر الأصلي هو حاوية المسار، وليس الصفحة بأكملها. تُغلّف هذه الطريقة كيفية تشغيل المسار، مما يُتيح لك استدعاء دالة .play() على TrackElement.

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

from selenium.common.exceptions import NoSuchElementException

# ...

class TrackElement(WebComponent):
    # ...

    def _get_track_info(self) -> dict:
        """Create a representation of the track's relevant information."""
        full_url = self._parent.find_element(*TrackLocator.URL).get_attribute(
            "href"
        )
        # Cut off the referrer query parameter
        clean_url = full_url.split("?")[0] if full_url else ""
        # Some tracks don't have a genre
        try:
            genre = self._parent.find_element(*TrackLocator.GENRE).text
        except NoSuchElementException:
            genre = ""
        return {
            "album": self._parent.find_element(*TrackLocator.ALBUM).text,
            "artist": self._parent.find_element(*TrackLocator.ARTIST).text,
            "genre": genre,
            "url": clean_url,
        }

لقد أنشأتَ طريقة مساعدة أخرى تُحدد الألبوم والفنان والنوع ورابط الألبوم. ولأن الفنانين لا يُفضلون رؤية موسيقاهم مُدرجةً في خانات الأنواع، يبدو أن Bandcamp جعل إضافة الأنواع اختيارية. يجب أن يُراعي الكود الخاص بك ذلك، لذا أنشأتَ كتلة try…exception أخرى تُضيف سلسلة نصية فارغة إذا لم تُقدَّم معلومات عن النوع.

يمكنك أيضًا استخدام تعبير شرطي في السطر 14 لقطع معلمة استعلام مرجعية محتملة وعرض عنوان URL نظيف لصفحة الألبوم.

رائع! مع هذه الإضافة، تكون قد انتهيت من إعداد كلٍّ من ملفي pages.py وelements.py، وبذلك تُطبّق POM بفعالية في تصميم أتمتة الويب لديك. ولكن لا يزال هناك وعد واحد يجب الوفاء به. عليك ملء ملف locators.py للسماح لجميع عناصر صفحتك باستخدام محددات المواقع التي تحتاجها بشدة للعثور على أي شيء على الصفحة.

إبقاء المواقع منفصلة

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

لقد استخدمتَ بالفعل صيغة عامل فك الضغط مع بعض أسماء الفئات الوصفية في الكود الذي كتبته سابقًا. الآن، حان الوقت لإعطاء TrackLocator.ARTIST* بعض المعنى من خلال إعداد الفئات وسمات الفئات التي استهدفتها بهذا الكود:

from selenium.webdriver.common.by import By

class DiscoverPageLocator:
    DISCOVER_RESULTS = (By.CLASS_NAME, "results-grid")
    COOKIE_ACCEPT_NECESSARY = (
        By.CSS_SELECTOR,
        "#cookie-control-dialog button.g-button.outline",
    )

class TrackListLocator:
    ITEM = (By.CLASS_NAME, "results-grid-item")
    PAGINATION_BUTTON = (By.ID, "view-more")

class TrackLocator:
    PLAY_BUTTON = (By.CSS_SELECTOR, "button.play-pause-button")
    URL = (By.CSS_SELECTOR, "div.meta p a")
    ALBUM = (By.CSS_SELECTOR, "div.meta p a strong")
    GENRE = (By.CSS_SELECTOR, "div.meta p.genre")
    ARTIST = (By.CSS_SELECTOR, "div.meta p a span")

في هذا المثال التطبيقي، تُخزّن مُحدِّدات المواقع ذات الصلة في فئات تُسمَّى باسم كائنات الصفحة المقابلة التي عرّفتها. تُخزَّن كمجموعات من عنصرين لكلٍّ منها. يُسجِّل العنصر الأول استراتيجية المُحدِّد التي تستخدمها، بينما يُمثِّل العنصر الثاني سلسلة المُحدِّد.

يتيح لك هذا استخدام عامل فك الضغط لتوفير كل من .find_element() والطرق ذات الصلة كوسائط.

الآن، جميع مُحدِّدات المواقع لديك موجودة في مكان واحد! يا لها من سعادة! ما عليك الآن سوى تحديث ملف locators.py عندما يُقرر Bandcamp نقل عناصر المسار إلى حاوية أخرى.

يمكن لكودك أن يستمر في الرجوع إلى DiscoverPageLocator.DISCOVER_RESULTS أينما احتاج إلى العثور على تلك الحاوية، حتى لو استُخدم في أماكن متعددة. لن تحتاج إلى البحث عن محددات المواقع المنتشرة في قاعدة بيانات Selenium المعقدة بشكل متزايد. يُحسّن هذا النهج من إمكانية الصيانة، وهو موثق ذاتيًا، لذا سيُرسل لك زملاؤك رسائل شكر وتقدير لسنوات قادمة.

استمتع بفوائد POM في الممارسة العملية

بعد أن قمتَ بتصميم جميع عناصر الصفحة اللازمة لهذا المشروع باتباع دليل العمليات (POM)، يمكنكَ تجربته والاستمتاع بالتجربة التجريدية التي يوفرها هذا الإعداد. انتقل إلى المجلد الرئيسي الذي يحتوي أيضًا على وحدة Bandcamp، ثم ابدأ جلسة REPL جديدة وشغّل أغنية.

>>> from selenium.webdriver import Firefox
>>> from bandcamp.pages import DiscoverPage

>>> BANDCAMP_DISCOVER_URL = "https://bandcamp.com/discover/"
>>> driver = Firefox()
>>> driver.get(BANDCAMP_DISCOVER_URL)

>>> page = DiscoverPage(driver)

>>> track_1 = page.discover_tracklist.available_tracks[0]
>>> track_1.play()
>>> track_1.pause()

>>> page._driver.quit()

يُبقي هذا الكود رأس المتصفح على الشاشة لتتمكن من رؤية أتمتتك عمليًا. سيفتح Selenium نافذة جديدة عند تثبيت Firefox، ثم ينتقل إلى صفحة Discover عند استدعاء .get() عليها.

بعد ذلك، ابدأ باستخدام كائنات صفحتك بإنشاء مثيل لـ DiscoverPage. يمكنك الآن الوصول إلى جميع المسارات المتاحة عبر الانتقال إلى discover_tracklist.، وهو عنصر TrackListElement، وفهرسته في قائمة available_tracks.. اختر أول عنصر TrackElement واستدعِ الدالة .play() عليه. يمكنك مشاهدة نقرة متصفحك على زر التشغيل، وبدء تشغيل الموسيقى!

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

في مشغل موسيقى Bandcamp، يعني ذلك أنك حددت مكانًا مركزيًا لتشغيل أو إيقاف أو تحميل المزيد من المقطوعات الموسيقية. على سبيل المثال، إذا غيّر Bandcamp اسم فئة الحاوية التي تحتوي على المقطوعات الموسيقية، فما عليك سوى تحديث DiscoverPageLocator.DISCOVER_RESULTS في ملف locators.py. إذا تغيّر رمز HTML لعنصر المقطوعة الموسيقية، فاضبط TrackLocator في ملف elements.py.

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

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

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

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

إنشاء تطبيق مشغل الموسيقى

لديك نموذج كائن صفحة قوي للتفاعل مع صفحة “اكتشف” على Bandcamp. الآن، حان وقت تجميع الأجزاء النهائية في مشغل موسيقى نصي قابل للتشغيل. ترغب في الاستمرار باتباع أنماط تصميم قابلة للصيانة، لذا ستقسم هذا الجزء من المشروع إلى مساحة اسم منفصلة، ​​وتقسمه إلى وحدتين:

  1. سيحتوي ملف player.py على منطق تطبيق مشغل الموسيقى الخاص بك وسيستخدم كائنات الصفحة التي قمت بتعريفها مسبقًا.
  2. سيوفر tui.py حلقة سطر الأوامر والوظائف الأخرى التي تركز على عرض المعلومات.

لتجميع المنطق الذي كتبته لأتمتة الويب في مكان واحد، ستقدم أيضًا حزمتين من المستوى الأعلى: /web للكود الذي كتبته حتى الآن، و/app للكود الذي ستكتبه في هذا القسم.

أخيرًا، ستُنشئ ملفًا صغيرًا باسم main.py ليكون بمثابة نقطة انطلاق لمشروعك. أعد هيكلة مشروعك وفقًا لذلك:

bandcamp/
│
├── app/
│   │
│   ├── __init__.py
│   ├── player.py
│   └── tui.py
│
├── web/
│   │
│   ├── __init__.py
│   ├── base.py
│   ├── elements.py
│   ├── locators.py
│   └── pages.py
│
├── __init__.py
└── __main__.py

بعد إعادة هيكلة المشروع وإضافة الملفات الجديدة الفارغة في البداية، قد تحتاج إلى تحديث بعض عمليات الاستيراد لضمان عمل الكود بسلاسة. على وجه التحديد، ستحتاج إلى إعادة تسمية جميع عمليات الاستيراد التي كانت تستخدم bandcamp.module سابقًا إلى bandcamp.web.module في ملفي pages.py وelements.py.

لمزيد من الراحة، يمكنك أيضًا إعداد فئة بيانات تسميها Track في base.py لاستبدال القاموس الذي أعاده TrackElement مسبقًا:

from dataclasses import dataclass
from pprint import pformat

# ...

@dataclass
class Track:
    album: str
    artist: str
    genre: str
    url: str

    def __str__(self):
        return pformat(self)

# ...

بفضل هذه الإضافات إلى base.py، أنشأتَ فئة بيانات بسيطة تحتوي على جميع المعلومات التي تريد جمعها حول مسار. استخدام فئة بيانات بدلاً من قاموس في هذه الحالة يُسهّل الوصول إلى معلومات المسار وعرضها.

اتخذ خطوة جانبية إلى elements.py وقم بتحديث الكود لاستخدام فئة البيانات هذه بدلاً من ذلك:

# ...

from bandcamp.web.base import WebComponent, Track

# ...

class TrackElement(WebComponent):
    # ...

    def _get_track_info(self) -> Track:
        # ...

        return Track(
            album=self._parent.find_element(*TrackLocator.ALBUM).text,
            artist=self._parent.find_element(*TrackLocator.ARTIST).text,
            genre=genre,
            url=clean_url,
        )

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

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

إنشاء فئة مشغل الموسيقى

أولاً، يأتي Player، وهو فئة تُغلّف المنطق عالي المستوى لفتح المتصفح، وإنشاء كائن DiscoverPage، وتوفير دوال بسيطة مثل .play() و.pause(). لاحظ أنه يُشبه الكود الذي كتبته في الأقسام السابقة، وكذلك الكود الذي استخدمته عند اختبار بنية POM في REPL:

from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options

from bandcamp.web.pages import DiscoverPage

BANDCAMP_DISCOVER_URL = "https://bandcamp.com/discover/"

class Player:
    """Play tracks from Bandcamp's Discover page."""

    def __init__(self) -> None:
        self._driver = self._set_up_driver()
        self.page = DiscoverPage(self._driver)
        self.tracklist = self.page.discover_tracklist
        self._current_track = self.tracklist.available_tracks[0]

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        """Close the headless browser."""
        self._driver.quit()

    def play(self, track_number=None):
        """Play the first track, or one of the available numbered tracks."""
        if track_number:
            self._current_track = self.tracklist.available_tracks[track_number - 1]
        self._current_track.play()

    def pause(self):
        """Pause the current track."""
        self._current_track.pause()

    def _set_up_driver(self):
        """Create a headless browser pointing to Bandcamp."""
        options = Options()
        options.add_argument("--headless")
        browser = Firefox(options=options)
        browser.get(BANDCAMP_DISCOVER_URL)
        return browser

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

تتعامل فئة Player أيضًا مع منطق الإعداد والإزالة المهم:

  • الإعداد: عند إنشاء مُشغِّل، يُشغِّل مُتصفِّح فايرفوكس بدون واجهة رسومية، وينتقل إلى صفحة Discover على Bandcamp باستخدام ._set_up_driver(). بعد ذلك، يُنشئ بايثون كائن صفحة DiscoverPage، ويجلب جميع المسارات المتاحة عبر DiscoverPage.discover_tracklist، وأخيرًا يُعيِّن المسار الحالي إلى أول عنصر مُتاح.
  • إزالة: حدّد طريقتي .__enter__() و.__exit__() الخاصتين، اللتين تسمحان لك باستخدام Player في مدير السياق وضمان إغلاق المتصفح تلقائيًا. لا ثعالب زومبي تستهلك ذاكرة الوصول العشوائي (RAM) لجهاز الكمبيوتر الخاص بك!

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

بما أنك قمتَ بالفعل بالعمل الشاق في فئات POM، يُمكن لـ Player البقاء نظيفًا. بالإضافة إلى فتح وإغلاق المتصفح بدون واجهة رسومية، يُمكنك تأجيل جميع التفاعلات الأخرى من خلال كائن DiscoverPage.

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

تجميع واجهة مستخدم نصية

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

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

ابدأ بتحديد التفاعلات عالية المستوى في ()interact. تُعد تفاعلات المستخدم التي ستحددها مثالاً جيدًا لمطابقة الأنماط الهيكلية في بايثون:

from bandcamp.app.player import Player

MAX_TRACKS = 100  # Allows to load more tracks once.

def interact():
    """Control the player through user interactions."""
    with Player() as player:
        while True:
            print(
                "\nType: play [&lt;track number>] | pause | tracks | more | exit"
            )
            match input("> ").strip().lower().split():
                case ["play"]:
                    play(player)
                case ["play", track]:
                    try:
                        track_number = int(track)
                        play(player, track_number)
                    except ValueError:
                        print("Please provide a valid track number.")
                case ["pause"]:
                    pause(player)
                case ["tracks"]:
                    display_tracks(player)
                case ["more"] if len(
                    player.tracklist.available_tracks
                ) >= MAX_TRACKS:
                    print(
                        "Can't load more tracks. Pick one from the track list."
                    )
                case ["more"]:
                    player.tracklist.load_more()
                    display_tracks(player)
                case ["exit"]:
                    print("Exiting the player...")
                    break
                case _:
                    print("Unknown command. Try again.")

داخل ()interact، تستخدم Player كمدير سياق. تذكر أن هذا ممكن لأنك عرّفت .__enter__() و .__exit__() في تلك الفئة. استخدام Player كمدير سياق يضمن إغلاق Python للمتصفح بدون واجهة مستخدم عند الخروج من مدير السياق.

بعد ذلك، يمكنك إعداد تكرار غير محدد باستخدام while True، والذي يبدأ بطباعة الخيارات الموجودة للتفاعل مع مشغل الموسيقى.

يمكنك بعد ذلك استخدام match لالتقاط إدخال المستخدم الذي تقوم بتطهيره باستخدام .strip() و.lower()، والذي تقوم بتقسيمه إلى عناصر منفصلة لمراعاة إمكانية دخول المستخدمين إلى اللعب متبوعًا برقم المسار.

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

يقوم كودك أيضًا ببعض اختبارات السلامة، ويستخدم case _ كحل نهائي شامل لأي مُدخلات غير مرغوب فيها. تستمر الحلقة التفاعلية حتى يكتب المستخدم exit.

ستحتاج بعد ذلك إلى إعداد الدالتين المفقودتين ()play و()pause، اللتين تقومان بشكل أساسي بتسليم الوظيفة إلى مثيل المشغل:

# ...

def play(player, track_number=None):
    """Play a track and show info about the track."""
    try:
        player.play(track_number)
        print(player._current_track._get_track_info())
    except IndexError:
        print(
            "Please provide a valid track number. "
            "You can list available tracks with `tracks`."
        )

def pause(player):
    """Pause the current track."""
    player.pause()

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

أخيرًا، تحتاج إلى طريقة لعرض قائمة الأغاني المتاحة في واجهة المستخدم الرسومية (TUI). للقيام بذلك، عرّف دالة ()display_tracks لعرض جميع المسارات المكتشفة بشكل جدولي:

# ...

COLUMN_WIDTH = CW = 30

# ...

def display_tracks(player):
    """Display information about the currently playable tracks."""
    header = f"{'#':&lt;5} {'Album':&lt;{CW}} {'Artist':&lt;{CW}} {'Genre':&lt;{CW}}"
    print(header)
    print("-" * 80)
    for track_number, track_element in enumerate(
        player.tracklist.available_tracks, start=1
    ):
        track = track_element._get_track_info()
        album = _truncate(track.album, CW)
        artist = _truncate(track.artist, CW)
        genre = _truncate(track.genre, CW)
        print(
            f"{track_number:&lt;5} {album:&lt;{CW}} {artist:&lt;{CW}} {genre:&lt;{CW}}"
        )


def _truncate(text, width):
    """Truncate track information."""
    return text[: width - 3] + "..." if len(text) > width else text

في هذا الكود، يمكنك استخدام f-strings لإعداد رأس عمود في السطر 9 واستيفاء أجزاء معلومات المسار لكل مسار كبند سطر.

لاحظ كيف تستخدم CW كاختصار لثابت COLUMN_WIDTH لإعداد خلايا ذات أحجام متناسقة باستخدام لغة التنسيق المصغرة في بايثون. كما تستخدم الدالة المساعدة ._truncate() لاختصار المعلومات الأطول.

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

اجعل تطبيقك قابلاً للتنفيذ

أخيرًا، قم بتجميع البرنامج النصي الخاص بك مع ملف __main__.py الذي يستدعي دالة ()interact:

from bandcamp.app.tui import interact

def main():
    """Provide the main entry point for the app."""
    interact()

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

[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "bandcamp_player"
version = "0.1.0"
requires-python = ">=3.10"
description = "A web player for Bandcamp using Selenium"
dependencies = [
    "selenium",
]
[project.scripts]
discover = "bandcamp.__main__:main"

انقل هذا الملف بحيث يتواجد في نفس المجلد الذي يحتوي على حزمة bandcamp الخاصة بك:

./
│
├── bandcamp/
│
└── pyproject.toml

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

بعد إعداد pyproject.toml، يمكنك الآن تثبيت الحزمة محليًا وتشغيل مشغل الموسيقى باستخدام الأمر الذي قمت بتعريفه في [project.scripts]:

(venv) $ python -m pip install .
...
(venv) $ discover

إذا قمت بإعداد كل شيء بشكل صحيح، فسوف تتمكن من رؤية مشغل الموسيقى Bandcamp Discover الخاص بك والتفاعل معه:

Type: play [&lt;track number>] | pause | tracks | more | exit
>

اكتب “tracks” لعرض قائمة المسارات المتاحة. ثم اكتب “play 3” لبدء تشغيل المسار الثالث. اكتب “pause” للإيقاف المؤقت، أو “more” لتحميل مسارات إضافية إذا كنت ترغب في رؤية قائمة أكبر. وأخيرًا، للخروج، اكتب “exit“.

لكنك لستَ مضطرًا للخروج من المشغّل بعد. يمكنك البقاء لفترة واكتشاف موسيقى جديدة. هذا هو الهدف الأساسي من بناء مشغّل الموسيقى هذا!

الخطوات التالية

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

  • البحث حسب النوع: النقر تلقائيًا على مرشح نوع معين.
  • الاختبار: أضف اختبارات باستخدام pytest أو unittest للتحقق من صحة أحمال قائمة المسارات، أو للتأكد من عمل أمر التشغيل.
  • إضافة واجهة مستخدم نصية غنية: استخدم مكتبة واجهة مستخدم نصية أكثر تقدمًا، مثل Textual، لإضافة الألوان أو القوائم التفاعلية.

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

في هذا البرنامج التعليمي، تعلمتَ كيفية استخدام بايثون وسيلينيوم لأتمتة تفاعلات الويب وإنشاء مشغل موسيقى نصي عملي يتفاعل مع صفحة “اكتشف” على Bandcamp. استكشفتَ مفاهيم أساسية مثل التنقل عبر صفحات الويب، وتحديد مواقع العناصر والتفاعل معها، ومعالجة المحتوى الديناميكي، وهيكلة الكود باستخدام نموذج كائن الصفحة (POM) لضمان سهولة الصيانة والتوسع.

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

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

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


اكتشاف المزيد من بايثون العربي

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

Scroll to Top

اكتشاف المزيد من بايثون العربي

اشترك الآن للاستمرار في القراءة والحصول على حق الوصول إلى الأرشيف الكامل.

Continue reading