/** * 登录后探索 采购建议 和 仓库 页面,找导出按钮 */ import { chromium } from 'playwright'; import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs'; import { execSync } from 'child_process'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const COOKIE_FILE = path.join(__dirname, 'cookies.json'); const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots'); const OCR_SCRIPT = path.join(__dirname, 'ocr_captcha.py'); const BASE_URL = 'https://www.dianxiaomi.com'; mkdirSync(SCREENSHOTS_DIR, { recursive: true }); function ocrCaptcha(imagePath) { try { return execSync(`python3 "${OCR_SCRIPT}" "${imagePath}"`, { encoding: 'utf-8', timeout: 30000 }).trim() || null; } catch { return null; } } async function login(browser) { // 先试 Cookie if (existsSync(COOKIE_FILE)) { const ctx = await browser.newContext({ viewport: { width: 1920, height: 1080 }, locale: 'zh-CN', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', }); const pg = await ctx.newPage(); await ctx.addCookies(JSON.parse(readFileSync(COOKIE_FILE, 'utf-8'))); await pg.goto(`${BASE_URL}/home.htm`, { waitUntil: 'load', timeout: 20000 }); // 检查是否已登录(看页面有没有用户名) const isLogged = await pg.evaluate(() => { return document.body.innerText.includes('MiLe-kf01') || document.body.innerText.includes('待办事项'); }); if (isLogged) { console.log('>> Cookie 有效'); return { page: pg, context: ctx }; } await ctx.close(); } // 登录 for (let i = 1; i <= 20; i++) { console.log(`>> 登录尝试 ${i}...`); const ctx = await browser.newContext({ viewport: { width: 1920, height: 1080 }, locale: 'zh-CN', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', }); const pg = await ctx.newPage(); let apiResult = null; pg.on('response', async (r) => { if (r.url().includes('userLoginNew2')) { try { apiResult = await r.text(); } catch { apiResult = '__REDIRECT__'; } } }); try { await pg.goto(`${BASE_URL}/home.htm`, { waitUntil: 'load', timeout: 30000 }); await pg.waitForSelector('#exampleInputName', { timeout: 10000 }); await pg.waitForFunction(() => typeof window.login === 'function', { timeout: 10000 }); await pg.waitForFunction(() => document.getElementById('verifyImgCode')?.complete, { timeout: 5000 }).catch(() => {}); await pg.waitForTimeout(1000); const el = await pg.$('#verifyImgCode'); const capPath = path.join(SCREENSHOTS_DIR, 'cap.png'); await el.screenshot({ path: capPath }); const code = ocrCaptcha(capPath); if (!code || code.length < 3) { await ctx.close(); continue; } console.log(` 验证码: "${code}"`); await pg.evaluate((c) => { $('#exampleInputName').val('MiLe-kf01'); $('#exampleInputPassword').val('Vxdas@302'); $('#verifyCode').val(c); }, code); apiResult = null; const nav = pg.waitForNavigation({ timeout: 15000, waitUntil: 'load' }).catch(() => null); await pg.evaluate(() => { login(); }); await nav; await pg.waitForTimeout(3000); if (apiResult === '__REDIRECT__' || !pg.url().includes('/home.htm') || (apiResult && !apiResult.includes('"code":-1'))) { console.log('>> 登录成功!'); const cookies = await ctx.cookies(); writeFileSync(COOKIE_FILE, JSON.stringify(cookies, null, 2)); return { page: pg, context: ctx }; } console.log(` 失败: ${apiResult?.substring(0, 100)}`); await ctx.close(); } catch (e) { console.log(` 异常: ${e.message}`); await ctx.close(); } } return null; } async function explorePage(pg, name, url) { console.log(`\n>> ===== ${name} =====`); try { await pg.goto(url, { waitUntil: 'load', timeout: 20000 }); } catch { console.log(' 加载超时'); return; } await pg.waitForTimeout(3000); console.log(` URL: ${pg.url()}`); console.log(` 标题: ${await pg.title()}`); await pg.screenshot({ path: path.join(SCREENSHOTS_DIR, `explore-${name}.png`), fullPage: true }); // 查找所有按钮 const btns = await pg.$$eval('button, a, span, input[type="button"]', els => els.map(el => ({ tag: el.tagName, text: el.textContent.trim().substring(0, 50), id: el.id, cls: (el.className || '').substring(0, 60), onclick: el.getAttribute('onclick')?.substring(0, 80) || '', href: el.href || '', })).filter(e => e.text && e.text.length < 30) .slice(0, 50) ); console.log(` 按钮(${btns.length} 个):`); for (const b of btns) { const star = b.text.includes('导出') ? '★' : ' '; console.log(` ${star} ${b.tag}#${b.id}: "${b.text}" onclick="${b.onclick}" ${b.href ? `href=${b.href}` : ''}`); } // 特别查找导出相关 const exportBtns = btns.filter(b => b.text.includes('导出') || b.text.includes('下载')); if (exportBtns.length) { console.log('\n ★★ 导出按钮详情:'); for (const b of exportBtns) { console.log(` ${b.tag}#${b.id} class="${b.cls}" text="${b.text}" onclick="${b.onclick}" href="${b.href}"`); } } // 页面内容关键部分 const content = await pg.evaluate(() => document.body?.innerText?.substring(0, 2000)); console.log(`\n 页面内容(前1000字):\n ${content?.substring(0, 1000)}`); } // 主流程 const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] }); const result = await login(browser); if (result) { const { page } = result; // 探索关键页面 await explorePage(page, '采购建议', `${BASE_URL}/purchasingProposal/index.htm?state=3`); await explorePage(page, '采购单', `${BASE_URL}/dxmPurchasingNote/waitPayIndex.htm?state=2`); await explorePage(page, '仓库商品', `${BASE_URL}/warehouseProduct/index.htm`); await explorePage(page, '自定导出', `${BASE_URL}/sys/index.htm?go=m409`); await result.context.close(); } await browser.close();