Self-Hosted Flight Fare Monitor Pulls Real Data From 6 Platforms and 10 Channels
A self-hosted multi-channel flight price monitor: 6 platforms, 10 channels, collecting real flight details, supporting same-flight cross-source price comparison, price calendar/K-line, and alert notifications. Data stays on your machine, configuration is all in the web UI, Apache-2.0.
- Repo: https://github.com/dengmeiluan/ticket-monitoring
- Online demo: https://dengmeiluan.github.io/ticket-monitoring/site/
- Releases: https://github.com/dengmeiluan/ticket-monitoring/releases
Why I built it
I always thought connecting flights were cheaper than direct ones. Then I checked Shanghai→Tokyo on 11/21:
| Item | Direct | Connecting |
|---|---|---|
| Flight | IJ004 | GS7884 / GS7989 |
| Time | 17:30→21:30 (3h) | 12h45 |
| Price | ¥975 | ¥1414 |
| Baggage | No free checked luggage | Includes 1 piece 23kg |
| Seats left | 9 | — |
Looking only at price, direct ¥975 is ¥439 cheaper than connecting ¥1414; but the direct flight has no checked baggage, while the connecting flight includes one 23kg piece, so the two aren't directly comparable.
Worse, this information isn't on one page: calendar price ¥1005, detail price ¥975; different sources give different prices, baggage, cabin class, and seat availability; PC and App prices can also differ.
To avoid refreshing eight times a day, I wrote a web page that connects 6 platforms and 10 channels into one view. Later I open-sourced it.
Features overview
- Platforms: Ctrip, Qunar, Fliggy, Tongcheng, Tuniu, Zhixing
- Channels: 5 browser + 5 App; Apps off by default, enabled on demand; when off, no imports, no device detection, no UI display
- Domestic/international: same collection, judgment, and push pipeline
- Details: price, flight number, departure/arrival times, stops, duration, terminal, cabin, aircraft, on-time rate, baggage
- Price comparison: same flight side-by-side across sources, who has a price, who doesn't, who writes "not provided", at a glance
- Calendar/K-line: 48 hours to six months; low points marked to a specific time; calendar price does not participate in alert judgment
- Alerts: DingTalk, Windows popup, ntfy, Bark, Alibaba Cloud phone/SMS
- Monitoring: multi-user, multi-route, multi-date; independent psychological price lines for direct/connecting; airline whitelist, baggage/time-window/connection/next-day gates; routes can be paused
- Deal radar: ten sources of official promo calendars (5 web + 5 App)
- Observation: real search console, original page mirror, three-way alignment, channel health, runtime pulse, CSV/evidence JSON export

Channels and endpoints
Among the 6 platforms, Ctrip, Qunar, Fliggy, and Tongcheng have both web and App endpoints; Zhixing has only App; Tuniu currently only has web (the App won't start on MuMu).
App channels are optional: off by default, enabled only when you need App-exclusive prices or Zhixing. Enabling App channels requires local MuMu (Android, ~15GB, Windows, requires VT); when off, the framework never touches it.
For channel capabilities, the project maintains a matrix of 10 channels × 7 dimensions, each cell tri-state: supported / unsupported / unverified. Unverified is not treated as supported. The dimensions that App channels still lack compared to browser channels are listed separately, not mixed in to gloss over gaps.

Technical implementation
Overall pipeline:
scheduler → channel collection (browser / App) → unified schema → SQLite(prices.db)
→ judgment / push / price calendar / K-line / deal radar
Several key points:
- Channel adaptation. Each of the 5 browser channels takes the most stable path: Qunar H5, Ctrip international PC/domestic H5, Tongcheng H5, Fliggy SSR, Tuniu HTTP; PC web services that are discontinued fall back to H5. Each channel's login slot is managed separately.
- App channels. MuMu Android emulator + adb; WebView results read via uiautomator2, canvas pages via OCR. Login happens on an embedded real phone screen; the login state is packaged into a snapshot and restored before collection; when a snapshot expires, the config page flags it, and it won't pretend to work.
- Unified schema. Price, flight number, times, stops, duration, terminal, cabin, aircraft, on-time rate, baggage; both raw rows and parsed rows are retained, and search details do a "parsed ⇄ mirror(raw) ⇄ RAW path" three-way alignment.
- Data model. Each scan writes every flight into flight_daily, independent of whether it's watched; watched flights are a subset of the configured route + date detail set. Thin calendar rows (no flight number/times) never trigger alert judgments and don't participate in same-flight price tiers.
- Scheduling and rate limiting. Multi-user same-route de-duplication, staggered queries; failures classified separately (login/captcha/soft block/timeout); no captcha bypass, stops and waits for human handling when a captcha or login appears.
- UI. A single-page console rendered with pure standard libraries, sub-views for workbench and panel-style configuration; hot-reload of config, mobile-friendly; Docker image includes Chromium and Chinese fonts.

Test data
I ran a round with the project's active probe: Shanghai→Tokyo, 2026-11-21, collected at 2026-11-04 12:32.
That round pulled 282 real details: Ctrip 163, Tuniu 119; the other 3 sources didn't supply any this round, so they were left blank, no fake samples. Calendar price ¥1005, direct flight detail ¥975—calendar is for picking dates; always click into details before ordering.
Deployment
- No Python install: download the platform package from Releases, unzip and run, open http://127.0.0.1:8765 in a browser
- Source:
pip install -r requirements.txt && python main.py - Docker / NAS:
cp config.example.yaml config.yaml
docker compose up -d
Open http://NAS_IP:8765 in a browser and it's ready. Config, price database, browser session archives, and logs all land on local volumes; container upgrade/rebuild doesn't lose data; image includes Chromium and Chinese fonts, first startup doesn't need to download a kernel.
Known limitations
- All channels require login to show prices; sessions expire, and when invalid you need to re-login on the config page.
- App channels need MuMu; when off, web channels are unaffected.
- Only monitors and alerts, does not place orders for you, no auto-ticket-grabbing.
- No captcha bypass; stops and waits for human handling when captcha or login appears.
- Multi-leg whole orders currently only verified for Ctrip 2 segments; 3 segments and other platforms not connected.
- Channel page structures change, collection code needs maintenance; "unverified" in the matrix is not treated as supported.
Feedback
The project is Apache-2.0, fully open source. Run into issues, want new channels/routes, feel free to open an issue or comment; I'll try to fix.
GitHub: https://github.com/dengmeiluan/ticket-monitoring
Prices change; actual order is final. Data in this article comes from real collection at 2026-11-04 12:32, only for explaining the monitoring methodology.