chore: 현재 작업 중간 커밋
This commit is contained in:
117
src/crawlers/naver.js
Normal file
117
src/crawlers/naver.js
Normal file
@@ -0,0 +1,117 @@
|
||||
"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 };
|
||||
Reference in New Issue
Block a user