FDAの運航情報を見やすく加工する

## 公式サイトの運航情報が見づらいので…
FDA（フジドリームエアラインズ）は県営名古屋空港・静岡空港などを拠点とする地域航空会社である。
この FDA が公開している運航情報のページは以下のリンクから見ることができる。

https://www.fujidreamairlines.com/bkt/app/RES/flightInfo/FRIG310_00BL.do?selectDay=0

ページ上部にナビゲーションはあるものの、路線ごと折りたたまれて1ページ完結の構成となっており、上部に固められている名古屋空港を除いては、当該空港の発着をまとめて見ることが容易ではない。

そこで、 Python を用いて運航情報をスクレイピング後、整形して見やすくするようなスクリプトを Google Colab で作ることにした。

## スクレイピング
スクレイピングには BeautifulSoup を用いた。
路線ごとの表にはそれぞれ ID が付いているので、その ID を手がかりに各表を指定。
基本的には表組み内の文字列を読み取っていく。
ただし、機体番号は文字列としては示されておらず、画像の URL に含まれているので、正規表現で機体番号のみを抽出する。
雑な読み方をしているため、欠航等で一部の便の機体番号が入っていない場合、データ数が合わなくなる。1路線内のどの便の機体番号が欠損しているか判断できないので、やむを得ず路線内の全便の機体番号を空にして入れることとした。
全路線の情報をリストに入れることができれば、 pandas のデータフレームに入れて自由に加工することができる。

```Python: スクレイピング部分

import requests
from bs4 import BeautifulSoup
import re

response = requests.get("https://www.fujidreamairlines.com/bkt/app/RES/flightInfo/FRIG310_00BL.do?selectDay=0")

soup = BeautifulSoup(response.text, 'html.parser')

route_list = ['NKM_OKD','NKM_AOJ','NKM_HNA','NKM_GAJ','NKM_KIJ','NKM_IZO','NKM_KCZ','NKM_FUK','NKM_KMJ',
              'NGO_KCZ','NGO_IZO',
              'FSZ_CTS','FSZ_OKD','FSZ_IZO','FSZ_FUK','FSZ_KMJ','FSZ_KOJ',
              'MMJ_CTS','MMJ_OKD','MMJ_UKB','MMJ_FUK',
              'UKB_AOJ','UKB_HNA','UKB_KCZ',
              'FUK_KIJ',
              'CTS_GAJ',
              'SDJ_IZO',
              'Charter']

flight_num = []; fleet = []; detail = []; from_ap = []; dest_ap = []
from_sch = []; from_act = []; dest_sch = []; dest_act = []; notes = []

# 路線ごと読み取り
for route_id in route_list:
    route = soup.find("div", id=route_id)

    if route != None:

        flight_count = 0
        fleet_temp = []
        for each in route.find_all("th",class_="number"):
            flight_num.append(re.findall('FDA\d+',each.text)[0])
            flight_count = flight_count + 1

        for each in route.find_all("img"):
            fleet_match = re.match('/bkt/app/html/RES/img/parts/',each.get('src'))
            if fleet_match != None:
                fleet_temp.append(re.findall('\d\d',each.get('src'))[0])
        if len(fleet_temp) != flight_count: # 欠航で一部の機体番号が入っていない場合
            fleet_temp = list([''] * flight_count)
        fleet.extend(fleet_temp)

        detail = []
        for each in route.find_all("td"):
            text = re.sub('\r?\n', '', each.text)
            detail.append(text.strip().replace(' ', '').replace('　', '').replace('\t', ''))

        for flight in range(flight_count):
            from_ap.append(detail[flight*7])
            dest_ap.append(detail[flight*7+1])
            from_sch.append(detail[flight*7+3])
            from_act.append(detail[flight*7+4])
            dest_sch.append(detail[flight*7+5])
            dest_act.append(detail[flight*7+6])
            notes.append(detail[flight*7+2])
```

## 見やすくデータ加工
スマホで表示させることも考えると、判別できる範囲で文字を減らした方が表が見やすくなる。
絵文字を使うことも可能。
```Python: データ加工
df['from_act'] = df['from_act'].str.replace('出発','')
df['dest_act'] = df['dest_act'].str.replace('到着','')
df['from_ap'] = df['from_ap'].replace({'名古屋(小牧)':'小牧','札幌(丘珠)':'丘珠','札幌(新千歳)':'新千歳','名古屋(中部)':'中部'})
df['dest_ap'] = df['dest_ap'].replace({'名古屋(小牧)':'小牧','札幌(丘珠)':'丘珠','札幌(新千歳)':'新千歳','名古屋(中部)':'中部'})
df['from_sch'] = df['from_sch'].str.replace(':','').str.replace('：','')
df['from_act'] = df['from_act'].str.replace(':','').str.replace('：','')
df['dest_sch'] = df['dest_sch'].str.replace(':','').str.replace('：','')
df['dest_act'] = df['dest_act'].str.replace(':','').str.replace('：','')

if emoji == 1:
    df['plane_color'] = df['plane_color'].replace({'01':'ドリームレッド🟥','02':'ライトブルー🟦','03':'ピンク💗','04':'グリーン🟩','05':'オレンジ🍊','06':'パープル🟣','07':'イエロー🟡','08':'ティーグリーン🍏','09':'ゴールド🥇','10':'シルバー🥈','11':'グリーン🟢','12':'ホワイト⚪','13':'ネイビー🔵','14':'ワインレッド🍷','15':'ローズピンク🌹','16':'バイオレット💜'})
    df['from_act'] = df['from_act'].str.replace('済み','済○')
    df['dest_act'] = df['dest_act'].str.replace('済み','済●')
    df['dest_act'] = df['dest_act'].str.replace('欠航','◆欠航◆')
else:
    df['plane_color'] = df['plane_color'].replace({'01':'ドリームレッド','02':'ライトブルー','03':'ピンク','04':'グリーン','05':'オレンジ','06':'パープル','07':'イエロー','08':'ティーグリーン','09':'ゴールド','10':'シルバー','11':'グリーン','12':'ホワイト','13':'ネイビー','14':'ワインレッド','15':'ローズピンク','16':'バイオレット'})
```
セルの背景色は `style.map` を使って塗り分けることができる。備考欄の言葉に対応させてセルを塗ることができる。
```Python: セル塗り(一部抜粋)
# 重要備考・欠航・遅延の背景色設定
def del_can_bf(x):
    if x.find('着陸できない') != -1:
        return 'background-color: cyan; text-align: start;'
    if x.find('到着地変更') != -1:
        return 'background-color: orange; text-align: start;'
    if x.find('欠航') != -1:
        return 'background-color: red; color: white; text-align: start;'
    if x.find('遅延') != -1:
        return 'background-color: yellow; text-align: start;'
    return 'text-align: start;'

# 備考あり便の表示
df_notes.sort_values(['fleet','from_sch']).style.map(del_can_bf)
```
このようにセルが塗り分けられる（2024/11/28 20時頃にやってみたもの）。
![fda_colab_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3943774/ab257e90-fc1e-1113-c582-758324c1c171.png)

## 完成品
Google Colab 形式の完成品はこちら。

https://colab.research.google.com/drive/1kxkZz-7SCPD_-XYHdDJB6IVEup4i6ede

各空港毎、機体番号ごとに一覧を表示させることができます。
FDA は各機体の色が異なるので、特定色の機体の「推し活」もあるとか。
運航情報ページの仕様が変わらない限り使えると思われます（路線が追加された場合は `route_list` に追加すればよい）。自己責任でご利用下さい。