Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια

Ο συγγραφέας του άρθρου, τη μετάφραση του οποίου δημοσιεύουμε σήμερα, λέει ότι στόχος του είναι να μιλήσει για την ανάπτυξη ενός web scraper στην Python χρησιμοποιώντας το Selenium, το οποίο αναζητά τιμές αεροπορικών εισιτηρίων. Κατά την αναζήτηση εισιτηρίων, χρησιμοποιούνται ευέλικτες ημερομηνίες (+- 3 ημέρες σε σχέση με τις καθορισμένες ημερομηνίες). Το scraper αποθηκεύει τα αποτελέσματα αναζήτησης σε ένα αρχείο Excel και στέλνει στο άτομο που διεξήγαγε την αναζήτηση ένα email με μια περίληψη του τι βρήκε. Ο στόχος αυτού του έργου είναι να βοηθήσει τους ταξιδιώτες να βρουν τις καλύτερες προσφορές.

Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια

Εάν, ενώ κατανοείτε το υλικό, αισθάνεστε χαμένοι, ρίξτε μια ματιά αυτό άρθρο.

Τι ψάχνουμε;

Είστε ελεύθεροι να χρησιμοποιήσετε το σύστημα που περιγράφεται εδώ όπως θέλετε. Για παράδειγμα, το χρησιμοποίησα για να αναζητήσω εκδρομές το Σαββατοκύριακο και εισιτήρια για την πόλη μου. Εάν σκέφτεστε σοβαρά να βρείτε κερδοφόρα εισιτήρια, μπορείτε να εκτελέσετε το σενάριο στον διακομιστή (απλό διακομιστή, για 130 ρούβλια το μήνα, είναι αρκετά κατάλληλο για αυτό) και βεβαιωθείτε ότι τρέχει μία ή δύο φορές την ημέρα. Τα αποτελέσματα αναζήτησης θα σας σταλούν μέσω email. Επιπλέον, συνιστώ να ρυθμίσετε τα πάντα έτσι ώστε το σενάριο να αποθηκεύει ένα αρχείο Excel με αποτελέσματα αναζήτησης σε έναν φάκελο Dropbox, ο οποίος θα σας επιτρέπει να προβάλλετε τέτοια αρχεία από οπουδήποτε και ανά πάσα στιγμή.

Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια
Δεν έχω βρει ακόμη τιμολόγια με λάθη, αλλά νομίζω ότι είναι δυνατό

Κατά την αναζήτηση, όπως ήδη αναφέρθηκε, χρησιμοποιείται μια "ευέλικτη ημερομηνία"· το σενάριο βρίσκει προσφορές που βρίσκονται εντός τριών ημερών από τις δεδομένες ημερομηνίες. Αν και κατά την εκτέλεση του σεναρίου, αναζητά προσφορές μόνο προς μία κατεύθυνση, είναι εύκολο να το τροποποιήσετε ώστε να μπορεί να συλλέγει δεδομένα για πολλές κατευθύνσεις πτήσης. Με τη βοήθειά του, μπορείτε ακόμη και να αναζητήσετε λανθασμένα τιμολόγια· τέτοια ευρήματα μπορεί να είναι πολύ ενδιαφέροντα.

Γιατί χρειάζεστε μια άλλη ξύστρα ιστού;

Όταν ξεκίνησα για πρώτη φορά το web scraping, ειλικρινά δεν με ενδιέφερε ιδιαίτερα. Ήθελα να κάνω περισσότερα έργα στον τομέα της προγνωστικής μοντελοποίησης, της οικονομικής ανάλυσης και, ενδεχομένως, στον τομέα της ανάλυσης του συναισθηματικού χρωματισμού των κειμένων. Αλλά αποδείχθηκε ότι ήταν πολύ ενδιαφέρον να καταλάβουμε πώς να δημιουργήσουμε ένα πρόγραμμα που συλλέγει δεδομένα από ιστότοπους. Καθώς εμβαθύνω σε αυτό το θέμα, συνειδητοποίησα ότι το web scraping είναι η «μηχανή» του Διαδικτύου.

Μπορεί να νομίζετε ότι αυτή είναι μια πολύ τολμηρή δήλωση. Αλλά σκεφτείτε ότι η Google ξεκίνησε με ένα web scraper που δημιούργησε ο Larry Page χρησιμοποιώντας Java και Python. Τα ρομπότ της Google εξερευνούν το Διαδίκτυο, προσπαθώντας να παρέχουν στους χρήστες του τις καλύτερες απαντήσεις στις ερωτήσεις τους. Η απόξεση Ιστού έχει ατελείωτες χρήσεις και ακόμα κι αν σας ενδιαφέρει κάτι άλλο στην Επιστήμη των Δεδομένων, θα χρειαστείτε κάποιες δεξιότητες απόξεσης για να λάβετε τα δεδομένα που χρειάζεστε για να αναλύσετε.

Βρήκα μερικές από τις τεχνικές που χρησιμοποιούνται εδώ σε ένα υπέροχο το βιβλίο σχετικά με το web scraping, το οποίο απέκτησα πρόσφατα. Περιέχει πολλά απλά παραδείγματα και ιδέες για πρακτική εφαρμογή όσων μάθατε. Επιπλέον, υπάρχει ένα πολύ ενδιαφέρον κεφάλαιο για την παράκαμψη των ελέγχων reCaptcha. Αυτό ήταν είδηση ​​για μένα, αφού δεν ήξερα καν ότι υπήρχαν ειδικά εργαλεία και ακόμη και ολόκληρες υπηρεσίες για την επίλυση τέτοιων προβλημάτων.

Σου αρέσει να ταξιδεύεις?!

Στην απλή και μάλλον ακίνδυνη ερώτηση που τίθεται στον τίτλο αυτής της ενότητας, μπορείτε συχνά να ακούσετε μια θετική απάντηση, συνοδευόμενη από μερικές ιστορίες από τα ταξίδια του ατόμου στο οποίο ρωτήθηκε. Οι περισσότεροι από εμάς θα συμφωνούσαν ότι τα ταξίδια είναι ένας πολύ καλός τρόπος για να βυθιστείτε σε νέα πολιτιστικά περιβάλλοντα και να διευρύνετε τους ορίζοντές σας. Ωστόσο, αν ρωτήσετε κάποιον αν του αρέσει να ψάχνει για αεροπορικά εισιτήρια, είμαι σίγουρος ότι η απάντηση δεν θα είναι τόσο θετική. Στην πραγματικότητα, η Python έρχεται να μας βοηθήσει εδώ.

Το πρώτο καθήκον που πρέπει να λύσουμε στο δρόμο για τη δημιουργία ενός συστήματος αναζήτησης πληροφοριών για αεροπορικά εισιτήρια θα είναι η επιλογή μιας κατάλληλης πλατφόρμας από την οποία θα λαμβάνουμε πληροφορίες. Η επίλυση αυτού του προβλήματος δεν ήταν εύκολη για μένα, αλλά τελικά επέλεξα την υπηρεσία Kayak. Δοκίμασα τις υπηρεσίες των Momondo, Skyscanner, Expedia και μερικών άλλων, αλλά οι μηχανισμοί προστασίας ρομπότ σε αυτούς τους πόρους ήταν αδιαπέραστοι. Μετά από αρκετές προσπάθειες, κατά τις οποίες χρειάστηκε να αντιμετωπίσω φανάρια, διαβάσεις πεζών και ποδήλατα, προσπαθώντας να πείσω τα συστήματα ότι ήμουν άνθρωπος, αποφάσισα ότι το Kayak ήταν το καταλληλότερο για μένα, παρά το γεγονός ότι ακόμη και αν φορτωθούν πάρα πολλές σελίδες σε σύντομο χρονικό διάστημα και αρχίζουν και οι έλεγχοι. Κατάφερα να κάνω το bot να στέλνει αιτήματα στον ιστότοπο σε διαστήματα 4 έως 6 ωρών και όλα δούλεψαν μια χαρά. Από καιρό σε καιρό, προκύπτουν δυσκολίες όταν εργάζεστε με το Kayak, αλλά αν αρχίσουν να σας ενοχλούν με επιταγές, τότε πρέπει είτε να τις αντιμετωπίσετε χειροκίνητα και μετά να ξεκινήσετε το bot ή να περιμένετε μερικές ώρες και οι έλεγχοι θα σταματήσουν. Εάν είναι απαραίτητο, μπορείτε εύκολα να προσαρμόσετε τον κώδικα για άλλη πλατφόρμα και, αν το κάνετε, μπορείτε να τον αναφέρετε στα σχόλια.

Αν μόλις ξεκινήσατε με το web scraping και δεν ξέρετε γιατί ορισμένοι ιστότοποι δυσκολεύονται να το αντιμετωπίσουν, τότε πριν ξεκινήσετε το πρώτο σας έργο σε αυτόν τον τομέα, κάντε μια χάρη στον εαυτό σας και κάντε μια αναζήτηση στο Google για τις λέξεις "web scraping etiquette" . Τα πειράματά σας μπορεί να τελειώσουν νωρίτερα από ό,τι νομίζετε, εάν κάνετε αλόγιστη απόξεση ιστού.

Ξεκινώντας

Ακολουθεί μια γενική επισκόπηση του τι θα συμβεί στον κώδικα του web scraper:

  • Εισαγάγετε τις απαιτούμενες βιβλιοθήκες.
  • Άνοιγμα καρτέλας Google Chrome.
  • Καλέστε μια συνάρτηση που ξεκινά το bot, μεταβιβάζοντάς του τις πόλεις και τις ημερομηνίες που θα χρησιμοποιηθούν κατά την αναζήτηση εισιτηρίων.
  • Αυτή η συνάρτηση λαμβάνει τα πρώτα αποτελέσματα αναζήτησης, ταξινομημένα με βάση τα καλύτερα, και κάνει κλικ σε ένα κουμπί για να φορτώσει περισσότερα αποτελέσματα.
  • Μια άλλη συνάρτηση συλλέγει δεδομένα από ολόκληρη τη σελίδα και επιστρέφει ένα πλαίσιο δεδομένων.
  • Τα δύο προηγούμενα βήματα εκτελούνται χρησιμοποιώντας τύπους ταξινόμησης ανά τιμή εισιτηρίου (φθηνό) και κατά ταχύτητα πτήσης (γρηγορότερη).
  • Στο χρήστη του σεναρίου αποστέλλεται ένα μήνυμα ηλεκτρονικού ταχυδρομείου που περιέχει μια περίληψη των τιμών των εισιτηρίων (φθηνότερα εισιτήρια και μέση τιμή) και ένα πλαίσιο δεδομένων με πληροφορίες ταξινομημένες με βάση τους τρεις προαναφερθέντες δείκτες αποθηκεύεται ως αρχείο Excel.
  • Όλες οι παραπάνω ενέργειες εκτελούνται σε έναν κύκλο μετά από ένα καθορισμένο χρονικό διάστημα.

Θα πρέπει να σημειωθεί ότι κάθε έργο Selenium ξεκινά με ένα πρόγραμμα οδήγησης web. χρησιμοποιώ Chromedriver, εργάζομαι με το Google Chrome, αλλά υπάρχουν και άλλες επιλογές. Το 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 χρησιμοποιείται για να κάνει το bot να «κοιμηθεί» για έναν τυχαίο αριθμό δευτερολέπτων πριν ξεκινήσει μια νέα λειτουργία αναζήτησης. Συνήθως, κανένα bot δεν μπορεί να κάνει χωρίς αυτό. Εάν εκτελέσετε τον παραπάνω κώδικα, θα ανοίξει ένα παράθυρο του Chrome, το οποίο θα χρησιμοποιήσει το bot για να εργαστεί με ιστότοπους.

Ας κάνουμε ένα μικρό πείραμα και ας ανοίξουμε τον ιστότοπο kayak.com σε ξεχωριστό παράθυρο. Θα επιλέξουμε την πόλη από την οποία πρόκειται να πετάξουμε και την πόλη που θέλουμε να φτάσουμε, καθώς και τις ημερομηνίες πτήσης. Όταν επιλέγετε ημερομηνίες, βεβαιωθείτε ότι χρησιμοποιείται το εύρος των +-3 ημερών. Έγραψα τον κώδικα λαμβάνοντας υπόψη τι παράγει ο ιστότοπος ως απάντηση σε τέτοια αιτήματα. Εάν, για παράδειγμα, πρέπει να αναζητήσετε εισιτήρια μόνο για καθορισμένες ημερομηνίες, τότε υπάρχει μεγάλη πιθανότητα να χρειαστεί να τροποποιήσετε τον κωδικό του bot. Όταν μιλάω για τον κώδικα, δίνω τις κατάλληλες εξηγήσεις, αλλά αν νιώθετε σύγχυση, ενημερώστε με.

Τώρα κάντε κλικ στο κουμπί αναζήτησης και δείτε τον σύνδεσμο στη γραμμή διευθύνσεων. Θα πρέπει να είναι παρόμοιο με τον σύνδεσμο που χρησιμοποιώ στο παρακάτω παράδειγμα όπου δηλώνεται η μεταβλητή kayak, το οποίο αποθηκεύει τη διεύθυνση URL και χρησιμοποιείται η μέθοδος get πρόγραμμα οδήγησης web. Αφού κάνετε κλικ στο κουμπί αναζήτησης, τα αποτελέσματα πρέπει να εμφανιστούν στη σελίδα.

Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια
Όταν χρησιμοποίησα την εντολή get περισσότερες από δύο ή τρεις φορές μέσα σε λίγα λεπτά, μου ζητήθηκε να ολοκληρώσω την επαλήθευση χρησιμοποιώντας το reCaptcha. Μπορείτε να περάσετε αυτόν τον έλεγχο χειροκίνητα και να συνεχίσετε να πειραματίζεστε μέχρι το σύστημα να αποφασίσει να εκτελέσει έναν νέο έλεγχο. Όταν δοκίμασα το σενάριο, φαινόταν ότι η πρώτη περίοδος αναζήτησης κυλούσε πάντα ομαλά, οπότε αν θέλατε να πειραματιστείτε με τον κώδικα, θα έπρεπε μόνο να ελέγχετε περιοδικά με μη αυτόματο τρόπο και να αφήνετε τον κώδικα να εκτελείται, χρησιμοποιώντας μεγάλα διαστήματα μεταξύ των περιόδων αναζήτησης. Και, αν το καλοσκεφτείτε, είναι απίθανο ένα άτομο να χρειάζεται πληροφορίες σχετικά με τις τιμές των εισιτηρίων που λαμβάνονται σε διαστήματα 10 λεπτών μεταξύ των εργασιών αναζήτησης.

Εργασία με μια σελίδα χρησιμοποιώντας XPath

Έτσι, ανοίξαμε ένα παράθυρο και φορτώσαμε τον ιστότοπο. Για να λάβουμε τιμολόγηση και άλλες πληροφορίες, πρέπει να χρησιμοποιήσουμε τεχνολογία XPath ή επιλογείς CSS. Αποφάσισα να παραμείνω στο XPath και δεν ένιωσα την ανάγκη να χρησιμοποιήσω επιλογείς CSS, αλλά είναι πολύ πιθανό να λειτουργήσω με αυτόν τον τρόπο. Η πλοήγηση σε μια σελίδα χρησιμοποιώντας το XPath μπορεί να είναι δύσκολη, ακόμα κι αν χρησιμοποιείτε τις τεχνικές που περιέγραψα αυτό άρθρο, το οποίο περιλάμβανε την αντιγραφή των αντίστοιχων αναγνωριστικών από τον κώδικα της σελίδας, συνειδητοποίησα ότι αυτός στην πραγματικότητα δεν είναι ο βέλτιστος τρόπος πρόσβασης στα απαραίτητα στοιχεία. Παρεμπιπτόντως, στο αυτό Το βιβλίο παρέχει μια εξαιρετική περιγραφή των βασικών στοιχείων της εργασίας με σελίδες χρησιμοποιώντας επιλογείς XPath και CSS. Αυτή είναι η αντίστοιχη μέθοδος προγράμματος οδήγησης web.

Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια
Λοιπόν, ας συνεχίσουμε να εργαζόμαστε για το bot. Ας χρησιμοποιήσουμε τις δυνατότητες του προγράμματος για να επιλέξουμε τα φθηνότερα εισιτήρια. Στην παρακάτω εικόνα, ο κωδικός επιλογής XPath επισημαίνεται με κόκκινο χρώμα. Για να δείτε τον κώδικα, πρέπει να κάνετε δεξί κλικ στο στοιχείο της σελίδας που σας ενδιαφέρει και να επιλέξετε την εντολή Inspect από το μενού που εμφανίζεται. Αυτή η εντολή μπορεί να κληθεί για διαφορετικά στοιχεία σελίδας, ο κώδικας των οποίων θα εμφανίζεται και θα επισημαίνεται στο πρόγραμμα προβολής κώδικα.

Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια
Προβολή κώδικα σελίδας

Για να βρείτε επιβεβαίωση του συλλογισμού μου σχετικά με τα μειονεκτήματα της αντιγραφής επιλογέων από κώδικα, δώστε προσοχή στα ακόλουθα χαρακτηριστικά.

Αυτό λαμβάνετε όταν αντιγράφετε τον κωδικό:

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

Για να αντιγράψετε κάτι τέτοιο, πρέπει να κάνετε δεξί κλικ στην ενότητα του κώδικα που σας ενδιαφέρει και να επιλέξετε την εντολή Copy > Copy XPath από το μενού που εμφανίζεται.

Να τι χρησιμοποίησα για να ορίσω το κουμπί Φθηνότερο:

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

Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια
Εντολή αντιγραφής > Αντιγραφή XPath

Είναι προφανές ότι η δεύτερη επιλογή φαίνεται πολύ πιο απλή. Όταν χρησιμοποιείται, αναζητά ένα στοιχείο α που έχει το χαρακτηριστικό 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).

Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια
Εμφανίζονται οι τρεις πρώτες γραμμές και μπορούμε να δούμε καθαρά όλα όσα χρειαζόμαστε. Ωστόσο, έχουμε πιο ενδιαφέροντες τρόπους απόκτησης πληροφοριών. Πρέπει να πάρουμε δεδομένα από κάθε στοιχείο ξεχωριστά.

Φτάνω στη δουλειά!

Ο ευκολότερος τρόπος για να γράψετε μια συνάρτηση είναι να φορτώσετε επιπλέον αποτελέσματα, οπότε από εκεί θα ξεκινήσουμε. Θα ήθελα να μεγιστοποιήσω τον αριθμό των πτήσεων για τις οποίες λαμβάνει πληροφορίες το πρόγραμμα, χωρίς να εγείρω υποψίες στην υπηρεσία που οδηγεί σε επιθεώρηση, επομένως κάνω κλικ στο κουμπί Φόρτωση περισσότερων αποτελεσμάτων μία φορά κάθε φορά που εμφανίζεται η σελίδα. Σε αυτόν τον κώδικα, θα πρέπει να δώσετε προσοχή στο μπλοκ 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 - στο δεύτερο. Ας προχωρήσουμε στην επόμενη λειτουργία.

Μηχανισμοί υποστήριξης

Τώρα έχουμε μια συνάρτηση που μας επιτρέπει να φορτώνουμε πρόσθετα αποτελέσματα αναζήτησης και μια συνάρτηση για την επεξεργασία αυτών των αποτελεσμάτων. Αυτό το άρθρο θα μπορούσε να έχει τελειώσει εδώ, καθώς αυτές οι δύο λειτουργίες παρέχουν όλα όσα χρειάζεστε για να ξύσετε σελίδες που μπορείτε να ανοίξετε μόνοι σας. Αλλά δεν έχουμε ακόμη εξετάσει ορισμένους από τους βοηθητικούς μηχανισμούς που συζητήθηκαν παραπάνω. Για παράδειγμα, αυτός είναι ο κωδικός για την αποστολή email και κάποια άλλα πράγματα. Όλα αυτά βρίσκονται στη συνάρτηση start_kayak, που θα εξετάσουμε τώρα.

Για να λειτουργήσει αυτή η λειτουργία, χρειάζεστε πληροφορίες για πόλεις και ημερομηνίες. Χρησιμοποιώντας αυτές τις πληροφορίες, σχηματίζει έναν σύνδεσμο σε μια μεταβλητή kayak, το οποίο χρησιμοποιείται για να σας μεταφέρει σε μια σελίδα που θα περιέχει αποτελέσματα αναζήτησης ταξινομημένα με βάση την καλύτερη αντιστοιχία τους στο ερώτημα. Μετά την πρώτη συνεδρία απόξεσης, θα εργαστούμε με τις τιμές στον πίνακα στο επάνω μέρος της σελίδας. Δηλαδή, θα βρούμε την ελάχιστη τιμή εισιτηρίου και τη μέση τιμή. Όλα αυτά, μαζί με την πρόβλεψη που εκδίδει ο ιστότοπος, θα σταλούν με email. Στη σελίδα, ο αντίστοιχος πίνακας πρέπει να βρίσκεται στην επάνω αριστερή γωνία. Η εργασία με αυτόν τον πίνακα, παρεμπιπτόντως, μπορεί να προκαλέσει σφάλμα κατά την αναζήτηση χρησιμοποιώντας ακριβείς ημερομηνίες, καθώς σε αυτήν την περίπτωση ο πίνακας δεν εμφανίζεται στη σελίδα.

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, αυτό το σύστημα email είναι αρκετά δημοφιλές, αλλά υπάρχουν πολλές πιθανές επιλογές. Εάν χρησιμοποιείτε λογαριασμό 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.....')

Έτσι μοιάζει μια δοκιμαστική εκτέλεση του σεναρίου.
Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια
Δοκιμαστική εκτέλεση του σεναρίου

Αποτελέσματα της

Αν τα καταφέρατε μέχρι εδώ, συγχαρητήρια! Τώρα έχετε ένα λειτουργικό ξύστρα ιστού, αν και μπορώ ήδη να δω πολλούς τρόπους για να το βελτιώσω. Για παράδειγμα, μπορεί να ενσωματωθεί στο Twilio έτσι ώστε να στέλνει μηνύματα κειμένου αντί για email. Μπορείτε να χρησιμοποιήσετε ένα VPN ή κάτι άλλο για να λαμβάνετε ταυτόχρονα αποτελέσματα από πολλούς διακομιστές. Επίσης, παρουσιάζεται περιοδικά ένα πρόβλημα με τον έλεγχο του χρήστη του ιστότοπου για να διαπιστωθεί εάν είναι άτομο, αλλά αυτό το πρόβλημα μπορεί επίσης να λυθεί. Σε κάθε περίπτωση, τώρα έχετε μια βάση που μπορείτε να επεκτείνετε αν θέλετε. Για παράδειγμα, βεβαιωθείτε ότι ένα αρχείο Excel αποστέλλεται στον χρήστη ως συνημμένο σε ένα μήνυμα ηλεκτρονικού ταχυδρομείου.

Python - ένας βοηθός στην εύρεση φθηνών αεροπορικών εισιτηρίων για όσους αγαπούν τα ταξίδια

Μόνο εγγεγραμμένοι χρήστες μπορούν να συμμετάσχουν στην έρευνα. Συνδεθείτε, Σας παρακαλούμε.

Χρησιμοποιείτε τεχνολογίες web scraping;

  • Ναί

  • Όχι

Ψήφισαν 8 χρήστες. 1 χρήστης απείχε.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο