118 lines
4.0 KiB
JavaScript
118 lines
4.0 KiB
JavaScript
"use strict";
|
|
|
|
const { withBrowser, navigateWithHumanBehavior, extractPricesFromPage } = require("./baseCrawler");
|
|
|
|
/**
|
|
* Formats a date as YYYYMMDD for Naver Flights URLs.
|
|
*/
|
|
function getDStr(date) {
|
|
const d = new Date(date);
|
|
const yy = d.getFullYear();
|
|
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
const dd = String(d.getDate()).padStart(2, "0");
|
|
return `${yy}${mm}${dd}`;
|
|
}
|
|
|
|
/**
|
|
* Maps internal cabin class to Naver fareType code.
|
|
* economy → Y, premium_economy → P, business → C, first → F
|
|
*/
|
|
function getFareType(byCabin) {
|
|
if (byCabin.first > 0) return "F";
|
|
if (byCabin.business > 0) return "C";
|
|
if (byCabin.premium_economy > 0) return "P";
|
|
return "Y";
|
|
}
|
|
|
|
/**
|
|
* Builds a Naver Flights URL from structured search params.
|
|
*
|
|
* Round-trip format:
|
|
* /flights/international/ICN-NRT-20260315/NRT-ICN-20260322?adult=1&fareType=Y
|
|
*
|
|
* Multi-city format:
|
|
* /flights/multi?itinerary=ICN:NRT:20260315,NRT:ICN:20260322&adult=1&fareType=Y
|
|
*
|
|
* One-way format:
|
|
* /flights/international/ICN-NRT-20260315?adult=1&fareType=Y
|
|
*/
|
|
function buildNaverUrl(searchParams) {
|
|
const segments = searchParams.segments || [];
|
|
if (segments.length === 0) return "https://flight.naver.com";
|
|
|
|
const tripType = searchParams.tripType;
|
|
const adults = searchParams.passengers?.total || 1;
|
|
const byCabin = searchParams.passengers?.byCabin || {};
|
|
const fareType = getFareType(byCabin);
|
|
|
|
const today = new Date();
|
|
const tomorrow = new Date(today);
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
if (tripType === "round_trip") {
|
|
const from = segments[0].from.toUpperCase();
|
|
const to = segments[0].to.toUpperCase();
|
|
const departDate = searchParams.departureDateWindow?.from || tomorrow;
|
|
const d1 = getDStr(departDate);
|
|
|
|
// Return date: use stayDurationDays if available, default to 7 days
|
|
const stayDays = searchParams.stayDurationDays?.minDays || 7;
|
|
const returnDate = new Date(departDate);
|
|
returnDate.setDate(returnDate.getDate() + stayDays);
|
|
const d2 = getDStr(returnDate);
|
|
|
|
// Naver round-trip: outbound segment / return segment (origin/dest reversed)
|
|
return `https://flight.naver.com/flights/international/${from}-${to}-${d1}/${to}-${from}-${d2}?adult=${adults}&fareType=${fareType}`;
|
|
}
|
|
|
|
if (tripType === "multi_city") {
|
|
// Naver multi-city: /flights/multi?itinerary=ICN:NRT:20260315,NRT:ICN:20260322
|
|
let currentBaseDate = new Date(searchParams.departureDateWindow?.from || tomorrow);
|
|
const itineraryParts = [];
|
|
for (const seg of segments) {
|
|
itineraryParts.push(`${seg.from.toUpperCase()}:${seg.to.toUpperCase()}:${getDStr(currentBaseDate)}`);
|
|
currentBaseDate.setDate(currentBaseDate.getDate() + 3);
|
|
}
|
|
return `https://flight.naver.com/flights/multi?itinerary=${itineraryParts.join(",")}&adult=${adults}&fareType=${fareType}`;
|
|
}
|
|
|
|
// one_way
|
|
const from = segments[0].from.toUpperCase();
|
|
const to = segments[0].to.toUpperCase();
|
|
const d1 = searchParams.departureDateWindow?.from ? getDStr(searchParams.departureDateWindow.from) : getDStr(tomorrow);
|
|
return `https://flight.naver.com/flights/international/${from}-${to}-${d1}?adult=${adults}&fareType=${fareType}`;
|
|
}
|
|
|
|
async function scrapeNaver(searchParams) {
|
|
const url = buildNaverUrl(searchParams);
|
|
console.log(`[Naver] Navigating to ${url}`);
|
|
|
|
return withBrowser(async (page) => {
|
|
await navigateWithHumanBehavior(page, url);
|
|
|
|
// Wait for price elements with generic selectors
|
|
try {
|
|
await page.waitForSelector('[class*="price"], [class*="Price"], [class*="fare"]', { timeout: 15000 });
|
|
} catch (e) {
|
|
console.log("[Naver] Timeout waiting for price elements.");
|
|
}
|
|
|
|
// Extract prices using generic Korean won patterns
|
|
const prices = await extractPricesFromPage(page);
|
|
|
|
if (prices.length === 0) {
|
|
console.log("[Naver] No prices found on page.");
|
|
return [];
|
|
}
|
|
|
|
return prices.map((price, i) => ({
|
|
provider: "naver",
|
|
price,
|
|
currency: "KRW",
|
|
metadata: { url, rank: i + 1 },
|
|
}));
|
|
});
|
|
}
|
|
|
|
module.exports = { scrapeNaver, buildNaverUrl };
|