github项目:https://github.com/wzyblowfire/flightsmonitor
页面分析
首先进入携程网的国际机票网页分析,可以看出该网页是一个动态页面,也就是说单一的请求获取response是无法得到我们需要的数据的,所以我们需要用后台分析一下我们真正所需要的数据到底在哪。
当搜索从香港到曼彻斯特的航班时,从Chrome控制台Network可以看到, 有个batchSearch的请求,获得了这一页中的所有航班信息,这时候我试了一下直接请求这个URL,加上对应的请求头和请求data,确实可以获取内容。
但是随后我发现了一个问题,如果我需要获取别的行程的信息,比如香港去伦敦,必须自己再一次手动去复制那个搜索的headers,因为headers中的sign和transactionid每一次搜索是唯一的,并且没有规律可循,也无法通过请求爬取。
为了解决需要手动复制这个问题,我使用了多种办法,最终选择了使用Selenium和Browsermob-proxy这两个库来实现。
库安装
1、Selenium安装
Selenium是一个浏览器自动化测试工具。
命令行输入
pip install selenium
安装完后注意,3.x以上版本的selenium是需要手动配置webdriver的。
配置教程:https://www.cnblogs.com/lvzuwen/p/7309341.html
记得按照自己电脑上浏览器的版本号下载对应版本的chromedriver.exe或geckodriver.exe。
2、Browsermob-proxy安装
Browsermob-prox是一个 浏览器代理服务器工具,可以监听浏览器的访问过程。
首先电脑上必须拥有java的环境,java配置:https://www.cnblogs.com/ssrs-wanghao/articles/8994856.html
Browsermob-proxy安装:https://blog.csdn.net/m0_37618247/article/details/85066272
记得Browsermob在github官网上release那里下载Browsermob-proxy包:https://github.com/wzyblowfire/browsermob-proxy/releases,我下的是2.1.4版本。
代码
(gitbub项目:https://github.com/wzyblowfire/flightsmonitor)
配置环境之后,就直接上代码了,首先是存储航班信息的类代码,这里只选择了数据中的一些信息,其实获取的信息中有很多信息,可根据自己的需求更改。
flights.py:
# -*- coding: utf-8 -*-
"""
Created on Wed Jul 31 15:02:31 2019
@author: wzyblowfire
"""
class Flights:
def __init__(self, data_dict):
self.fid = data_dict['itineraryId'].replace(',', '-') # 航班号
self.name = data_dict['flightSegments'][0]['airlineName'] # 航空公司
dur = data_dict['flightSegments'][0]['duration']
hour = int(dur/60)
minute = dur%60
self.duration = str(hour)+'h'+str(minute)+'m' # 行时长
self.detime = data_dict['flightSegments'][0]['flightList'][0]['departureDateTime'] # 出发时间
self.decity = data_dict['flightSegments'][0]['flightList'][0]['departureCityName'] # 出发地
self.arcity = []
self.artime = []
for x in data_dict['flightSegments'][0]['flightList']:
self.arcity.append(x['arrivalCityName']) # 达到城市(包括中转)
self.artime.append(x['arrivalDateTime']) # 达到时间(包括中转)
self.price = []
for x in data_dict['priceList']:
self.price.append(x['adultPrice']+x['adultTax']) # 机票价格
self.minprice = min(self.price) # 最低价格
def __str__(self):
# 信息输出
stri = 'name: ' +self.name+'\n'+\
'fid: '+self.fid+'\n'+\
'detime: '+str(self.detime)+' '+str(self.decity)+'\n'+\
'artime: '+str(self.artime)+' '+str(self.arcity)+'\n'+\
'duration: '+self.duration+'\n'+\
'minprice: '+str(self.minprice)
return stri
爬虫代码,每60秒获取一次信息,这里使用的是chrome浏览器,由于Selenium的速度较慢,所以这里选择先使用Selenium获取后续访问batchSearch的hearders和request data,然后后续持续访问就可以直接使用requests中的方法,提高效率,而且避免浏览器弹框。
selen.py:
# -*- coding: utf-8 -*-
"""
Created on Mon Aug 5 15:19:27 2019
@author: wzyblowfire
"""
import os
import time
import json
import requests
from browsermobproxy import Server
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
import spider
from flight import Flights
def search_url(depport, arrport, depdate):
"""
获取携程国际机票搜索的url
参数:
depport:出发机场码(机场码可参考(https://github.com/wzyblowfire/flightsmonitor)
data文件夹下的world-airports.csv或
访问http://ourairports.com/airports.html下载)
arrport: 到达机场码
depdate: 出发日期
返回值:
international_url:国际航班搜索url
"""
international_url = ('https://flights.ctrip.com/international/search/oneway-%s-%s?' + \
'depdate=%s&cabin=y_s&adult=1&child=0&infant=0') % (depport, arrport, depdate)
return international_url
def get_initinfo(url):
"""
本函数用于获取签名sign信息,transactionID和后续请求data.
其中使用了selenium和browsermob-proxy.
参数:
url: 携程搜索国际航班的url
返回值:
headers:后续请求头信息
postdata: 后续持续获取航班信息请求头中的提交json信息
"""
# browsermob-proxy配置路径,请将这里填写为自己电脑上的路径
path = os.path.join('D:\code\env', 'browsermob-proxy-2.1.4','bin','browsermob-proxy.bat')
server = Server(path) # 设置服务器脚本路径
server.start()
proxy = server.create_proxy() # 创建一个浏览器代理
# chrome测试配置
chrome_options = Options()
chrome_options.add_argument('--ignore-certificate-errors')
chrome_options.add_argument('--proxy-server={0}'.format(proxy.proxy))
chrome_options.add_argument('--disable-gpu')
driver = webdriver.Chrome(chrome_options = chrome_options) # 使用selenium创建浏览器窗口
proxy.new_har(url, options={'captureContent':True, 'captureHeaders':True}) # 代理服务器开始监测,捕捉文本和请求头信息
driver.get(url)
# 显示等待5秒,因为网页会持续加载,需要等待一段时间,直到航空公司内容出现,说明加载成功
WebDriverWait(driver,5,0.5).until(EC.presence_of_element_located((By.CLASS_NAME,'airline-name')))
result = proxy.har
server.stop()
driver.quit()
# 获取https://flights.ctrip.com/international/search/api/search/batchSearch这个访问过程中的重要信息
headers = {}
for entry in result['log']['entries']:
if 'batchSearch' in entry['request']['url']:
postdata = entry['request']['postData']['text']
header = entry['request']['headers']
for x in header:
headers[x['name']] = x['value']
return headers, postdata
def spider_searchflights(headers, post_data):
"""
后续持续获取数据函数
参数:
headers:请求头信息
post_data: 请求头中的数据信息(json)
返回:
dict_json: 航班信息(字典)
"""
search_URL = 'https://flights.ctrip.com/international/search/api/search/batchSearch?v='
response = requests.post(search_URL, data=post_data, headers=headers)
dict_json = json.loads(response.text)
# 如果请求不成功,输出信息
if dict_json['status'] != 0:
print(dict_json['msg'])
return dict_json
if __name__ == '__main__':
url = search_url('hkg', 'man', '2019-09-20')
headers, postdata = get_initinfo(url)
postdata = json.loads(postdata)
postdata = json.dumps(postdata)
while True:
result = spider.spider_searchflights(headers, postdata)
result = result['data']['flightItineraryList']
flights = {}
for x in result:
flight = Flights(x)
print(flight)
time.sleep(60) #每60秒更新一次