卡塔尔航空 机票查询采集 · qrx_runner
只查价、不抢票。给定航向 + 日期,抓取当天 flight-offers 接口数据,输出「全部航班 × 全部票价档」的中文 CSV 表。
底层用 DrissionPage 驱动浏览器 + 拟人鼠标轨迹,通过 Akamai _abck 校验。支持两种浏览器后端:
- 本地 Chrome(推荐,走家庭/住宅 IP)——新开的 AdsPower 环境若是裸 IP/机房 IP,常出现
_abck=0 仍被 403,住宅 IP 是更稳的解法,见「二·B」。
- AdsPower 指纹浏览器(需 profile 已养熟、且绑了靠谱代理 IP)。
一、目录里有什么
| 文件 |
必需 |
作用 |
human_mouse.py |
✅ 运行依赖 |
拟人鼠标轨迹引擎(用 run_cdp 发 trusted 事件) |
qrx_grabber.py |
✅ 第 1 步 |
多会话并发抓取,存原始 flight-offers JSON |
day_prices.py |
✅ 第 2 步 |
把 JSON 解析成中文票价表 CSV |
jobs.json |
✅ 配置 |
抓取任务:端口 / 航向 / 日期 |
qrx_*.json(原始)/ qrx_*_prices.csv / qrx_*_prices.json / qrx_summary.json / qrx_grabber.log |
❌ 产物 |
每次运行自动生成,可删 |
程序化接产物(都是 UTF-8,无乱码):qrx_*_prices.json(结构化航班×票价)、qrx_*_prices.csv(UTF-8-SIG)、qrx_summary.json(抓取状态)。建议程序直接读这三个文件,不要去抓控制台 stdout。
拷贝本目录到任意机器即可独立运行(__pycache__ 可忽略/删除)。
二、准备(一次性)
- 安装依赖:
pip install DrissionPage
- 在 AdsPower 中打开目标 profile(例如
k1hwxgd1),记下它的调试端口(默认形如 1384)。
脚本只连接该 CDP 端口,不需要 AdsPower 的 api-key。
- 确认端口可用(可选):
(Invoke-RestMethod "http://127.0.0.1:1384/json/version").Browser
返回 Chrome/... 即存活。
二·B、用本地 Chrome(住宅 IP,推荐)
新 AdsPower 环境常因裸 IP/机房 IP被 Akamai 边缘直接 403(即使 _abck=0)。本地 Chrome 走家庭宽带住宅 IP,起点信任高、养号快(实测十几秒就到 _abck=0)。
方式一(最稳,推荐):先在你自己的终端(非受限环境)用调试端口拉起一个独立用户数据的 Chrome:
& "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="<本目录>\.local_chrome_profile" --no-first-run --no-default-browser-check --disable-blink-features=AutomationControlled "https://www.qatarairways.com/en-us/homepage.html"
确认 http://127.0.0.1:9222/json/version 可连后,jobs.json 用 "port":"9222"(不写 mode,接管模式)跑。
方式二:jobs.json 里给任务加 "mode":"local",脚本会先探测 9222——已可连就直接接管,没开就自动用上面的参数拉起 Chrome。
注:方式二的“自动拉起”在普通终端里正常;若你把脚本跑在带 Job Object 的沙箱/CI 里,spawn 的子 Chrome 可能被回收导致端口起不来(报 chrome_launch_failed),此时改用方式一手动起 Chrome 再接管即可。
三、运行(一条命令跑完)
cd qrx_runner
python qrx_grabber.py jobs.json
跑完第 1 步抓取后,脚本会自动进入第 2 步:把每个抓下来的 JSON 解析成 *_prices.csv / *_prices.json,并打印 💰 价格提醒(该航线当天最低价 + 对应航班)。
- 汇总行:
共N任务:有航班 x / 无航班 y / 被拦或异常 z,并写 qrx_summary.json、qrx_grabber.log。
- 被拦/无航班的任务会被自动跳过解析(不会因单个任务报错而中断整条流程)。
- 若只想单独重新解析某个已存 JSON(不重新抓取),仍可用:
python day_prices.py qrx_out_a.json。CSV 用 Excel 直接打开(UTF-8-SIG,不乱码)。
四、配置 jobs.json 字段
数组,每项一个查询任务。同一个 port 的任务串行跑(同一会话不能并发),不同 port 并行跑。
| 字段 |
说明 |
port |
浏览器调试端口(字符串)。AdsPower 填其 debug_port;本地 Chrome 用 9222 |
mode |
可选。"local"=脚本自动拉起本机 Chrome(住宅 IP);不填=接管已打开的该端口(AdsPower 用这个) |
retries |
可选。被拦(403)后「冷却+补热」重试次数,默认 2 |
label |
任务标签,仅日志显示用 |
outfile |
抓下来的原始 JSON 存名 |
trip |
O=单程,R=往返。只查某天全量航班用 O 最直观 |
from / to |
出发 / 到达机场三字码(如 AGA / ABV) |
dep |
出发日期 YYYY-MM-DD |
ret |
返程日期,仅 trip=R 需要 |
url |
(可选)直接给完整查询 URL,给了就忽略上面的字段 |
示例(查 2026-11-28 AGA→ABV 当天全量航班):
[
{"port":"1384","label":"AGA-ABV-2026-11-28","outfile":"qrx_ret_a.json",
"trip":"O","from":"AGA","to":"ABV","dep":"2026-11-28"}
]
五、输出表列(中文表头)
航班序号, 航段, 中转次数, 总时长, 航班明细, 舱位, 票价族, 总价, 币种, 余座
- 每个航班最多 5 档票价:
ECONOMY_CLASSIC / ECONOMY_CONVENIENCE / ECONOMY_COMFORT + BUSINESS_COMFORT / BUSINESS_ELITE。
航班明细 形如 QR1434(ABV 14:05->DOH 22:30) QR4567(...),含航班号、起降机场与时间、跨天数(+1)。
六、三种结果状态(重要)
第 1 步 qrx_grabber.log / qrx_summary.json 里每个任务标一个 status:
| status |
含义 |
处理 |
OK |
flight-offers 200 且有航班 |
正常,直接跑第 2 步 |
NO_FLIGHTS |
flight-offers 200 但 flightOffers 为空——该日期/路线确实无可售航班(正常空结果,不是失败) |
day_prices.py 会输出「当天无可售航班」并给出 alertMessages 提示,CSV 仍生成(表头 + 一行"无航班") |
BLOCKED |
没拿到 200(Akamai 拦截 / _abck≠0 / 无响应 / 超时) |
见下方排错 |
「无航班」和「被拦截」是两回事:前者是查询成功、当天本就没票;后者是请求没过风控。脚本已分开统计,不会把空结果误报成成功,也不会因空结果报错。
七、排错
- 卡在 bootstrap 很久:正常。养
_abck 到 ~0~ 有时要 2~6 分钟(评分严时更久),别中途关掉浏览器或杀进程。
_abck 掉出 0:脚本会在任务之间自动回首页补热再打下一个,无需干预。
attach_failed / 端口连不上:AdsPower 里该 profile 没打开,或端口不对——重开 profile 并核对 jobs.json 的 port。
BLOCKED 持续出现:
_abck=0 但仍 403 = IP/代理层面被拒,不是养号问题:换住宅 IP(本地 Chrome)或给该 profile 换靠谱代理;反复硬打只会加重降权;
- 不要短时间内重复查同一日期/路线——同一会话反复请求同一深链会被判定异常并升分(甚至弹 reCAPTCHA)。换没查过的日期、任务间自然歇口气;
- 当天已打好几轮后 IP 会“疲劳”,休息一段时间(十几分钟~几小时)再查比连打成功率高得多;
- 降低并行度/频率(任务多时给每个会话少排点);
- 深链(如 flight-selection 结果页继续往下点选)易触发
_abck=-1,只查某天航班用单程 trip=O 首屏最稳。
- 弹 reCAPTCHA 登录/人机校验页:脚本不破解验证码;
dismiss_blockers 只在被跳到非预期页时自动退回首页、对可见弹层按一次接受/关闭(已做 12s 节流,不会机械重点)。反复弹说明 IP/会话被高度怀疑,靠降频+换住宅 IP 解决。
- 价格口径提醒:返回价格按出发国家计价、币种随出发地变(如尼日利亚出发=
NGN、摩洛哥出发=MAD);单程查询返回的就是那天各航班分档报价,无需也无法稳定拿到"往返合并总价"。
八、扩展到并发
一个 port = 一个独立会话(一套 IP + _abck)。要并发就在 AdsPower 多开几个 profile(各自建议绑独立代理 IP),把它们的端口都写进 jobs.json,qrx_grabber.py 会按端口起线程并行、会话内自动串行。
九、程序化运行 / 编码(GBK 乱码已解决)
脚本已内置两道修正,不管在哪跑都不会乱码:
sys.stdout.reconfigure(encoding="utf-8") — stdout 强制 UTF-8;
- Windows 下
SetConsoleOutputCP(65001) — 把控制台输出码页切为 UTF-8,交互终端里中文也能正常显示。
如果你用自己的程序调用(如另一段 Python 跑子进程),最稳的是直接读产物文件(UTF-8),而不是解析控制台:
import json
rows = json.load(open("qrx_out_a_prices.json", encoding="utf-8"))["rows"]
# rows: [{flight, route, stops, duration, segments, cabin, fare_family, total, currency, seats}, ...]
若确实需要拿 stdout,记得按 UTF-8 解码,例如:
import subprocess
out = subprocess.run(["python", "day_prices.py", "qrx_out_a.json"], capture_output=True)
print(out.stdout.decode("utf-8")) # ← 按 utf-8 解
CSV 用 utf-8-sig(带 BOM)写,Excel/WPS 双击不乱码;程序用 csv/pandas 读时编码传 utf-8-sig 即可。
交互终端手动补充(一般不需要,脚本已自动设码页):chcp 65001 或 $env:PYTHONUTF8=1。