Hướng dẫn quét web: Sử dụng Python để tìm chuyến bay giá rẻ!


Phan Khánh Vân
10 tháng trước
Hữu ích 6 Chia sẻ Viết bình luận 0
Đã xem 1572

Giới thiệu

Trong hướng dẫn này, tôi sẽ chỉ cho bạn cách sử dụng Python để tự động lướt một trang web như Expedia trên cơ sở hàng giờ để tìm kiếm các chuyến bay và gửi cho bạn tốc độ bay tốt nhất cho một lộ trình cụ thể mà bạn muốn mỗi giờ đến email của bạn.

Kết quả cuối cùng là email tốt đẹp này:

Chúng tôi sẽ làm việc như sau:

  1. Kết nối Python với trình duyệt web của chúng tôi và truy cập trang web (Expedia trong ví dụ của chúng tôi ở đây).
  2. Chọn loại vé dựa trên sở thích của chúng tôi (khứ hồi, một chiều, v.v.).
  3. Chọn nước khởi hành.
  4. Chọn quốc gia đến (nếu khứ hồi).
  5. Chọn ngày khởi hành và ngày trở về.
  6. Tổng hợp tất cả các chuyến bay có sẵn trong một định dạng có cấu trúc (dành cho những người thích thực hiện một số phân tích dữ liệu khám phá!).
  7. Kết nối với email của bạn.
  8. Gửi tỷ lệ tốt nhất cho giờ hiện tại.

Băt đâu nao!

Thư viện nhập khẩu

Hãy tiếp tục và nhập thư viện của chúng tôi:

Selenium (để truy cập các trang web và thử nghiệm tự động hóa):

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

Gấu trúc (chúng tôi chủ yếu sẽ chỉ sử dụng gấu trúc để cấu trúc dữ liệu của chúng tôi):

import pandas as pd

Thời gian và ngày giờ (để sử dụng độ trễ và trả về thời gian hiện tại, chúng ta sẽ thấy lý do tại sao sau này):

import time
import datetime

Chúng tôi cần những người để kết nối với email của chúng tôi và gửi tin nhắn của chúng tôi:

import smtplib
from email.mime.multipart import MIMEMultipart

Lưu ý: Tôi sẽ không đi sâu vào việc quét web bằng cách sử dụng selenium, nhưng nếu bạn muốn có một hướng dẫn chi tiết hơn về việc cạo chung, hãy kiểm tra các hướng dẫn trước đây của tôi để tìm cách sử dụng Selenium và quét web nói chung Phần 1 và  Phần 2 .

Hãy mã hóa

Kết nối với trình duyệt web

browser = webdriver.Chrome(executable_path='/chromedriver')

Điều này sẽ mở một trình duyệt trống cho bạn biết rằng trình duyệt này đang được kiểm soát bởi phần mềm kiểm tra tự động như vậy:

Chọn vé

Tiếp theo, tôi sẽ nhanh chóng truy cập Expedia để kiểm tra giao diện và các tùy chọn có sẵn để lựa chọn.

Tôi nhấp chuột phải + kiểm tra loại vé (khứ hồi, một chiều, v.v.) để xem các thẻ liên quan đến nó.

Như chúng ta có thể thấy bên dưới, nó có thẻ 'nhãn' với 'id = chuyến bay-type-roundtrip-nhãn-hp-chuyến bay' .

Theo đó, tôi sẽ sử dụng chúng để lưu trữ các thẻ và id cho ba loại vé khác nhau như sau:

#Setting ticket types paths
return_ticket = "//label[@id='flight-type-roundtrip-label-hp-flight']"
one_way_ticket = "//label[@id='flight-type-one-way-label-hp-flight']"
multi_ticket = "//label[@id='flight-type-multi-dest-label-hp-flight']"

Sau đó, tôi xác định một chức năng để chọn một loại vé:

def ticket_chooser(ticket):

    try:
        ticket_type = browser.find_element_by_xpath(ticket)
        ticket_type.click()
    except Exception as e:
        pass

Trình tự trên là trình tự tương tự tôi sẽ sử dụng cho phần còn lại của mã (tìm kiếm thẻ và id hoặc các thuộc tính khác và xác định hàm để đưa ra lựa chọn trên trang web).

Chọn các quốc gia khởi hành và đến

Dưới đây tôi xác định một chức năng để chọn quốc gia khởi hành.

def dep_country_chooser(dep_country):
    fly_from = browser.find_element_by_xpath("//input[@id='flight-origin-hp-flight']")
    time.sleep(1)
    fly_from.clear()
    time.sleep(1.5)
    fly_from.send_keys('  ' + dep_country)
    time.sleep(1.5)
    first_item = browser.find_element_by_xpath("//a[@id='aria-option-0']")
    time.sleep(1.5)
    first_item.click()

Tôi làm theo logic dưới đây:

  1. Tìm phần tử bằng cách sử dụng thẻ và thuộc tính của nó.
  2. Xóa bất kỳ giá trị được viết trong lĩnh vực quốc gia.
  3. Nhập vào quốc gia tôi muốn (sẽ được chuyển vào hàm) bằng cách sử dụng .sendkeys.
  4. Chọn lựa chọn đầu tiên xuất hiện từ menu thả xuống (cũng sử dụng thẻ và id của nó có thể được tìm thấy bằng cách nhấp chuột phải + kiểm tra phần tử khi menu thả xuống xuất hiện).
  5. Nhấp vào lựa chọn đầu tiên này.

Lưu ý rằng tôi đang sử dụng  time.sleep giữa các bước để tạo cơ hội cho các thành phần của trang cập nhật / tải giữa các bước. Không time.sleep,  đôi khi tập lệnh của chúng tôi hoạt động nhanh hơn tải trang và do đó cố gắng truy cập các phần tử chưa tải nhưng khiến mã của chúng tôi bị hỏng.

Chúng ta hãy làm tương tự cho đất nước đến.

def arrival_country_chooser(arrival_country):
    fly_to = browser.find_element_by_xpath("//input[@id='flight-destination-hp-flight']")
    time.sleep(1)
    fly_to.clear()
    time.sleep(1.5)
    fly_to.send_keys('  ' + arrival_country)
    time.sleep(1.5)
    first_item = browser.find_element_by_xpath("//a[@id='aria-option-0']")
    time.sleep(1.5)
    first_item.click()

Chọn ngày khởi hành và ngày trở về

Ngày khởi hành:

def dep_date_chooser(month, day, year):

    dep_date_button = browser.find_element_by_xpath("//input[@id='flight-departing-hp-flight']")
    dep_date_button.clear()
    dep_date_button.send_keys(month + '/' + day + '/' + year)

Rất thẳng về phía trước:

  1. Tìm phần tử trên trang web như trước.
  2. Xóa bất cứ điều gì đã được viết trước đó.
  3. Điền vào phần tử với tháng, ngày và năm được nhập vào hàm dưới dạng đối số + dấu gạch chéo cho định dạng ngày.

Ngày trở lại:

def return_date_chooser(month, day, year):
    return_date_button = browser.find_element_by_xpath("//input[@id='flight-returning-hp-flight']")

    for i in range(11):
        return_date_button.send_keys(Keys.BACKSPACE)
    return_date_button.send_keys(month + '/' + day + '/' + year)

Đối với ngày trả lại, việc xóa bất kỳ nội dung nào được viết không hoạt động vì một số lý do (có thể do trang này có tính năng tự động điền không cho phép tôi ghi đè lên .clear())

Cách tôi làm việc xung quanh vấn đề này là bằng cách sử dụng Keys.BACKSPACE chỉ đơn giản là bảo Python nhấp vào backspace (để xóa bất cứ điều gì được viết trong trường ngày). Tôi đặt nó trong một vòng lặp for để nhấp vào backspace 11 lần để xóa tất cả các ký tự cho ngày trong trường.

Lấy kết quả

Xác định chức năng sẽ nhấp vào nút tìm kiếm.

def search():
    search = browser.find_element_by_xpath("//button[@class='btn-primary btn-action gcw-submit']")
    search.click()
    time.sleep(15)
    print('Results ready!')

Ở đây tốt hơn là sử dụng độ trễ dài 15 giây hoặc lâu hơn để đảm bảo tất cả các kết quả được tải trước khi chúng tôi tiến hành các bước tiếp theo.

Trang web kết quả như sau (với các lĩnh vực tôi quan tâm được đánh dấu):

Tổng hợp dữ liệu

Chúng tôi sẽ sử dụng trình tự này để biên dịch dữ liệu của chúng tôi:

  1. Đầu tiên, tạo một DataFrame Pandas để giữ dữ liệu của chúng tôi.
  2. Tạo các biến cho tất cả các thuộc tính chuyến bay (được tô sáng trong hình trước) sẽ được lưu trong danh sách.
  3. Tìm tất cả các yếu tố cho một thuộc tính (ví dụ: tất cả thời gian khởi hành).
  4. Lưu trữ chúng trong biến liên quan mà chúng tôi tạo ra như một danh sách.
  5. Đặt tất cả các danh sách đó cạnh nhau dưới dạng cột trong DataFrame của chúng tôi.
  6. Lưu DataFrame vào một bảng Excel (trong trường hợp chúng tôi muốn phân tích dữ liệu này sau).

Dưới đây là mã:

df = pd.DataFrame()
def compile_data():
    global df
    global dep_times_list
    global arr_times_list
    global airlines_list
    global price_list
    global durations_list
    global stops_list
    global layovers_list


    #departure times
    dep_times = browser.find_elements_by_xpath("//span[@data-test-id='departure-time']")
    dep_times_list = [value.text for value in dep_times]


    #arrival times
    arr_times = browser.find_elements_by_xpath("//span[@data-test-id='arrival-time']")
    arr_times_list = [value.text for value in arr_times]


    #airline name
    airlines = browser.find_elements_by_xpath("//span[@data-test-id='airline-name']")
    airlines_list = [value.text for value in airlines]


    #prices
    prices = browser.find_elements_by_xpath("//span[@data-test-id='listing-price-dollars']")
    price_list = [value.text.split('$')[1] for value in prices]


    #durations
    durations = browser.find_elements_by_xpath("//span[@data-test-id='duration']")
    durations_list = [value.text for value in durations]


    #stops
    stops = browser.find_elements_by_xpath("//span[@class='number-stops']")
    stops_list = [value.text for value in stops]


    #layovers
    layovers = browser.find_elements_by_xpath("//span[@data-test-id='layover-airport-stops']")
    layovers_list = [value.text for value in layovers]


    now = datetime.datetime.now()
    current_date = (str(now.year) + '-' + str(now.month) + '-' + str(now.day))
    current_time = (str(now.hour) + ':' + str(now.minute))
    current_price = 'price' + '(' + current_date + '---' + current_time + ')'
    for i in range(len(dep_times_list)):
        try:
            df.loc[i, 'departure_time'] = dep_times_list[i]
        except Exception as e:
            pass
        try:
            df.loc[i, 'arrival_time'] = arr_times_list[i]
        except Exception as e:
            pass
        try:
            df.loc[i, 'airline'] = airlines_list[i]
        except Exception as e:
            pass
        try:
            df.loc[i, 'duration'] = durations_list[i]
        except Exception as e:
            pass
        try:
            df.loc[i, 'stops'] = stops_list[i]
        except Exception as e:
            pass
        try:
            df.loc[i, 'layovers'] = layovers_list[i]
        except Exception as e:
            pass
        try:
            df.loc[i, str(current_price)] = price_list[i]
        except Exception as e:
            pass

    print('Excel Sheet Created!')

Một điều đáng nói là đối với cột giá tôi đang đổi tên nó mỗi khi mã chạy bằng đoạn mã này:

now = datetime.datetime.now()
current_date = (str(now.year) + '-' + str(now.month) + '-' + str(now.day))
current_time = (str(now.hour) + ':' + str(now.minute))
current_price = 'price' + '(' + current_date + '---' + current_time + ')'

Điều này là do tôi muốn có tiêu đề của cột ghi rõ thời gian hiện tại tại lần chạy cụ thể đó để có thể xem sau này giá thay đổi theo thời gian như thế nào trong trường hợp tôi muốn làm điều đó.

Thiết lập chức năng email của chúng tôi

Trong phần này tôi sẽ thiết lập ba chức năng:

  • Một để kết nối với email của tôi.
  • Một để tạo thông điệp.
  • Một cái cuối cùng để thực sự gửi nó.

Trước tiên, tôi cũng cần lưu trữ thông tin đăng nhập email của mình theo hai biến như sau:

#email credentials
username = 'myemail@hotmail.com'
password = 'XXXXXXXXXXX'

Kết nối

def connect_mail(username, password):
    global server
    server = smtplib.SMTP('smtp.outlook.com', 587)
    server.ehlo()
    server.starttls()
    server.login(username, password)

Tạo tin nhắn

#Create message template for email
def create_msg():
    global msg
    msg = '\nCurrent Cheapest flight:\n\nDeparture time: {}\nArrival time: {}\nAirline: {}\nFlight duration: {}\nNo. of stops: {}\nPrice: {}\n'.format(cheapest_dep_time,
                       cheapest_arrival_time,
                       cheapest_airline,
                       cheapest_duration,
                       cheapest_stops,
                       cheapest_price)

Ở đây tôi tạo thông báo bằng cách sử dụng trình giữ chỗ '{}' cho các giá trị được truyền vào trong mỗi lần chạy.

Ngoài ra, các biến được sử dụng ở đây như  cheapest_arrival_timecheapest_airlinev.v. sẽ được xác định sau khi chúng ta bắt đầu chạy tất cả các hàm của mình để giữ các giá trị cho mỗi lần chạy cụ thể.

Gửi tin nhắn

def send_email(msg):
    global message
    message = MIMEMultipart()
    message['Subject'] = 'Current Best flight'
    message['From'] = 'myemail@hotmail.com'
    message['to'] = 'myotheremail@hotmail.com'

    server.sendmail('myemail@hotmail.com', 'myotheremail@hotmail.com', msg)

Hãy chạy mã của chúng tôi!

Bây giờ chúng tôi cuối cùng sẽ chạy các chức năng của chúng tôi. Chúng tôi sẽ sử dụng logic dưới đây.

Phần cạo dữ liệu:

  1. Truy cập liên kết của chúng tôi cho Expedia và ngủ trong 5 giây để cho phép trang tải.
  2. Chọn "chỉ chuyến bay" vì tôi hiện không quan tâm đến các ưu đãi khác như chuyến bay cộng với khách sạn.
  3. Chạy chức năng chọn vé của chúng tôi cho một vé khứ hồi.
  4. Điều hành người chọn quốc gia khởi hành của chúng tôi (đối với Cairo vì đây là nơi tôi đang ở).
  5. Điều hành người chọn quốc gia đến của chúng tôi (hãy làm New York).
  6. Chạy trình chọn ngày khởi hành của chúng tôi (ưu tiên đặt số 0 trước tháng hoặc ngày của bạn như ngày 01 cho tháng 1, ví dụ, vì đây là định dạng mà Expedia sử dụng).
  7. Chạy chọn ngày trở lại của chúng tôi.
  8. Chạy các chức năng tìm kiếm và biên dịch của chúng tôi.

Phần email:

  1. Truy cập vào hàng đầu tiên của DataFrame của chúng tôi, thông thường, chuyến bay đầu tiên là chuyến bay rẻ nhất và tốt nhất trên Expedia, nhưng nếu chúng tôi muốn đi sâu hơn, chúng tôi có thể lọc theo giá tối thiểu và có được hàng đó.
  2. Gán các giá trị trong mỗi cột của hàng chúng tôi đã chọn vào các biến được sử dụng trong thông điệp email của chúng tôi như ( cheapest_dep_timecheapest_arrival_time,  vv)
  3. Chạy các chức năng email của chúng tôi để tạo tin nhắn, kết nối và gửi email.

Cuối cùng, chúng tôi lưu DataFrame của chúng tôi vào một bảng Excel và ngủ trong 3600 giây (1 giờ).

Vòng lặp này sẽ chạy 8 lần trong khoảng thời gian một giờ, do đó nó sẽ chạy trong 8 giờ. Bạn có thể điều chỉnh thời gian theo sở thích của bạn.

for i in range(8):    
    link = 'https://www.expedia.com/'
    browser.get(link)
    time.sleep(5)

    #choose flights only
    flights_only = browser.find_element_by_xpath("//button[@id='tab-flight-tab-hp']")
    flights_only.click()

    ticket_chooser(return_ticket)

    dep_country_chooser('Cairo')

    arrival_country_chooser('New york')

    dep_date_chooser('04', '01', '2019')

    return_date_chooser('05', '02', '2019')

    search()

    compile_data()

    #save values for email
    current_values = df.iloc[0]

    cheapest_dep_time = current_values[0]
    cheapest_arrival_time = current_values[1]
    cheapest_airline = current_values[2]
    cheapest_duration = current_values[3]
    cheapest_stops = current_values[4]
    cheapest_price = current_values[-1]


    print('run {} completed!'.format(i))

    create_msg()
    connect_mail(username,password)
    send_email(msg)
    print('Email sent!')

    df.to_excel('flights.xlsx')

    time.sleep(3600)

Bây giờ tôi sẽ nhận được email này mỗi giờ trong 8 giờ tiếp theo:

Tôi cũng có bảng Excel gọn gàng này với tất cả các chuyến bay và nó sẽ tiếp tục cập nhật mỗi giờ với một cột mới cho giá hiện tại:

Bây giờ bạn có thể thực hiện điều này hơn nữa bằng cách áp dụng rất nhiều ý tưởng khác như:

  • Truy cập nhiều trang web và gửi cho mình mức giá tốt nhất hiện tại từ mỗi trang web.
  • Chạy vòng cho nhiều phạm vi ngày và kiểm tra ngày nào đưa ra mức giá tốt nhất trên trang web nào.
  • Kiểm tra giá phát triển theo thời gian cho mỗi hãng hàng không.

Nếu bạn có ý tưởng khác đừng ngần ngại chia sẻ!

Đó là nó! Tôi hy vọng bạn tìm thấy nó hữu ích.

Hữu ích 6 Chia sẻ Viết bình luận 0
Đã xem 1572