import { chromium } from 'playwright'; import dotenv from 'dotenv'; import log from 'electron-log'; dotenv.config(); const checkLoginStatus = async (page) => { try { const currentUrl = await page.url(); log.info('current url==========>:', currentUrl); const loginPagePatterns = ['/login', '/signin', '/auth']; const isLoginPage = loginPagePatterns.some(pattern => currentUrl.includes(pattern)); if(!isLoginPage) { return true; } // 检查页面内容中的关键字 const pageContent = await page.content(); const loginKeywords = ['退出', '房价房量管理']; const hasLoginKeyword = loginKeywords.some(keyword => pageContent.includes(keyword) ); if (hasLoginKeyword) { return true; } return false; } catch (error) { return false; } } (async () => { const browser = await chromium.connectOverCDP('http://localhost:9222'); const context = browser.contexts()[0]; await context.addInitScript(() => { Object.defineProperty(navigator, 'webdriver', { get: ()=> undefined }); }); 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(process.env.FZ_USERNAME, { 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(process.env.FZ_PASSWORD, { 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.waitForTimeout(4000 + Math.random() * 300); await page.getByRole('menuitem', { name: '房价房量管理' }).click(); await page.waitForTimeout(4000 + Math.random() * 1000); await page.getByText('房价房量日历').click(); // await page.pause(); /* * 1、我要知道日期 * 2、我要知道房型 * 3、我要知道是关闭或开启操作 * * 存在以下影响自动化情况: * 1、房型重新拖拽排序,会影响后续操作 * 2、筛选日期下存在没有安排的房型 */ await page.getByRole('textbox', { name: '-02-27' }).click(); await page.getByText('27').nth(3).click(); // 开启房型,div:nth-child(动态变化的) > .boardRow___p2ZiO > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r await page.locator('div:nth-child(7) > div > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r').first().click(); await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(2) > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r').click(); await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(3) > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r').click(); await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(4) > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r').click(); await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(5) > .ant-spin-nested-loading > .ant-spin-container > div > div > .success___VQjXR > .bar___i4k4r').click(); // 关闭房型,div:nth-child(动态变化的) > .boardRow___p2ZiO > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r').first().click(); await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(2) > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r').click(); await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(3) > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r').click(); await page.locator('div:nth-child(7) > .boardRow___p2ZiO > div:nth-child(2) > div:nth-child(4) > .ant-spin-nested-loading > .ant-spin-container > div > div > .error___IM8Yw > .bar___i4k4r').click(); })();