介绍

在本教程中, 我将向您展示如何使用 python 自动浏览像 expedia 这样的网站, 每小时查找航班, 并将您想要的特定路线的最佳航班费率直接发送到您的电子邮件。

最终的结果是这个不错的电子邮件:

我们将按以下方式开展工作:

  1. 将 python 连接到我们的 web 浏览器并访问网站 (在我们的示例中的 expedia)。
  2. 根据我们的喜好选择机票类型 (往返、单程等)。
  3. 选择出发国家/地区。
  4. 选择到达国家/地区 (如果是往返)。
  5. 选择出发和返回日期。
  6. 以结构化格式编译所有可用的航班 (对于那些喜欢做一些探索性数据分析的人!)
  7. 连接到您的电子邮件。
  8. 发送当前小时的最佳费率。

让我们开始吧!

导入库

让我们继续导入我们的库:

硒 (用于访问网站和自动化测试):

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

熊猫 (我们将主要使用熊猫来构建我们的数据):

import pandas as pd

时间和日期时间 (对于使用延迟和返回当前时间, 我们稍后将看到原因):

import time
import datetime

我们需要那些连接到我们的电子邮件和发送我们的消息:

import smtplib
from email.mime.multipart import MIMEMultipart

注意: 我不会去太深入到使用硒的网络刮擦, 但如果你想一个更详细的教程刮一般检查我以前的教程刮使用和网络刮在一般的第1部分和第2部分.

让我们开始编码

连接到 web 浏览器

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

这将打开一个空浏览器, 告诉您此浏览器是由自动测试软件控制的, 如下所示:

选择门票

接下来, 我将快速转到expedia来检查接口和可供选择的选项。

我点击右键单击 + 检查票证类型 (往返, 单向等), 以查看与之相关的标签。

正如我们可以看到下面, 它有一个“标签” 标签与 “id = 飞行类型往返标签-hp 飞行”bp.blogspot.com/-SuaMYMOjBqc/XFbGjGbE47I/AAAAAAAAAYU/WRV-yOj2SH8PRDZsX-BltuXEv12hi5p8QCLcBGAs/s1600/Screen%2BShot%2B2019-02-03%2Bat%2B12.45.28%2BPM.png “rel =” nofollow “> >

因此, 我将使用它们来存储三种不同的票证类型的标记和 id, 如下所示:

#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']"

然后定义一个函数来选择票证类型:

def ticket_chooser(ticket):

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

上面的顺序和我将用于其余代码的顺序相同 (查找标记和 id 或其他属性, 并定义一个函数来在网页上进行选择)。

选择出发和到达的国家

下面我定义了一个函数来选择出发的国家。

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()

我遵循以下逻辑:

  1. 使用元素的标记和属性查找元素。
  2. 清除在国家/地区字段中写入的任何值。
  3. 键入我想要的国家 (将传递到函数中) .sendkeys 使用。
  4. 从下拉菜单中选择出现的第一个选项 (也使用它的标记和 id, 当下拉菜单出现时, 右键单击 “+ 检查” 元素即可找到该标记和 id)。
  5. 单击此首选项。

请注意, 我使用 time.sleep 的是步骤之间的机会, 以便为页面的元素提供一个机会, 以便在步骤之间进行更新。如果没有 time.sleep ,有时我们的脚本的作用比页面加载的速度更快, 从而尝试访问尚未加载的元素, 从而导致我们的代码中断。

让我们为抵达国做同样的事情。

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()

选择出发和返回日期

出发日期:

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)

非常直的前进:

  1. 像以前一样在网页上查找元素。
  2. 清除以前编写的内容。
  3. 用在函数中输入的月、日和年份作为参数填充元素 + 日期格式的斜杠。

返回日期:

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

返回 _ date _ 需结束. 发送 _ key (月 + “/” + day + “/”/”年)

对于返回日期, 清除所写内容由于某种原因而不起作用 (可能是由于页面有此自动填充不允许我覆盖它 .clear() )

我解决这个问题的方法是使用 Keys.BACKSPACE 它, 它只是告诉 python 单击后空间 (删除日期字段中写的任何内容)。我把它放在一个 for 循环中, 单击后空间 11次, 以删除字段中日期的所有字符。

获取结果

定义将单击搜索按钮的函数。

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

在这里, 最好使用15秒左右的长延迟, 以确保在我们继续下一步之前加载所有结果。

生成的网页如下 (标记了我感兴趣的字段):

编译数据

我们将使用此序列来编译我们的数据:

  1. 首先, 创建一个熊猫数据框架来保存我们的数据。
  2. 为要存储在列表中的所有飞行属性 (在上图中突出显示) 创建变量。
  3. 查找属性的所有元素 (例如, 所有出发时间)。
  4. 将它们存储在我们创建的相关变量中作为列表。
  5. 将所有这些列表并排放在我们的数据框架中的列中。
  6. 将数据框架保存到 excel 工作表 (以防我们稍后分析此数据)。

下面是代码:

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, ‘ 航空公司 ‘] = 航空公司 _ list [i] 除了例外作为 e: 通过尝试: df. loc [i, ‘ 期限 ‘] = 持续时间 _ list [i] 除了例外 e: 通过尝试: df. loc [i, ‘ 停止 ‘] = 停止 _ list [i] 除了例外作为 e: 通过尝试: df. loc [i, ‘ layovers ‘ = layovers _ list [i] 除了例外作为 e: 通过尝试: df. loc [i, str (当前价格)] = 价目表 _ list [i] 除了例外作为 e: 通行证 print (“excel 工作表已创建!”)

值得一提的是, 对于价格列, 每次使用此代码段运行代码时, 我都会重命名它:

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 + ')'

这是因为我希望有列的标题, 说明该特定运行的当前时间, 以便以后能够看到价格如何随着时间的推移而变化, 以防我想这样做。

设置我们的电子邮件功能

在这一部分中, 我将设置三个函数:

  • 一个连接到我的电子邮件。
  • 一个用于创建消息。
  • 最后一个实际发送它。

首先, 我还需要将我的电子邮件登录凭据存储在两个变量中, 如下所示:

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

连接

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

创建消息

#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)

在这里, 我使用占位符 {} “创建消息, 以便在每次运行期间传入的值。

此外, 这里使用的变量, cheapest_arrival_time cheapest_airline 如, 等等, 将被定义后, 当我们开始运行我们的所有函数, 以保持每个特定运行的值。

发送消息

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)

让我们运行我们的代码!

现在我们将最终运行我们的函数。我们将使用下面的逻辑。

数据刮除部分:

  1. 访问我们的 expedia 链接, 并睡 5秒, 以允许加载页面。
  2. 选择 “仅限航班”, 因为我目前对航班加酒店等其他优惠不感兴趣。
  3. 运行我们的机票选择功能的回程机票。
  4. 运行我们的出发国家选择器 (为开罗, 因为这是我目前所在的地方)。
  5. 运行我们的到达国家选择器 (让我们做纽约)。
  6. 运行我们的出发日期选择器 (最好在您的月份或日期之前放置零, 例如, 1月为 01, 因为这是 expedia 使用的格式)。
  7. 运行返回日期选择器。
  8. 运行我们的搜索和编译功能。

电子邮件部分:

  1. 访问我们的 datframe 的第一行, 因为通常情况下, 第一班航班是 expedia 上最便宜、最好的, 但如果我们想走得更远, 我们可以按最低价格进行过滤, 得到那一行

)

  • 运行我们的电子邮件功能以创建邮件、连接和发送电子邮件。
  • 最后, 我们将数据框架保存到 excel 工作表中, 并在 3600秒 (1小时) 内休眠。

    此循环将每小时运行 8次, 因此将运行8小时。你可以根据自己的喜好调整时机。

    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)

    现在, 我将得到这封电子邮件每小时在接下来的 8小时:

    我也有这个整洁的 excel 表与所有的航班, 它将不断更新每小时与一个新的列为当前价格:

    现在, 您可以通过应用许多其他想法来进一步解决这一问题, 例如:

    • 访问多个网站, 并从每个网站向自己发送当前的最佳价格。
    • 运行多个日期范围的循环, 并检查哪些日期给出了网站上的最佳价格。
    • 检查每家航空公司的价格如何随时间变化。

    如果你有其他想法, 不要犹豫分享!

    就是这样!我希望你发现它有用。

    Comments are closed.