# Поиск авиабилетов

Интеграция поиска авиабилетов — запросы по паре городов с сортировкой от самых дешёвых и деталями маршрута.



Готовые к запуску примеры поиска цен на авиабилеты: самые дешёвые и самые быстрые варианты, полные детали маршрута и сравнение для нескольких пассажиров. Определения полей см. в справочнике [Авиабилеты](/docs/v31/tickets). Используются коды IATA (не ICAO). Результаты кэшируются на 1 час — цены ориентировочные и могут измениться в момент бронирования.

| # | Вопрос                                                     | Используемые данные |
| - | ---------------------------------------------------------- | ------------------- |
| 1 | Найти самые дешёвые и самые быстрые варианты для JFK → LAX | GET /tickets/search |
| 2 | Показать полный маршрут с деталями пересадок для LHR → JFK | GET /tickets/search |
| 3 | Поиск для 2 пассажиров LHR → CDG — сравнить все варианты   | GET /tickets/search |

<DocRequirements>
  <DocRequirementAuth />
</DocRequirements>

### Установка [#install]

Приведённые ниже скрипты используют `requests` для вызовов API и `rich` для вывода в терминал. Установите один раз:

```bash
pip install requests rich
```

***

### 1. Самый дешёвый и самый быстрый: JFK → LAX [#1-cheapest-and-fastest-jfk--lax]

**Вопрос:** какие варианты билетов на маршруте JFK–LAX самые дешёвые и самые быстрые?

API возвращает результаты, отсортированные от самых дешёвых. Самый быстрый вариант найдите, просмотрев `total_duration_min`.

```python
import os

import requests
from rich.console import Console
from rich.panel import Panel

console = Console()

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"


def fetch_tickets(
    origin: str,
    destination: str,
    date: str | None = None,
    passengers: int = 1,
) -> dict | None:
    params: dict[str, str | int] = {
        "origin": origin,
        "destination": destination,
        "passengers": passengers,
    }
    if date:
        params["date"] = date
    r = requests.get(
        f"{BASE}/tickets/search",
        headers=HEADERS,
        params=params,
        timeout=(10, 15),
    )
    if r.status_code == 404:
        return None
    r.raise_for_status()
    return r.json()


def fmt_mins(minutes: int | None) -> str:
    if minutes is None:
        return "—"
    return f"{minutes // 60}h {minutes % 60:02d}m"


def stops_label(stops: int) -> str:
    return "Nonstop" if stops == 0 else f"{stops} stop(s)"


def flight_summary(flight: dict) -> str:
    legs = flight.get("legs", [])
    first_leg = legs[0] if legs else {}
    return (
        f"${flight.get('price_usd', 0):.2f}  ·  "
        f"{stops_label(flight.get('stops', 0))}  ·  "
        f"{fmt_mins(flight.get('total_duration_min'))}  ·  "
        f"{first_leg.get('flight_number', '—')} ({first_leg.get('airline', '—')})"
    )


def main() -> None:
    data = fetch_tickets("JFK", "LAX")
    if not data:
        console.print("[red]No ticket data returned.[/red]")
        return

    flights = data.get("flights", [])
    if not flights:
        console.print("[yellow]No flights available for JFK → LAX.[/yellow]")
        return

    cheapest = flights[0]
    fastest = min(flights, key=lambda f: f.get("total_duration_min") or 99999)

    console.print(
        Panel(
            f"[bold green]Cheapest[/bold green]\n{flight_summary(cheapest)}",
            title="JFK → LAX",
            style="green",
        )
    )

    if fastest is not cheapest:
        console.print(
            Panel(
                f"[bold cyan]Fastest[/bold cyan]\n{flight_summary(fastest)}",
                title="JFK → LAX",
                style="cyan",
            )
        )
    else:
        console.print("[dim]Cheapest option is also the fastest.[/dim]")


if __name__ == "__main__":
    main()
```

**Результат:** две панели — самый дешёвый и самый быстрый вариант, — в каждой показаны цена, количество пересадок, общая продолжительность, а также номер рейса и авиакомпания первого сегмента. Если самый дешёвый вариант совпадает с самым быстрым, показывается только одна панель.

***

### 2. Полный маршрут с деталями пересадок: LHR → JFK [#2-full-itinerary-with-layover-details-lhr--jfk]

**Вопрос:** как выглядит маршрут с пересадкой, включая каждый сегмент и время ожидания на пересадках?

Найдите в результатах первый вариант с пересадкой и выведите каждый сегмент отдельной строкой, затем перечислите пункты пересадок со временем ожидания.

```python
import os

import requests
from rich.console import Console
from rich.panel import Panel
from rich.table import Table

console = Console()

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"


def fetch_tickets(origin: str, destination: str) -> dict | None:
    r = requests.get(
        f"{BASE}/tickets/search",
        headers=HEADERS,
        params={"origin": origin, "destination": destination, "passengers": 1},
        timeout=(10, 15),
    )
    if r.status_code == 404:
        return None
    r.raise_for_status()
    return r.json()


def fmt_mins(minutes: int | None) -> str:
    if minutes is None:
        return "—"
    return f"{minutes // 60}h {minutes % 60:02d}m"


def print_itinerary(flight: dict, label: str) -> None:
    console.print(
        Panel(
            f"[bold]{label}[/bold]  ·  ${flight.get('price_usd', 0):.2f}  ·  "
            f"{fmt_mins(flight.get('total_duration_min'))}",
            style="blue",
        )
    )

    legs_table = Table(show_header=True, show_lines=True)
    legs_table.add_column("Airline", style="cyan")
    legs_table.add_column("Flight")
    legs_table.add_column("From")
    legs_table.add_column("To")
    legs_table.add_column("Departs")
    legs_table.add_column("Arrives")
    legs_table.add_column("Duration")

    for leg in flight.get("legs", []):
        legs_table.add_row(
            leg.get("airline", "—"),
            leg.get("flight_number", "—"),
            f"{leg.get('departure_airport', '—')} ({leg.get('departure_airport_name', '—')[:20]})",
            f"{leg.get('arrival_airport', '—')} ({leg.get('arrival_airport_name', '—')[:20]})",
            leg.get("departure_time", "—"),
            leg.get("arrival_time", "—"),
            fmt_mins(leg.get("duration_min")),
        )

    console.print(legs_table)

    layovers = flight.get("layovers", [])
    if layovers:
        console.print("[bold]Layovers[/bold]")
        for lw in layovers:
            console.print(
                f"  {lw.get('airport', '—')}  {lw.get('airport_name', '—')}  "
                f"[yellow]{fmt_mins(lw.get('duration_min'))} wait[/yellow]"
            )


def main() -> None:
    data = fetch_tickets("LHR", "JFK")
    if not data:
        console.print("[red]No ticket data returned.[/red]")
        return

    flights = data.get("flights", [])
    if not flights:
        console.print("[yellow]No flights available for LHR → JFK.[/yellow]")
        return

    connecting = next((f for f in flights if f.get("stops", 0) > 0), None)

    if connecting:
        print_itinerary(connecting, "Connecting Itinerary — LHR → JFK")
    else:
        console.print("[dim]No connecting flights in results — showing cheapest nonstop.[/dim]")
        print_itinerary(flights[0], "Nonstop — LHR → JFK")


if __name__ == "__main__":
    main()
```

**Результат:** карточка маршрута с каждым сегментом (авиакомпания, номер рейса, аэропорты, время вылета и прилёта, продолжительность), за которой следует список пересадок со временем ожидания в каждом пункте. Если вариантов с пересадкой нет, выводится самый дешёвый беспересадочный рейс.

***

### 3. Все варианты для 2 пассажиров: LHR → CDG [#3-all-options-for-2-passengers-lhr--cdg]

**Вопрос:** какие варианты билетов доступны для 2 пассажиров на маршруте LHR–CDG?

Выведите все возвращённые варианты в виде сравнительной таблицы. Показывайте исходную валюту, если она отличается от USD.

```python
import os

import requests
from rich.console import Console
from rich.table import Table

console = Console()

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"


def fetch_tickets(
    origin: str, destination: str, passengers: int
) -> dict | None:
    r = requests.get(
        f"{BASE}/tickets/search",
        headers=HEADERS,
        params={
            "origin": origin,
            "destination": destination,
            "passengers": passengers,
        },
        timeout=(10, 15),
    )
    if r.status_code == 404:
        return None
    r.raise_for_status()
    return r.json()


def fmt_mins(minutes: int | None) -> str:
    if minutes is None:
        return "—"
    return f"{minutes // 60}h {minutes % 60:02d}m"


def stops_label(stops: int) -> str:
    return "Nonstop" if stops == 0 else f"{stops} stop(s)"


def main() -> None:
    passengers = 2
    data = fetch_tickets("LHR", "CDG", passengers=passengers)
    if not data:
        console.print("[red]No ticket data returned.[/red]")
        return

    flights = data.get("flights", [])
    if not flights:
        console.print("[yellow]No flights available for LHR → CDG.[/yellow]")
        return

    table = Table(
        title=f"LHR → CDG — {len(flights)} option(s) for {passengers} passengers",
        show_lines=True,
    )
    table.add_column("#", justify="right", style="dim")
    table.add_column("Price (USD)", justify="right", style="bold green")
    table.add_column("Price/pax", justify="right")
    table.add_column("Duration", justify="center")
    table.add_column("Stops", justify="center")
    table.add_column("First Flight")

    for i, flight in enumerate(flights, start=1):
        price = flight.get("price_usd", 0.0)
        per_pax = price / passengers if passengers else price

        orig_price = flight.get("original_price")
        orig_currency = flight.get("original_currency", "USD")
        price_cell = f"${price:.2f}"
        if orig_currency and orig_currency != "USD" and orig_price:
            price_cell += f"\n[dim]({orig_price:.0f} {orig_currency})[/dim]"

        legs = flight.get("legs", [])
        first = legs[0] if legs else {}
        first_flight = (
            f"{first.get('flight_number', '—')} {first.get('airline', '—')}"
        )

        table.add_row(
            str(i),
            price_cell,
            f"${per_pax:.2f}",
            fmt_mins(flight.get("total_duration_min")),
            stops_label(flight.get("stops", 0)),
            first_flight,
        )

    console.print(table)
    console.print(
        "[dim]Prices are market snapshots — confirm at booking. "
        "Results cached for 1 hour.[/dim]"
    )


if __name__ == "__main__":
    main()
```

**Результат:** таблица со всеми доступными вариантами: общая цена, цена на одного пассажира, продолжительность, количество пересадок и номер рейса первого сегмента. Если исходная валюта отличается от USD, она показывается под ценой в USD.
