بايثون - مساعد في العثور على تذاكر طيران رخيصة الثمن لأولئك الذين يحبون السفر

يقول كاتب المقال الذي ننشر ترجمته اليوم، إن هدفه هو الحديث عن تطوير أداة مسح الويب في بايثون باستخدام السيلينيوم، والتي تبحث عن أسعار تذاكر الطيران. عند البحث عن التذاكر، يتم استخدام تواريخ مرنة (+- 3 أيام بالنسبة للتواريخ المحددة). تقوم أداة الكشط بحفظ نتائج البحث في ملف Excel وترسل إلى الشخص الذي أجرى البحث بريدًا إلكترونيًا يتضمن ملخصًا لما وجده. الهدف من هذا المشروع هو مساعدة المسافرين في العثور على أفضل العروض.

بايثون - مساعد في العثور على تذاكر طيران رخيصة الثمن لأولئك الذين يحبون السفر

إذا شعرت بالضياع أثناء فهم المادة، فقم بإلقاء نظرة عليها هذا شرط.

ما الذي تبحث عنه؟

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

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

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

لماذا تحتاج إلى مكشطة ويب أخرى؟

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

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

لقد وجدت بعض التقنيات المستخدمة هنا بشكل رائع книге حول تجريف الويب، الذي حصلت عليه مؤخرًا. يحتوي على العديد من الأمثلة والأفكار البسيطة للتطبيق العملي لما تعلمته. بالإضافة إلى ذلك، هناك فصل مثير للاهتمام حول تجاوز عمليات التحقق من reCaptcha. لقد جاء هذا بمثابة خبر بالنسبة لي، لأنني لم أكن أعلم حتى أن هناك أدوات خاصة وحتى خدمات كاملة لحل مثل هذه المشكلات.

أتحب الترحال؟!

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

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

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

الشروع في العمل

فيما يلي نظرة عامة على ما سيحدث في كود استخراج الويب الخاص بنا:

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

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

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

هذا هو الكود الذي تحدثنا عنه أعلاه.

from time import sleep, strftime
from random import randint
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import smtplib
from email.mime.multipart import MIMEMultipart

# Используйте тут ваш путь к chromedriver!
chromedriver_path = 'C:/{YOUR PATH HERE}/chromedriver_win32/chromedriver.exe'

driver = webdriver.Chrome(executable_path=chromedriver_path) # Этой командой открывается окно Chrome
sleep(2)

في بداية الكود، يمكنك رؤية أوامر استيراد الحزمة المستخدمة خلال مشروعنا. لذا، randint يستخدم لجعل الروبوت "ينام" لعدد عشوائي من الثواني قبل بدء عملية بحث جديدة. عادة، لا يستطيع أي روبوت الاستغناء عن هذا. إذا قمت بتشغيل الكود أعلاه، فسيتم فتح نافذة Chrome، والتي سيستخدمها الروبوت للعمل مع المواقع.

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

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

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

العمل مع الصفحة باستخدام XPath

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

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

بايثون - مساعد في العثور على تذاكر طيران رخيصة الثمن لأولئك الذين يحبون السفر
عرض كود الصفحة

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

وهذا ما تحصل عليه عند نسخ الكود:

//*[@id="wtKI-price_aTab"]/div[1]/div/div/div[1]/div/span/span

لنسخ شيء مثل هذا، تحتاج إلى النقر بزر الماوس الأيمن على قسم التعليمات البرمجية الذي تهتم به وتحديد الأمر Copy > Copy XPath من القائمة التي تظهر.

إليك ما استخدمته لتحديد الزر الأرخص:

cheap_results = ‘//a[@data-code = "price"]’

بايثون - مساعد في العثور على تذاكر طيران رخيصة الثمن لأولئك الذين يحبون السفر
أمر النسخ> نسخ XPath

من الواضح تمامًا أن الخيار الثاني يبدو أبسط بكثير. عند استخدامه، فإنه يبحث عن العنصر a الذي له السمة data-code، متساوي price. عند استخدام الخيار الأول يتم البحث عن العنصر id وهو يساوي wtKI-price_aTab، ويبدو مسار XPath للعنصر /div[1]/div/div/div[1]/div/span/span. سيؤدي استعلام XPath مثل هذا لصفحة ما إلى تنفيذ المهمة، ولكن مرة واحدة فقط. أستطيع أن أقول ذلك الآن id سيتم تغييره في المرة التالية التي يتم فيها تحميل الصفحة. تسلسل الأحرف wtKI يتغير ديناميكيًا في كل مرة يتم فيها تحميل الصفحة، وبالتالي فإن الكود الذي يستخدمها سيكون عديم الفائدة بعد إعادة تحميل الصفحة التالية. لذا خذ بعض الوقت لفهم XPath. هذه المعرفة سوف تخدمك بشكل جيد.

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

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

تجدر الإشارة إلى أنه إذا فهمت ما ورد أعلاه، فيجب أن تفهم بسهولة معظم التعليمات البرمجية التي سنقوم بتحليلها. أثناء تشغيل هذا الكود، يمكننا الوصول إلى ما نحتاجه (في الواقع، العنصر الذي يتم تغليف النتيجة فيه) باستخدام نوع ما من آلية تحديد المسار (XPath). يتم ذلك من أجل الحصول على نص العنصر ووضعه في كائن يمكن قراءة البيانات منه (تم استخدامه لأول مرة flight_containers، ثم - flights_list).

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

اذهب للعمل!

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

# Загрузка большего количества результатов для того, чтобы максимизировать объём собираемых данных
def load_more():
    try:
        more_results = '//a[@class = "moreButton"]'
        driver.find_element_by_xpath(more_results).click()
        # Вывод этих заметок в ходе работы программы помогает мне быстро выяснить то, чем она занята
        print('sleeping.....')
        sleep(randint(45,60))
    except:
        pass

الآن، بعد تحليل طويل لهذه الوظيفة (في بعض الأحيان يمكنني أن أبتعد)، نحن مستعدون للإعلان عن وظيفة من شأنها أن تتخلص من الصفحة.

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

def page_scrape():
    """This function takes care of the scraping part"""
    
    xp_sections = '//*[@class="section duration"]'
    sections = driver.find_elements_by_xpath(xp_sections)
    sections_list = [value.text for value in sections]
    section_a_list = sections_list[::2] # так мы разделяем информацию о двух полётах
    section_b_list = sections_list[1::2]
    
    # Если вы наткнулись на reCaptcha, вам может понадобиться что-то предпринять.
    # О том, что что-то пошло не так, вы узнаете исходя из того, что вышеприведённые списки пусты
    # это выражение if позволяет завершить работу программы или сделать ещё что-нибудь
    # тут можно приостановить работу, что позволит вам пройти проверку и продолжить скрапинг
    # я использую тут SystemExit так как хочу протестировать всё с самого начала
    if section_a_list == []:
        raise SystemExit
    
    # Я буду использовать букву A для уходящих рейсов и B для прибывающих
    a_duration = []
    a_section_names = []
    for n in section_a_list:
        # Получаем время
        a_section_names.append(''.join(n.split()[2:5]))
        a_duration.append(''.join(n.split()[0:2]))
    b_duration = []
    b_section_names = []
    for n in section_b_list:
        # Получаем время
        b_section_names.append(''.join(n.split()[2:5]))
        b_duration.append(''.join(n.split()[0:2]))

    xp_dates = '//div[@class="section date"]'
    dates = driver.find_elements_by_xpath(xp_dates)
    dates_list = [value.text for value in dates]
    a_date_list = dates_list[::2]
    b_date_list = dates_list[1::2]
    # Получаем день недели
    a_day = [value.split()[0] for value in a_date_list]
    a_weekday = [value.split()[1] for value in a_date_list]
    b_day = [value.split()[0] for value in b_date_list]
    b_weekday = [value.split()[1] for value in b_date_list]
    
    # Получаем цены
    xp_prices = '//a[@class="booking-link"]/span[@class="price option-text"]'
    prices = driver.find_elements_by_xpath(xp_prices)
    prices_list = [price.text.replace('$','') for price in prices if price.text != '']
    prices_list = list(map(int, prices_list))

    # stops - это большой список, в котором первый фрагмент пути находится по чётному индексу, а второй - по нечётному
    xp_stops = '//div[@class="section stops"]/div[1]'
    stops = driver.find_elements_by_xpath(xp_stops)
    stops_list = [stop.text[0].replace('n','0') for stop in stops]
    a_stop_list = stops_list[::2]
    b_stop_list = stops_list[1::2]

    xp_stops_cities = '//div[@class="section stops"]/div[2]'
    stops_cities = driver.find_elements_by_xpath(xp_stops_cities)
    stops_cities_list = [stop.text for stop in stops_cities]
    a_stop_name_list = stops_cities_list[::2]
    b_stop_name_list = stops_cities_list[1::2]
    
    # сведения о компании-перевозчике, время отправления и прибытия для обоих рейсов
    xp_schedule = '//div[@class="section times"]'
    schedules = driver.find_elements_by_xpath(xp_schedule)
    hours_list = []
    carrier_list = []
    for schedule in schedules:
        hours_list.append(schedule.text.split('n')[0])
        carrier_list.append(schedule.text.split('n')[1])
    # разделяем сведения о времени и о перевозчиках между рейсами a и b
    a_hours = hours_list[::2]
    a_carrier = carrier_list[1::2]
    b_hours = hours_list[::2]
    b_carrier = carrier_list[1::2]

    
    cols = (['Out Day', 'Out Time', 'Out Weekday', 'Out Airline', 'Out Cities', 'Out Duration', 'Out Stops', 'Out Stop Cities',
            'Return Day', 'Return Time', 'Return Weekday', 'Return Airline', 'Return Cities', 'Return Duration', 'Return Stops', 'Return Stop Cities',
            'Price'])

    flights_df = pd.DataFrame({'Out Day': a_day,
                               'Out Weekday': a_weekday,
                               'Out Duration': a_duration,
                               'Out Cities': a_section_names,
                               'Return Day': b_day,
                               'Return Weekday': b_weekday,
                               'Return Duration': b_duration,
                               'Return Cities': b_section_names,
                               'Out Stops': a_stop_list,
                               'Out Stop Cities': a_stop_name_list,
                               'Return Stops': b_stop_list,
                               'Return Stop Cities': b_stop_name_list,
                               'Out Time': a_hours,
                               'Out Airline': a_carrier,
                               'Return Time': b_hours,
                               'Return Airline': b_carrier,                           
                               'Price': prices_list})[cols]
    
    flights_df['timestamp'] = strftime("%Y%m%d-%H%M") # время сбора данных
    return flights_df

حاولت تسمية المتغيرات حتى يكون الكود مفهومًا. تذكر أن المتغيرات تبدأ بـ a تنتمي إلى المرحلة الأولى من المسار، و b - إلى الثانية. دعنا ننتقل إلى الوظيفة التالية.

آليات الدعم

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

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

def start_kayak(city_from, city_to, date_start, date_end):
    """City codes - it's the IATA codes!
    Date format -  YYYY-MM-DD"""
    
    kayak = ('https://www.kayak.com/flights/' + city_from + '-' + city_to +
             '/' + date_start + '-flexible/' + date_end + '-flexible?sort=bestflight_a')
    driver.get(kayak)
    sleep(randint(8,10))
    
    # иногда появляется всплывающее окно, для проверки на это и его закрытия можно воспользоваться блоком try
    try:
        xp_popup_close = '//button[contains(@id,"dialog-close") and contains(@class,"Button-No-Standard-Style close ")]'
        driver.find_elements_by_xpath(xp_popup_close)[5].click()
    except Exception as e:
        pass
    sleep(randint(60,95))
    print('loading more.....')
    
#     load_more()
    
    print('starting first scrape.....')
    df_flights_best = page_scrape()
    df_flights_best['sort'] = 'best'
    sleep(randint(60,80))
    
    # Возьмём самую низкую цену из таблицы, расположенной в верхней части страницы
    matrix = driver.find_elements_by_xpath('//*[contains(@id,"FlexMatrixCell")]')
    matrix_prices = [price.text.replace('$','') for price in matrix]
    matrix_prices = list(map(int, matrix_prices))
    matrix_min = min(matrix_prices)
    matrix_avg = sum(matrix_prices)/len(matrix_prices)
    
    print('switching to cheapest results.....')
    cheap_results = '//a[@data-code = "price"]'
    driver.find_element_by_xpath(cheap_results).click()
    sleep(randint(60,90))
    print('loading more.....')
    
#     load_more()
    
    print('starting second scrape.....')
    df_flights_cheap = page_scrape()
    df_flights_cheap['sort'] = 'cheap'
    sleep(randint(60,80))
    
    print('switching to quickest results.....')
    quick_results = '//a[@data-code = "duration"]'
    driver.find_element_by_xpath(quick_results).click()  
    sleep(randint(60,90))
    print('loading more.....')
    
#     load_more()
    
    print('starting third scrape.....')
    df_flights_fast = page_scrape()
    df_flights_fast['sort'] = 'fast'
    sleep(randint(60,80))
    
    # Сохранение нового фрейма в Excel-файл, имя которого отражает города и даты
    final_df = df_flights_cheap.append(df_flights_best).append(df_flights_fast)
    final_df.to_excel('search_backups//{}_flights_{}-{}_from_{}_to_{}.xlsx'.format(strftime("%Y%m%d-%H%M"),
                                                                                   city_from, city_to, 
                                                                                   date_start, date_end), index=False)
    print('saved df.....')
    
    # Можно следить за тем, как прогноз, выдаваемый сайтом, соотносится с реальностью
    xp_loading = '//div[contains(@id,"advice")]'
    loading = driver.find_element_by_xpath(xp_loading).text
    xp_prediction = '//span[@class="info-text"]'
    prediction = driver.find_element_by_xpath(xp_prediction).text
    print(loading+'n'+prediction)
    
    # иногда в переменной loading оказывается эта строка, которая, позже, вызывает проблемы с отправкой письма
    # если это прозошло - меняем её на "Not Sure"
    weird = '¯_(ツ)_/¯'
    if loading == weird:
        loading = 'Not sure'
    
    username = '[email protected]'
    password = 'YOUR PASSWORD'

    server = smtplib.SMTP('smtp.outlook.com', 587)
    server.ehlo()
    server.starttls()
    server.login(username, password)
    msg = ('Subject: Flight Scrapernn
Cheapest Flight: {}nAverage Price: {}nnRecommendation: {}nnEnd of message'.format(matrix_min, matrix_avg, (loading+'n'+prediction)))
    message = MIMEMultipart()
    message['From'] = '[email protected]'
    message['to'] = '[email protected]'
    server.sendmail('[email protected]', '[email protected]', msg)
    print('sent email.....')

لقد اختبرت هذا البرنامج النصي باستخدام حساب Outlook (hotmail.com). لم أختبره للعمل بشكل صحيح مع حساب Gmail، نظام البريد الإلكتروني هذا يحظى بشعبية كبيرة، ولكن هناك العديد من الخيارات الممكنة. إذا كنت تستخدم حساب Hotmail، لكي يعمل كل شيء، فأنت بحاجة فقط إلى إدخال بياناتك في الكود.

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

نظام جاهز

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

city_from = input('From which city? ')
city_to = input('Where to? ')
date_start = input('Search around which departure date? Please use YYYY-MM-DD format only ')
date_end = input('Return when? Please use YYYY-MM-DD format only ')

# city_from = 'LIS'
# city_to = 'SIN'
# date_start = '2019-08-21'
# date_end = '2019-09-07'

for n in range(0,5):
    start_kayak(city_from, city_to, date_start, date_end)
    print('iteration {} was complete @ {}'.format(n, strftime("%Y%m%d-%H%M")))
    
    # Ждём 4 часа
    sleep(60*60*4)
    print('sleep finished.....')

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

نتائج

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

بايثون - مساعد في العثور على تذاكر طيران رخيصة الثمن لأولئك الذين يحبون السفر

يمكن للمستخدمين المسجلين فقط المشاركة في الاستطلاع. تسجيل الدخول، من فضلك.

هل تستخدم تقنيات تجريف الويب؟

  • نعم

  • لا

صوّت 8 مستخدمًا. امتنع مستخدم واحد عن التصويت.

المصدر: www.habr.com

إضافة تعليق