Update:
去年AA大幅增强风控,导致之前大佬分享的全自动脚本不再能用,会一直报错说access blocked。
基于这个稍微改了几个版本,这里分享个半自动,但也是用起来最最方便的轮椅,至少大家能自己动手丰衣足食
原理其实很简单,现在搜票api不能单独发了,需要cookie和credentials,我们直接从chrome里偷来发api reqest。
注意事项
-
还是不能发太多request,暂时没试出来准确limit,同ip发多了会被封ip+brower,换个brower能破。被封的过段时间也会自己解开,具体时间不记得了,好像是一天?
-
这个需要手动自己每次跑,跑的时候重新execute。也可以改成loop,但是cookie可能会过期,而且被封的概率大幅增加
使用步骤
-
打开 aa.com
-
F12打开console,第一次execute command 需要手动允许一下
-
复制这段代码跑就行了,记得改下参数, 在最上面几行,比较self explanatory。 主要就是改你的起飞机场和日期
-
-
// === AA Award Calendar Scanner - Run in browser console on aa.com === (async function() { // ================== CONFIGURATION ================== const fromYear = 2026; const fromMonth = 2; // January = 1 const toMonth = 1; // const origins = ['JFK', 'TYO']; const destinations = ['TYO', 'JFK']; const cabin = 'BUSINESS,FIRST'; // Options: "COACH", "PREMIUM_COACH", "BUSINESS,FIRST" const maxStops = '1'; // "0", "1", "2" const includeLink = true; const delayMs = 300; // Delay between requests (ms) - do not set to 0 // =================================================== const TYO = new Set(['NRT', 'HND']); function isSameCity(a, b) { return a === b || (TYO.has(a) && TYO.has(b)); } // Build list of dates to query (first day of each month) const dates = []; for (let year = fromYear; year <= (fromMonth <= toMonth ? fromYear : fromYear + 1); year++) { const startM = year === fromYear ? fromMonth : 1; const endM = year === fromYear && fromMonth <= toMonth ? toMonth : (year > fromYear ? toMonth : 12); for (let month = startM; month <= endM; month++) { dates.push(`${year}-${String(month).padStart(2, '0')}-01`); } } console.log(`Starting scan: ${dates.length} months × ${origins.length} origins × ${destinations.length} destinations / 2 (roundtrip) = ${dates.length * origins.length * destinations.length/2} total requests`); const results = []; let totalRequests = 0; for (const depDate of dates) { for (const origin of origins) { for (const dest of destinations) { if (isSameCity(origin, dest)) continue; totalRequests++; await new Promise(r => setTimeout(r, delayMs)); let foundInThisMonth = 0; try { const response = await fetch("https://www.aa.com/booking/api/search/calendar", { "headers": { "accept": "application/json, text/plain, */*", "accept-language": "en-US", "content-type": "application/json", "cache-control": "no-cache", "pragma": "no-cache", "priority": "u=1, i", "sec-ch-ua": "\"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not_A Brand\";v=\"99\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"macOS\"", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin" }, "referrer": "https://www.aa.com/booking/choose-flights", "body": JSON.stringify({ "metadata": { "selectedProducts": [], "tripType": "OneWay", "udo": {} }, "passengers": [{ "type": "adult", "count": 1 }], "requestHeader": { "clientId": "AAcom" }, "slices": [{ "allCarriers": true, "cabin": cabin, "departureDate": depDate, "destination": dest, "destinationNearbyAirports": false, "maxStops": maxStops, "origin": origin, "originNearbyAirports": false }], "tripOptions": { "corporateBooking": false, "fareType": "Lowest", "locale": "en_US", "pointOfSale": null, "searchType": "Award" }, "loyaltyInfo": null, "version": "", "queryParams": { "sliceIndex": 0, "sessionId": "", "solutionSet": "", "solutionId": "" } }), "method": "POST", "mode": "cors", "credentials": "include" }); if (!response.ok) { console.log(`⚠️ ${depDate} ${origin}→${dest}: HTTP ${response.status} (skipped)`); continue; } const data = await response.json(); for (const month of data.calendarMonths || []) { for (const week of month.weeks || []) { for (const day of week.days || []) { if (!day.validDay) continue; const solution = day.solution; if (solution && solution.perPassengerAwardPoints < 100000) { foundInThisMonth++; const bookUrl = `https://www.aa.com/booking/search?type=OneWay&searchType=Award&from=${origin}&to=${dest}&pax=1&cabin=${cabin}&locale=en_US&nearbyAirports=false&depart=${day.date}&carriers=ALL&pos=US&adult=1`; // Detailed find with full link console.log(`🎯 Found: ${day.date} (${new Date(day.date).toLocaleDateString('en-US', { weekday: 'short' })}) ${origin}→${dest} | ${solution.perPassengerAwardPoints.toLocaleString()} points + $${solution.perPassengerSaleTotal?.amount || 0} | ${bookUrl}`); results.push({ date: day.date, route: `${origin} → ${dest}`, dayOfWeek: new Date(day.date).toLocaleDateString('en-US', { weekday: 'short' }), points: solution.perPassengerAwardPoints.toLocaleString(), cash: solution.perPassengerSaleTotal?.amount || 0, currency: solution.perPassengerSaleTotal?.currency || 'USD', link: includeLink ? `<a href="${bookUrl}" target="_blank">Book</a>` : '' }); } } } } } catch (err) { console.log(`💥 Error on ${depDate} ${origin}→${dest}: ${err.message}`); } // === PROGRESS UPDATE FOR THIS MONTH/ROUTE === if (foundInThisMonth > 0) { console.log(`✅ ${depDate} ${origin}→${dest}: Found ${foundInThisMonth} award dates`); } else { console.log(`❌ ${depDate} ${origin}→${dest}: No availability`); } } } } // ============= DISPLAY FINAL RESULTS ============= console.log(`\nScan complete! Processed ${totalRequests} requests.`); if (results.length === 0) { document.body.insertAdjacentHTML('beforeend', '<h2 style="color:orange">No award availability found in the scanned period 😔</h2>'); return; } results.sort((a, b) => a.date.localeCompare(b.date)); let tableHTML = ` <h2 style="color:green">🎉 Found ${results.length} Award Opportunities!</h2> <p><strong>Scanned period:</strong> ${dates[0]} to ${dates[dates.length-1].slice(0,7)}-31</p> <table border="1" cellpadding="8" cellspacing="0" style="border-collapse:collapse; font-family:Arial; background:white; margin-top:20px;"> <thead style="background:#f0f0f0"> <tr> <th>Date</th> <th>Day</th> <th>Route</th> <th>Points</th> <th>+ Cash</th> <th>Link</th> </tr> </thead> <tbody>`; for (const r of results) { tableHTML += ` <tr> <td>${r.date}</td> <td>${r.dayOfWeek}</td> <td>${r.route}</td> <td style="text-align:right; font-weight:bold">${r.points}</td> <td style="text-align:right">${r.cash} ${r.currency}</td> <td>${r.link}</td> </tr>`; } tableHTML += `</tbody></table>`; document.body.insertAdjacentHTML('afterbegin', tableHTML); console.log(`${results.length} total awards displayed in table above.`); })();
-
效果演示
Screenshot 2026-01-02 at 5.55.55 AM1920×1013 403 KB


