From 07d3e10056523f66b1f95f0858d10b148e7834b9 Mon Sep 17 00:00:00 2001 From: duanshuwen Date: Wed, 11 Mar 2026 21:25:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A3=9E=E7=8C=AA=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/scripts/fg_trace.mjs | 179 +++++++++++++++++++--------------- 1 file changed, 98 insertions(+), 81 deletions(-) diff --git a/src/main/scripts/fg_trace.mjs b/src/main/scripts/fg_trace.mjs index c263bd6..f840390 100644 --- a/src/main/scripts/fg_trace.mjs +++ b/src/main/scripts/fg_trace.mjs @@ -91,96 +91,113 @@ const toggleRoomByDateIndex = async (page, container, { roomType, dateIndex, ope }; (async () => { - const browser = await chromium.connectOverCDP(`http://127.0.0.1:9222`); - const context = browser.contexts()[0]; + let browser; - await context.addInitScript(() => { - Object.defineProperty(navigator, 'webdriver', { get: ()=> undefined }); - }); - - const pages = await context.pages(); + try { + browser = await chromium.connectOverCDP(`http://127.0.0.1:9222`); + const context = browser.contexts()[0]; - const page = pages.length ? pages[0] : await context.newPage(); - - await page.goto('https://hotel.fliggy.com/ebooking/hotelBaseInfoUv.htm#/ebk/homeV1'); - - const isLogin = await checkLoginStatus(page); - - if(!isLogin) { - await page.getByRole('textbox', { name: '请输入账号' }).dblclick(); - await page.getByRole('textbox', { name: '请输入账号' }).click(); - await page.getByRole('textbox', { name: '请输入账号' }).fill('hoteltmwq123', { delay: 80 + Math.random() * 120 }); - await page.waitForTimeout(1000 + Math.random() * 1000); - await page.getByRole('button', { name: '下一步' }).click(); - - const frame_1 = await page.frameLocator('#alibaba-login-box'); - await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).dblclick(); - await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).fill('Tmwq654321', { delay: 80 + Math.random() * 120 }); - await page.waitForTimeout(1000 + Math.random() * 1000); - await page.locator('#alibaba-login-box').contentFrame().getByRole('button', { name: '登录' }).click(); - - // 等待滑块真正出现在 DOM 并可见 - await page.waitForTimeout(4000 + Math.random() * 1000); - const frame_2 = await frame_1.frameLocator('#baxia-dialog-content'); - const container = await frame_2.locator('#nc_1_nocaptcha'); - const slider = await frame_2.locator('#nc_1_n1z'); - const isVisible = await slider.isVisible(); - - if (isVisible) { - // 重新获取滑块按钮(可能嵌套在 iframe 里) - const containerBox = await container.boundingBox(); - const sliderBox = await slider.boundingBox(); + await context.addInitScript(() => { + Object.defineProperty(navigator, 'webdriver', { get: ()=> undefined }); + }); - const startX = sliderBox.x + sliderBox.width / 2; - const startY = sliderBox.y + sliderBox.height / 2; - const distance = containerBox.width - sliderBox.width; // 适当拉长拖动距离 - const steps = 20; // 分多步模拟人手拖动 - - await page.mouse.move(startX, startY); - // 等待随机时间再开始滑动(模拟人类反应) - await page.waitForTimeout(200 + Math.random() * 300); - await page.mouse.down(); - await page.waitForTimeout(100 + Math.random() * 200); - - // 按轨迹滑动 - for (let i = 0; i < steps; i++) { - await page.mouse.move( - sliderBox.x + sliderBox.width / 2 + (distance * (i + 1) / steps), - sliderBox.y + sliderBox.height / 2 + (Math.random() * 5 - 2.5), // 模拟轻微Y轴抖动 - { steps: 5 } - ); + const pages = await context.pages(); + + const page = pages.length ? pages[0] : await context.newPage(); + + await page.goto('https://hotel.fliggy.com/ebooking/hotelBaseInfoUv.htm#/ebk/homeV1'); + + const isLogin = await checkLoginStatus(page); + + if(!isLogin) { + await page.getByRole('textbox', { name: '请输入账号' }).dblclick(); + await page.getByRole('textbox', { name: '请输入账号' }).click(); + await page.getByRole('textbox', { name: '请输入账号' }).fill('hoteltmwq123', { delay: 80 + Math.random() * 120 }); + await page.waitForTimeout(1000 + Math.random() * 1000); + await page.getByRole('button', { name: '下一步' }).click(); + + const frame_1 = await page.frameLocator('#alibaba-login-box'); + await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).dblclick(); + await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).fill('Tmwq654321', { delay: 80 + Math.random() * 120 }); + await page.waitForTimeout(1000 + Math.random() * 1000); + await page.locator('#alibaba-login-box').contentFrame().getByRole('button', { name: '登录' }).click(); + + // 等待滑块真正出现在 DOM 并可见 + await page.waitForTimeout(4000 + Math.random() * 1000); + const frame_2 = await frame_1.frameLocator('#baxia-dialog-content'); + const container = await frame_2.locator('#nc_1_nocaptcha'); + const slider = await frame_2.locator('#nc_1_n1z'); + const isVisible = await slider.isVisible(); + + if (isVisible) { + // 重新获取滑块按钮(可能嵌套在 iframe 里) + const containerBox = await container.boundingBox(); + const sliderBox = await slider.boundingBox(); + + const startX = sliderBox.x + sliderBox.width / 2; + const startY = sliderBox.y + sliderBox.height / 2; + const distance = containerBox.width - sliderBox.width; // 适当拉长拖动距离 + const steps = 20; // 分多步模拟人手拖动 + + await page.mouse.move(startX, startY); + // 等待随机时间再开始滑动(模拟人类反应) + await page.waitForTimeout(200 + Math.random() * 300); + await page.mouse.down(); + await page.waitForTimeout(100 + Math.random() * 200); + + // 按轨迹滑动 + for (let i = 0; i < steps; i++) { + await page.mouse.move( + sliderBox.x + sliderBox.width / 2 + (distance * (i + 1) / steps), + sliderBox.y + sliderBox.height / 2 + (Math.random() * 5 - 2.5), // 模拟轻微Y轴抖动 + { steps: 5 } + ); + } + + await page.mouse.up(); } - - await page.mouse.up(); } - } - await page.waitForTimeout(4000 + Math.random() * 300); + await page.waitForTimeout(4000 + Math.random() * 300); - await page.getByRole('menuitem', { name: '房价房量管理' }).click(); - await page.waitForTimeout(4000 + Math.random() * 1000); - await page.getByText('房价房量日历').click(); + await page.getByRole('menuitem', { name: '房价房量管理' }).click(); + await page.waitForTimeout(4000 + Math.random() * 1000); + await page.getByText('房价房量日历').click(); - const roomType = process.env.ROOM_TYPE; - const startDate = process.env.START_DATE; - const endDate = process.env.END_DATE; - const operation = process.env.OPERATION === 'close' ? 'close' : 'open'; + const roomType = process.env.ROOM_TYPE; + const startDate = process.env.START_DATE; + const endDate = process.env.END_DATE; + const operation = process.env.OPERATION === 'close' ? 'close' : 'open'; - if (!roomType || !startDate) { - log.info('ROOM_TYPE/START_DATE not provided, skip room toggle.'); - return; - } - - const container = page.locator('#price-reserve-table-container'); - await container.waitFor({ state: 'visible' }); - - const dateList = buildDateList(startDate, endDate); - for (const mmdd of dateList) { - const dateIndex = await findHeaderDateIndex(container, mmdd); - if (dateIndex < 0) { - throw new Error(`Date not found in header: ${mmdd}`); + if (!roomType || !startDate) { + log.info('ROOM_TYPE/START_DATE not provided, skip room toggle.'); + return; + } + + const container = page.locator('#price-reserve-table-container'); + await container.waitFor({ state: 'visible' }); + + const dateList = buildDateList(startDate, endDate); + for (const mmdd of dateList) { + const dateIndex = await findHeaderDateIndex(container, mmdd); + if (dateIndex < 0) { + throw new Error(`Date not found in header: ${mmdd}`); + } + await toggleRoomByDateIndex(page, container, { roomType, dateIndex, operation }); + await page.waitForTimeout(600 + Math.random() * 600); + } + } catch (error) { + log.error(error); + process.exitCode = 1; + }finally { + if (browser) { + try { + if (typeof browser.disconnect === 'function') { + await browser.disconnect(); + } else { + await browser.close(); + } + } catch {} } - await toggleRoomByDateIndex(page, container, { roomType, dateIndex, operation }); - await page.waitForTimeout(600 + Math.random() * 600); } })();