这个项目虽然我写的比较粗糙,但是相比于网上老掉牙的复制粘贴代码要有很强的参考价值,废话不多说,开撸!
第一件事打开厦航机票网页,下图所示
这个网站唯一的好处就是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)
这个点击的节点 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)
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'))
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
把整个过程用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代理,不然可能被封