python爬虫项目- 爬取厦门航空官网机票价格 selenium 多进程爬取

这个项目虽然我写的比较粗糙,但是相比于网上老掉牙的复制粘贴代码要有很强的参考价值,废话不多说,开撸!

第一件事打开厦航机票网页,下图所示

python爬虫项目- 爬取厦门航空官网机票价格 selenium 多进程爬取_第1张图片

这个网站唯一的好处就是url是有规律变动的,这省了很多事,

分析url:https://et.xiamenair.com/xiamenair/book/findFlights.action?lang=zh&tripType=0&queryFlightInfo=XMN,PEK,2017-12-16

XMN,PEK表示出发城市和到达城市机场的二级代码,XMN表示厦门,PEK表示北京,

2017-12-16 事机票出发的日期,

其余部分观察后发现都是固定不变的,直接拼接url=“https://et.xiamenair.com/xiamenair/book/findFlights.action?lang=zh&tripType=0&queryFlightInfo=”+str(城市代码)+",2017-12-16"

爬取这一天的机票价格,直接用request请求就能得到,

当然没那么简单,这个网站的机票价格也是动态加载的,直接获取的话只能得到一部分价格,还有一部分需要你点击“更多舱位及价格”才会加载,网站最坑的就在这里,用模拟post请求发送获取不到这个数据,原因事因为这特么除了post表单参数外,还要验证cookie,用谷歌浏览器自带功能或者抓包工具你会发现,cookie值复杂的一笔,其中夹杂着好多参数要分析,大部分与unix时间戳有关,需要慢慢研究js文件,这里就不讲这个复杂的方法了。

本次抓取采用的是selenium+谷歌浏览器的方式获取,具体是啥自己百度。

代码中需要的插件和模块 导入如下,有问题问百度。

# coding:utf
import multiprocessing # 多进程模块
import time # 时间模块
import datetime #日期模块
from lxml import etree 
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver


先把selenium和谷歌浏览器加上:

driver=webdriver.ChromeOptions()
    prefs = {"profile.managed_default_content_settings.images": 2} # 不加载图片
    driver.add_experimental_option("prefs",prefs)
    driver=webdriver.Chrome(chrome_options=driver)
    driver.get(urls)

打开这个页面后,

selenium在这次项目主要做的就是帮我们点击“更多舱位及价格”,


这个点击的节点 xpath表达式是:“

//div[@class='segment-mess']/div[@class='form-mess']/div[@class='spe-line']/a[@class='right show-more webtrends-findFlights-showMore']/span

一个页面上往往有多个 “更多舱位及价格“ 点击事件,所以需要用for循环挨个点击,为了保证能加载完全,每次点击time.sleep(2)等待2秒,

代码如下:

    while True:
        qqq = driver.find_elements_by_xpath(
            "//div[@class='segment-mess']/div[@class='form-mess']/div[@class='spe-line']/a[@class='right show-more webtrends-findFlights-showMore']/span")
        for i in qqq:
            try:
                ActionChains(driver).click(i).perform()  # 对定位到的元素执行鼠标左键操作
                print(u'正在加载当前航班所有机票价格...')
                time.sleep(2)
            except Exception as e:
                print(u'加载失败', e)


这下整个页面的就全部点击加载出来了,

我们把整个页面完整的数据源码弄出来:

html = driver.page_source
response = etree.HTML(html)

接下来就是获取数据了,这里面的操作堪称眼花缭乱,要想把机票价格 航班班次 出发时间 出发日期 出发城市 等等一串信息 做到一一对应,还是有点难度的,需要你去见识下厦航的网站结构和对xpath有一定的领悟,这里我就不详细描述了,直接上代码:

        for each in response.xpath("//div[@class='form-mess']"):
            hangci = each.xpath(".//span[@class='form-flt-num']/text()")[0]
            starttime = each.xpath(".//div[@class='col-lg-2 col-md-2 col-sm-2 col-xs-2']/h2[@class='bold']/text()")[0]
            endtime = each.xpath(".//div[@class='col-lg-2 col-md-2 col-sm-2 col-xs-1']/h2[@class='bold']/text()")[0]
            lists = each.xpath(".//div[@class='form-mess-inner hide-mess cabin-info spe-height lh30 clear']")
            for i in lists:
                flightNo = 'MF' + hangci  # 航次
                official_price = i.xpath(".//span[@class='flight-price']/text()")[0]  # 价格
                cabin = i.xpath(".//span[@class='form-cabin']/text()")[0]  # 座位等级
                flightTime = starttime  # 出发时间
                # item6= endtime      #到达时间
                flightDate = response.xpath("//div[@class='nav-outer low-price-nav']//li[@class='this-tab']//h3/@date")[
                    0]  # 出发日期
                uptime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())  # 爬取时间
                print(flightDate,flightNo,official_price,cabin,flightTime,uptime,urlc)

这基本上就获取到当页所有的数据,目前对这些数据我没有做任何操作,只是单纯的打印出来,

但是既然是爬取机票网站,爬一天数据肯定是不止的,用日期做递增url重新打开页面又太慢了,这里有个小窍门


只要点击上图中的那个标签就会跳到下一天的日期,例如你现在点击‘12/17星期天’那个标签 他就会在原有的界面上加载出新的一天的数据,并且‘12/17星期天‘这个标签的位置会回到‘12/16星期天’那个位置,

这样的话我们就可以做一个循环点击,每爬完一页(也就是一天)的数据,就点击一下,然后就可以获取下一天的内容,点多少次,你可以设置个条件,

这个点击节点的xpath是:

'//*[@id="load-mess"]/div[1]/div[1]/div[2]/div[1]/ul/li[5]'

一直点击的代码如下:

# 一直点击下一页
qq = driver.find_element_by_xpath('//*[@id="load-mess"]/div[1]/div[1]/div[2]/div[1]/ul/li[5]')
ActionChains(driver).click(qq).perform()
time.sleep(2)
注意每次点完休息2秒,这样可以让他有足够时间加载下一天的数据。


设置条件的话,比如爬取从今天到30天内的数据:

# 现在的时间
now = datetime.datetime.now()
# 递增的时间
delta = datetime.timedelta(days=1)
# 30天后的时间
endnow = now + datetime.timedelta(days=30)
# 六天后的时间转换成字符串
endnow = str(endnow.strftime('%Y-%m-%d'))


用xpath取出点击后的时间日期
date = response.xpath('//*[@id="load-mess"]/div[1]/div[1]/div[2]/div[1]/ul/li[5]/a/h3/@date')[0]

全部转换成字符串,做判断,一旦点到的日期等于30天后的日期,就停止循环,break

if str(date) == endnow:
    time.sleep(5)
    driver.quit()
    break


把整个过程用def func(url)函数封装一下,我们再用多线程开启,加快效率:
list=[] 将每条线路的url做成列表,然后循环爬取,

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes=2)
    for url in list:
        pool.apply_async(func,(url,))
    pool.close()
    pool.join()
    print("done")


一般情况下爬取到这样的数据就可以直接放进mysql数据库,这里再上数据库代码:

# 链接数据库----------------------------------------------
connect = pymysql.connect(
    user="root",
    password="123456",
    port=3306,
    host="127.0.0.1",
    db="mysql",
    charset="utf8"
)
con = connect.cursor()  #获取游标
con.execute("use mysql")   #使用数据库
try:
    con.execute(
        "insert into ceshi(dep,arr,flightNo,official_price,cabin,flightTime,flightDate,uptime)values(%s,%s,%s,%s,%s,%s,%s,%s)",[dep, arr, flightNo, official_price, cabin, flightTime, flightDate, uptime])
    connect.commit()  # 我们需要提交数据库,否则数据还是不能上传的
    print('数据正在上传...')
except Exception as e:
    print('数据上传失败...', e)
    pass

做到这里 一个大致完整的项目已经差不多了,为了方便解说,我把它拆散了,所以你们想复制粘贴用会很麻烦,

下面我就直接把这个项目代码奉上

# coding:utf
import multiprocessing
from multiprocessing import Process
import time
import datetime
import pymysql
from lxml import etree
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver

# 机场代码列表,这里只是添加了几个做案例
list= ["CGO,DLC", "CGO,FOC", "CGO,HAK", "CGO,HET","CGO,HRB","CGO,INC","CGO,JJN"]

# 现在的时间
now = datetime.datetime.now()
# 递增的时间
delta = datetime.timedelta(days=1)
# 30天后的时间
endnow = now + datetime.timedelta(days=2)
# 六天后的时间转换成字符串
endnow = str(endnow.strftime('%Y-%m-%d'))


# 链接数据库----------------------------------------------
connect = pymysql.connect(
    user="root",
    password="520134",
    port=3306,
    host="127.0.0.1",
    db="mysql",
    charset="utf8"
)
con = connect.cursor()  #获取游标
con.execute("use mysql")   #使用数据库


def func(url):
    urlc=url #传进来的url是"CGO,FOC"这样,所以完整的url需要拼接
    global now #用全局变量把当天日期导进来
    # now 是当天的日期,一般抓取都是从当天开始抓,所以每次运行都可以不用管,这是根据代码开始运行的日期自动生成,每天跑一次,然后存到数据库。
    urls = 'https://et.xiamenair.com/xiamenair/book/findFlights.action?lang=zh&tripType=0&queryFlightInfo=' + str(url) + ','+str(now)
    global endnow #用全局变量把30天后的日期导进来

    driver=webdriver.ChromeOptions()
    prefs = {"profile.managed_default_content_settings.images": 2} # 不加载图片
    driver.add_experimental_option("prefs",prefs)
    driver=webdriver.Chrome(chrome_options=driver)
    driver.get(urls)

    while True:
        # 找到加载更多舱位的xpath节点,
        qqq = driver.find_elements_by_xpath(
            "//div[@class='segment-mess']/div[@class='form-mess']/div[@class='spe-line']/a[@class='right show-more webtrends-findFlights-showMore']/span")
        # 遍历每一个节点,挨个点击加载
        for i in qqq:
            try:
                ActionChains(driver).click(i).perform()  # 对定位到的元素执行鼠标左键操作
                print(u'正在加载当前航班所有机票价格...')
                time.sleep(2)
            except Exception as e:
                print(u'加载失败', e)

        # 加载完的界面
        html = driver.page_source
        response = etree.HTML(html)
        print(u'全部加载完成')

        # 通过xpath把加载完的页面数据全部取出来,做一一对应
        for each in response.xpath("//div[@class='form-mess']"):
            hangci = each.xpath(".//span[@class='form-flt-num']/text()")[0]
            starttime = each.xpath(".//div[@class='col-lg-2 col-md-2 col-sm-2 col-xs-2']/h2[@class='bold']/text()")[0]
            endtime = each.xpath(".//div[@class='col-lg-2 col-md-2 col-sm-2 col-xs-1']/h2[@class='bold']/text()")[0]
            lists = each.xpath(".//div[@class='form-mess-inner hide-mess cabin-info spe-height lh30 clear']")
            for i in lists:
                item1 = urlc # 出发城市,到达城市,将他们做字符串切割
                dep = item1[:3]
                arr = item1[4:]
                flightNo = 'MF' + hangci  # 航次
                official_price = i.xpath(".//span[@class='flight-price']/text()")[0]  # 价格
                cabin = i.xpath(".//span[@class='form-cabin']/text()")[0]  # 座位等级 例如 F 表示头等舱
                flightTime = starttime  # 出发时间
                # item6= endtime      #到达时间
                flightDate = response.xpath("//div[@class='nav-outer low-price-nav']//li[@class='this-tab']//h3/@date")[0]  # 出发日期
                uptime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())  # 爬取时间
                
                #尝试传入数据库,根据你们自身mysql表的设计,酌情修改;或者不传数据库做写入文本也可以
                try:
                    con.execute(
                        "insert into ceshi(dep,arr,flightNo,official_price,cabin,flightTime,flightDate,uptime)values(%s,%s,%s,%s,%s,%s,%s,%s)",
                        [dep, arr, flightNo, official_price, cabin, flightTime, flightDate, uptime])
                    connect.commit()  # 我们需要提交数据库,否则数据还是不能上传的
                    print('数据正在上传...')
                except Exception as e:
                    print('数据上传失败...', e)
                    pass

        # 定位下一日期
        date = response.xpath('//*[@id="load-mess"]/div[1]/div[1]/div[2]/div[1]/ul/li[5]/a/h3/@date')[0]

        # 当下一日期等于指定日期,就停止循环
        if str(date) == endnow:
            time.sleep(5)
            driver.quit()
            break
        # 一直点击下一页
        qq = driver.find_element_by_xpath('//*[@id="load-mess"]/div[1]/div[1]/div[2]/div[1]/ul/li[5]')
        ActionChains(driver).click(qq).perform()
        time.sleep(2)




if __name__ == "__main__":
    # 开启两个进程,即同时打开两个谷歌浏览器爬取
    pool = multiprocessing.Pool(processes=2)
    for i in list:
        pool.apply_async(func,(i,))
    pool.close()
    pool.join()
    print("done")


# 如果是大规模爬取,建议添加随机userAgent和ip代理,不然可能被封








你可能感兴趣的:(python爬虫项目,python,xpath,数据库,selenium,爬虫)