# FlyCheap

## 中国民航数据库－CivilAviation

### 航空公司（Airline）

- **包含**：全球九百余家航空公司及国内航空公司
- **定义**：支持航司全名、ICAO、IATA代码定义
- **成员**：航司英语全称、呼号、国家
- **==运算**：支持互相比较、字符串比较是否等于IATA、ICAO、航空公司名之一

### 机场（Airport）

- **包含**：包含国内定期航班机场、港澳台机场，也可手动添加
- **定义**：支持城市名（多机场城市需 + 机场名）、ICAO、IATA代码定义
- **成员**：经纬度、中英文城市与机场名、机场大小等
- **+运算**：生成项目使用航线（城市对航线）字符串
- **-运算**：生成航线（Route）
- **==运算**：支持互相比较、字符串比较是否等于IATA、ICAO、机场名之一

### 航线（Route）

- **定义**：支持机场、元组、字符串
- **成员**：部分航线的全价和大圆航线距离
- **拆分**：生成出发、到达机场对应成员元组
- **格式化**：生成出发、到达机场对应成员构成的航线（如城市对航线）字符串

## 数据收集－CtripCrawler

### 特性

- 单线程（多线程通过外部实现）
- 代理池（可使用[ProxyPool](https://github.com/Python3WebSpider/ProxyPool)，亦可使用自定义函数）
- 防丢包（数据偏少三次重试）
- 忽略集（跳过低航班量航线）
- 矩阵化（全连接航线）
- 定日期（忽略今日和之前日期）
- 带格式（输出表格带有格式）

### 缺点

- 爬取慢（一天、一个城市对、所有往返航班：平均用时2~3秒，低速网络、高密度航线不超过8秒）
- 忽略共享航班和有经停的航班

### 输出

- **文件夹**：起始爬取航班日期 / 收集日期
- **文件名**：航线（ ~ 代表双向， - 代表单向）
- **表头**：航班日期、星期、航司、机型、出发到达机场及时刻、价格、折扣

### 附加程序

- **CtripSearcher**: 通过当前携程搜索页面的搜索api编写，参考CSDN，但爬取较为缓慢，未使用
- **ItineraryCollector**: 随机收集航程（某日某航线所有航班信息），反爬使用

## 数据重构－Rebuilder

### 特性

- 使用 pandas 数据结构
- 加载数据较快
- 处理速度随数据量和数据复杂度变化

### 数据重构功能

#### 数据整合（merge）

- [x] 数据总集，整合所有收集的航班原始信息

#### 总览（overview）

- [x] **航司**：按时刻、航线，总览密度与系数；按起飞机场总览航班数量
- [x] **航线**：按日期、提前天数，总览均值和标准差；按时刻总览密度与系数
- [x] **日期**：按航线，以收集日期或航线日期，总览每日折扣均值

#### 相关系数（correlation）

- [x] **月份**：确定某月机票折扣“跳跃”的相关系数与航线、航司、采集日期关系
- [x] **提前天数**：确定提前x~y天内机票折扣随提前天数减少的相关系数与航线、航司、航班日期关系
- [x] **星期**：确定每个星期在各个周期内的相关系数与航线、航司关系
- [x] **时刻**：确定每个时刻在一个航程内的相关系数与航线、航司关系

### 附加功能

- 四种数据导入方式
- 整合数据的重复利用

## 数据结构示例

```python
>>> from flycheap import Airport, Route
>>> route = Airport('广州') - Airport('PEK')	#构造航线
>>> route
flycheap.civilaviation.Route(广州, 北京首都)

>>> route.airfare	#航线全价
2540

>>> route.separates()	#分解为机场数据结构
(flycheap.civilaviation.Airport('CAN', 'ZGGG', '广州白云', ...), civilaviation.flycheap.Airport('PEK', 'ZBAA', '北京首都', ...))

>>> route.separates('icao')	#分解为机场成员
('ZGGG', 'ZBAA')

>>> route.format('iata')	#生成航线字符格式
'CAN-PEK'

>>> route = Route.fromformat('成都天府-NKG')	#从字符串获得航线
>>> route.separates('airport')	#分解为成员：机场名
('成都天府', '南京禄口')
```

## 多线程爬取示例

### 设置python文件  (D:\routine.py)

```python
from datetime import date
from flycheap import CtripCrawler
from pandas import DataFrame
from argparse import ArgumentParser

flight_date = date.today()
kwargs = {
    'targets': ["BJS", "SHA", "CAN", "CTU", "DLC"], 
    'flight_date': flight_date, 
    'ignore_threshold': 3, 
    'ignore_routes': set(), 
    'days': 7}
crawler = CtripCrawler(**kwargs)

parser = ArgumentParser()
parser.add_argument("--part", type = int, default = 1)
parser.add_argument("--parts", type = int, default = 1)
parser.add_argument("--attempt", type = int, default = 3)
parser.add_argument("-reverse", action = 'store_true')
parser.add_argument("-overwrite", action = 'store_true')
parser.add_argument("-nopreskip", action = 'store_true')
parser.add_argument("--antiempty", type = int, default = 0)
parser.add_argument("--noretry", type = str, action = 'append', default = [])

title = ['出发日期', '星期', '航司', '机型', '出发', '到达', '出发时刻', '到达时刻', '价格', '折扣']
for data in crawler.run(**vars(parser.parse_args())):
	print(DataFrame(data, columns = title).assign(**{'收集日期': date.today()}))
```

### 设置batch文件 (routine.bat)

```bash
@echo off
start python D:\routine.py -nopreskip --part 3 --parts 3 --noretry SHA --attempt 2 --antiempty 2
start python D:\routine.py -nopreskip --part 2 --parts 3 --noretry SHA --attempt 2 --antiempty 2
start python D:\routine.py -nopreskip --part 1 --parts 3 --noretry SHA --attempt 2 --antiempty 2
```

## 数据重构示例

```python
from flycheap import Rebuilder
if __name__ == '__main__':
    rebuild = Rebuilder('2022-2-17')
    #rebuild.append_data('dataset.csv')
    rebuild.append_folder()
    rebuild.adv(1, 7)
```

