0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FlightAware AeroAPIで羽田発着便リストを取得する方法:空港発着便リスト vs スケジュール

Last updated at Posted at 2025-10-02

はじめに

FlightAware AeroAPIとは、航空便データをJSONで取得できるAPIです。

AeroAPI 4.28.0

AeroAPIは、ソフトウェア開発者がFlightAwareの様々なフライトデータにアクセスできる、シンプルなクエリベースのAPIです。ユーザーは現在データや履歴データを取得できます。

AeroAPIは正確で実用的な航空データを提供するRESTful APIです。Foresight™の導入により、顧客は米国における予測航空会社到着時刻(ETA)の半数以上を支えるデータにアクセスできます。

カテゴリ

AeroAPIは、検索しやすくするためにいくつかのカテゴリに分類されています。

  • フライト:概要情報、計画ルート、位置情報など
  • Foresight:FlightAware Foresight™で強化されたフライト位置情報
  • 空港:空港情報とFIDSスタイルのリソース
  • 運航会社:運航会社情報と機材稼働状況リソース
  • アラート:フライトアラートと配信先設定
  • 履歴:各種エンドポイント向け過去フライトデータ
  • その他:フライト遅延情報、将来のスケジュール情報、航空機所有者情報
  • アカウント:AeroAPI利用統計

今回は、HND(羽田空港)を例に、今日の便一覧と路線スケジュールを取る2パターンを紹介します。

パターン① 空港発着便リスト

/airports/{id}/flights/departures と /arrivals

用途:

  • 「今日の羽田発着全便をCSVにまとめたい」
  • 「運航実績や遅延を含めて知りたい」

特徴:

  • 直近3日間程度のデータを取得可能
  • 実際の運航ステータスや機材が取れる

Pythonサンプルコード(CSV出力付き)

使い方

  • 今日のRJTT:python flight_list.py

  • 指定日のRJTT:python flight_list.py 2025-10-05

  • 指定日・指定空港:python flight_list.py 2025-10-05 RJOI


import sys
import csv
import time
import random
import requests
import datetime as dt
import zoneinfo
from urllib.parse import urlencode

API_KEY = "YOUR_AEROAPI_KEY"
BASE = "https://aeroapi.flightaware.com/aeroapi"

# 任意で空港ラベルを追加してください
AIRPORT_LABELS = {
    "RJTT": "Tokyo Haneda",
    "RJOI": "Iwakuni",
    "RJAA": "Tokyo Narita",
    "ROAH": "Naha",
}

# レート制限対策:ページ間/窓間のウェイトやリトライ回数
PAGE_SLEEP_SEC = 0.3
WINDOW_SLEEP_SEC = 1.0
MAX_RETRIES = 7

# JSTでの時間窓(初期は4分割)
WINDOWS_JST = [
    (dt.time(0, 0, 0),  dt.time(5, 59, 59)),
    (dt.time(6, 0, 0),  dt.time(11, 59, 59)),
    (dt.time(12, 0, 0), dt.time(17, 59, 59)),
    (dt.time(18, 0, 0), dt.time(23, 59, 59)),
]


def parse_args():
    """
    使い方:
      python flight_list.py                 → 今日, 既定空港(RJTT)
      python flight_list.py 2025-10-05     → 指定日, 既定空港(RJTT)
      python flight_list.py 2025-10-05 RJTT → 指定日, 指定空港
    """
    tz_jst = zoneinfo.ZoneInfo("Asia/Tokyo")
    target_date = dt.datetime.now(tz_jst).date()
    airport_id = "RJTT"

    if len(sys.argv) >= 2:
        a1 = sys.argv[1]
        # YYYY-MM-DD 判定
        try:
            y, m, d = map(int, a1.split("-"))
            target_date = dt.date(y, m, d)
            if len(sys.argv) >= 3:
                airport_id = sys.argv[2].upper()
        except ValueError:
            # 1引数目が日付でなければ空港コードとして扱う
            airport_id = a1.upper()
    return target_date, airport_id


def to_utc_iso(jst_date, t0, t1, tz_jst):
    s_jst = dt.datetime.combine(jst_date, t0, tzinfo=tz_jst)
    e_jst = dt.datetime.combine(jst_date, t1, tzinfo=tz_jst)
    s_utc = s_jst.astimezone(dt.timezone.utc).isoformat().replace("+00:00", "Z")
    e_utc = e_jst.astimezone(dt.timezone.utc).isoformat().replace("+00:00", "Z")
    return s_utc, e_utc


def request_with_backoff(url, headers, params=None, max_retries=MAX_RETRIES, stage=""):
    """
    429/5xx をリトライ。Retry-After があれば尊重、なければ指数バックオフ+ジッター。
    """
    attempt = 0
    while True:
        attempt += 1
        if attempt > 1:
            print(f"  retry {attempt-1}/{max_retries} ({stage})")
        r = requests.get(url, headers=headers, params=params, timeout=30)
        sc = r.status_code
        print(f"HTTP {sc}")
        if sc == 200:
            return r.json()
        if sc in (429, 500, 502, 503, 504) and attempt <= max_retries:
            ra = r.headers.get("Retry-After")
            if ra:
                try:
                    sleep_sec = float(ra)
                except Exception:
                    sleep_sec = 5.0
            else:
                base = 2 ** (attempt - 1)
                sleep_sec = base + random.uniform(0, 0.5 * base)
            print(f"  backoff {sleep_sec:.1f}s (stage={stage})")
            time.sleep(sleep_sec)
            continue
        r.raise_for_status()


def resolve_next_url(next_raw, base_endpoint):
    """
    links.next を安全に絶対URL化。/aeroapi を必ず保持。
    想定される形式:
      - 絶対: https://aeroapi.flightaware.com/aeroapi/airports/...
      - 先頭?: ?cursor=xxx
      - 先頭/: /airports/... or /aeroapi/airports/...
      - 相対: airports/...
    """
    if not next_raw:
        return None
    if next_raw.startswith("http"):
        return next_raw
    if next_raw.startswith("?"):
        return f"{BASE}{base_endpoint}{next_raw}"
    if next_raw.startswith("/"):
        if next_raw.startswith("/aeroapi/"):
            return f"https://aeroapi.flightaware.com{next_raw}"
        return f"https://aeroapi.flightaware.com/aeroapi{next_raw}"
    return f"{BASE}/{next_raw.lstrip('/')}"



def fetch_all_window(endpoint, key_name, start_utc, end_utc, headers, page_limit=100):
    """
    1つの時間窓で全ページ取得(到着・出発兼用)
    """
    params = {"start": start_utc, "end": end_utc}
    url = f"{BASE}{endpoint}"
    page = 0
    items = []

    while url and page < page_limit:
        page += 1
        q = f"?{urlencode(params)}" if page == 1 else ""
        print(f"\n--- Fetching {key_name} page {page} ---")
        print(f"URL: {url}{q}")
        data = request_with_backoff(url, headers, params if page == 1 else None, stage=f"{key_name} p{page}")
        chunk = data.get(key_name, [])
        print(f"{key_name}件数(this page): {len(chunk)}")
        items.extend(chunk)

        next_raw = (data.get("links") or {}).get("next")
        print(f"next(raw): {next_raw}")
        url = resolve_next_url(next_raw, endpoint) if next_raw else None

        time.sleep(PAGE_SLEEP_SEC)

    print(f"合計 {key_name}件数(window): {len(items)}")
    return items


def main():
    tz_jst = zoneinfo.ZoneInfo("Asia/Tokyo")
    target_date, airport_id = parse_args()
    airport_label = AIRPORT_LABELS.get(airport_id, airport_id)

    headers = {"x-apikey": API_KEY}

    print(f"=== {airport_label} ({airport_id}) Flight List (paged + windowed) ===")
    print(f"対象日(JST): {target_date}")

    # 窓ごとに「出発→到着」を交互に叩いてレート分散
    collected_dep = []
    collected_arr = []

    for i, (t0, t1) in enumerate(WINDOWS_JST, 1):
        s_utc, e_utc = to_utc_iso(target_date, t0, t1, tz_jst)

        print(f"\n=== departures window {i}/{len(WINDOWS_JST)} ===")
        print(f"JST {t0}{t1} → UTC {s_utc}{e_utc}")
        dep_part = fetch_all_window(f"/airports/{airport_id}/flights/departures", "departures",
                                    s_utc, e_utc, headers)
        collected_dep.extend(dep_part)

        print(f"\n=== arrivals window {i}/{len(WINDOWS_JST)} ===")
        print(f"JST {t0}{t1} → UTC {s_utc}{e_utc}")
        arr_part = fetch_all_window(f"/airports/{airport_id}/flights/arrivals", "arrivals",
                                    s_utc, e_utc, headers)
        collected_arr.extend(arr_part)

        time.sleep(WINDOW_SLEEP_SEC)

    print(f"\n*** departures total (all windows): {len(collected_dep)} ***")
    print(f"*** arrivals   total (all windows): {len(collected_arr)} ***")

    # 正規化
    def g(d, *keys):
        cur = d
        for k in keys:
            cur = (cur or {}).get(k)
        return cur

    def norm(rec, kind_key):
        return {
            "kind": "departure" if kind_key == "departures" else "arrival",
            "fa_flight_id": rec.get("fa_flight_id"),
            "ident": rec.get("ident"),
            "operator": rec.get("operator"),
            "codeshares": ",".join(rec.get("codeshares", [])) if rec.get("codeshares") else None,
            "origin_icao": g(rec, "origin", "code_icao"),
            "origin_iata": g(rec, "origin", "code_iata"),
            "destination_icao": g(rec, "destination", "code_icao"),
            "destination_iata": g(rec, "destination", "code_iata"),
            "scheduled_off": rec.get("scheduled_off"),
            "estimated_off": rec.get("estimated_off"),
            "actual_off": rec.get("actual_off"),
            "scheduled_on": rec.get("scheduled_on"),
            "estimated_on": rec.get("estimated_on"),
            "actual_on": rec.get("actual_on"),
            "status": rec.get("status"),
            "aircraft_type": rec.get("aircraft_type"),
            "route_distance_nm": rec.get("distance"),
        }

    print("\nデータ正規化中…")
    rows = [norm(r, "departures") for r in collected_dep] + [norm(r, "arrivals") for r in collected_arr]
    print(f"正規化後レコード: {len(rows)}")

    outname = f"{airport_id}_flights_{target_date.strftime('%Y%m%d')}.csv"
    cols = list(rows[0].keys()) if rows else [
        "kind", "fa_flight_id", "ident", "operator", "codeshares", "origin_icao", "origin_iata",
        "destination_icao", "destination_iata", "scheduled_off", "estimated_off", "actual_off",
        "scheduled_on", "estimated_on", "actual_on", "status", "aircraft_type", "route_distance_nm"
    ]

    print(f"\nCSV出力: {outname}")
    with open(outname, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=cols)
        w.writeheader()
        for i, r in enumerate(rows, 1):
            w.writerow(r)
            if i % 100 == 0:
                print(f"  ...{i}件書き込み")

    print(f"\n✅ 完了: {outname}  (総レコード: {len(rows)})")


if __name__ == "__main__":
    main()


パターン② フライトスケジュール

/schedules/{date_start}/{date_end}

用途:

  • 「ダイヤ(定期便)を調べたい」
  • 「来月の羽田→伊丹便の本数を調べたい」

特徴:

  • 90日先までの予定ダイヤを取得可能
  • 路線(origin+destination)を指定

Pythonサンプルコード

import requests
import csv
import os

API_KEY = "YOUR_AEROAPI_KEY"
BASE = "https://aeroapi.flightaware.com/aeroapi"

headers = {"x-apikey": API_KEY}

url = f"{BASE}/schedules/2025-10-01/2025-10-02"
params = {
    "origin": "RJTT",   # 羽田
    "destination": "RJOO",  # 伊丹
}

response = requests.get(url, headers=headers, params=params)
data = response.json()

# スケジュール配列を抽出
flights = data.get("scheduled", [])

# 出力フォルダを作成
os.makedirs("out", exist_ok=True)
outfile = "out/flight_schedules_simple.csv"

# CSV書き出し
with open(outfile, "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow([
        "ident", "actual_ident", "aircraft_type",
        "scheduled_out", "scheduled_in",
        "origin_icao", "destination_icao",
        "meal_service", "seats_cabin_first",
        "seats_cabin_business", "seats_cabin_coach",
    ])
    for flight in flights:
        writer.writerow([
            flight.get("ident"),
            flight.get("actual_ident"),
            flight.get("aircraft_type"),
            flight.get("scheduled_out"),
            flight.get("scheduled_in"),
            flight.get("origin_icao"),
            flight.get("destination_icao"),
            flight.get("meal_service"),
            flight.get("seats_cabin_first"),
            flight.get("seats_cabin_business"),
            flight.get("seats_cabin_coach"),
        ])

print(f"✅ CSVファイルを保存しました: {outfile} ({len(flights)}件)")

アウトプット

ident,actual_ident,aircraft_type,scheduled_out,scheduled_in,origin_icao,destination_icao,meal_service,seats_cabin_first,seats_cabin_business,seats_cabin_coach
ANA17,,B772,2025-10-01T00:00:00Z,2025-10-01T01:05:00Z,RJTT,RJOO,ファースト: 食事なし / エコノミー: 食事なし,28,0,364
THA6064,ANA17,B772,2025-10-01T00:00:00Z,2025-10-01T01:05:00Z,RJTT,RJOO,ファースト: 食事なし / エコノミー: 食事なし,28,0,364
JAL111,,B788,2025-10-01T00:30:00Z,2025-10-01T01:40:00Z,RJTT,RJOO,ファースト: 食事 / ビジネス: 食事なし / エコノミー: 食事なし,6,58,227
BAW4627,JAL111,B788,2025-10-01T00:30:00Z,2025-10-01T01:40:00Z,RJTT,RJOO,ファースト: 食事 / ビジネス: 食事 / エコノミー: 食事,6,58,227
BKP4150,JAL111,B788,2025-10-01T00:30:00Z,2025-10-01T01:40:00Z,RJTT,RJOO,ファースト: 食事なし / ビジネス: 食事なし / エコノミー: 食事なし,6,58,227
ASA7561,JAL111,B788,2025-10-01T00:30:00Z,2025-10-01T01:40:00Z,RJTT,RJOO,ファースト: 食事なし / ビジネス: 食事なし / エコノミー: 食事なし,6,58,227
HAL5019,JAL111,B788,2025-10-01T00:30:00Z,2025-10-01T01:40:00Z,RJTT,RJOO,ファースト: 食事なし / ビジネス: 食事なし / エコノミー: 食事なし,6,58,227
DLH4854,ANA19,,2025-10-01T01:00:00Z,2025-10-01T02:05:00Z,RJTT,RJOO,ファースト: 食事なし / エコノミー: 食事なし,10,0,260
ANA19,,,2025-10-01T01:00:00Z,2025-10-01T02:05:00Z,RJTT,RJOO,ファースト: 食事なし / エコノミー: 食事なし,10,0,260
AAL8397,JAL113,B788,2025-10-01T01:30:00Z,2025-10-01T02:35:00Z,RJTT,RJOO,ファースト: 食事 / ビジネス: 食事 / エコノミー: 食事,6,58,227
JAL113,,B788,2025-10-01T01:30:00Z,2025-10-01T02:35:00Z,RJTT,RJOO,ファースト: 食事 / ビジネス: 食事なし / エコノミー: 食事なし,6,58,227
HAL5021,JAL113,B788,2025-10-01T01:30:00Z,2025-10-01T02:35:00Z,RJTT,RJOO,ファースト: 食事なし / ビジネス: 食事なし / エコノミー: 食事なし,6,58,227
ASA7563,JAL113,B788,2025-10-01T01:30:00Z,2025-10-01T02:35:00Z,RJTT,RJOO,ファースト: 食事なし / ビジネス: 食事なし / エコノミー: 食事なし,6,58,227
AMX7752,JAL113,B788,2025-10-01T01:30:00Z,2025-10-01T02:35:00Z,RJTT,RJOO,ビジネス: 食事なし / エコノミー: 食事なし,0,38,123
AIC8007,ANA21,,2025-10-01T02:00:00Z,2025-10-01T03:05:00Z,RJTT,RJOO,ファースト: 食事なし / エコノミー: 食事なし,10,0,260

使い分けまとめ

発着便一覧が欲しい → /airports/{id}/flights/departures & arrivals

ダイヤ(予定便)を知りたい → /schedules/{date_start}/{date_end}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?