高效爬取ADF Page的可行方案咨询 无需启动完整浏览器的实现方法
可行优化方案
方案1:优化现有Puppeteer逻辑(改造成本最低,性能提升最明显)
当前单次15秒的耗时90%以上来自每次请求都重复启动、销毁浏览器实例,调整为复用实例即可将单次参数获取耗时压缩到1秒以内:
- 全局复用同一个浏览器实例,不要每次调用都执行
launch和close - 启动时添加轻量化配置,禁用不必要的资源加载:
// 全局初始化一次浏览器即可 let browser: Browser const initBrowser = async () => { browser = await puppeteer.launch({ headless: 'new', // 新版无头模式性能更高 args: [ '--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--blink-settings=imagesEnabled=false', // 禁用图片 '--disable-stylesheets' // 禁用CSS加载 ] }) } const sessionParams = async () => { if (!browser) await initBrowser() const page = await browser.newPage() // 不用等全页加载,只要目标输入框出现就取值 await page.goto('https://sia.unal.edu.co/ServiciosApp/facespublico/public/servicioPublico.jsf?taskflowId=task-flow-AC_CatalogoAsignaturas') await page.waitForSelector('input[name="Adf-Window-Id"]') const params = { windowId: await page.$eval('input[name="Adf-Window-Id"]', input => input.value), viewState: await page.$eval('input[name="javax.faces.ViewState"]', input => input.value), // 把当前页面的Cookie也一起返回,后续POST请求要携带同一份Cookie参数才有效 cookies: await page.cookies() } await page.close() return params }
- 额外可加参数缓存逻辑:在参数有效期内(通常和会话Cookie绑定,有效期10-30分钟)重复使用,不需要每次发起业务请求都重新获取参数。
方案2:逆向前端逻辑,完全摆脱浏览器依赖(性能最优,毫秒级获取参数)
拿到winnoloop是因为ADF Faces的校验逻辑:首次返回的静态HTML里隐藏框默认值就是winnoloop,只有执行了页面附带的JS初始化逻辑后,才会替换成合法的Adf-Window-Id和ViewState。
你可以直接提取页面中负责生成这两个参数的JS代码,用Node.js的vm模块直接执行即可,不需要启动任何浏览器实例,单次获取参数耗时仅几十毫秒:
- 首次用GET请求拉取页面内容,提取所有
<script>标签里的代码和外链JS文件内容 - 定位负责修改隐藏输入框值的逻辑,剥离无关代码后直接在Node.js环境运行
- 注意GET请求拿到的Cookie要和后续POST请求携带的Cookie保持一致,否则参数会被后端判定为无效。
方案3:轻量JS运行环境替代完整浏览器
如果不想做逆向,也可以用jsdom这类轻量JS运行环境替代Puppeteer,资源占用和执行速度都比完整浏览器好很多:
import { JSDOM } from 'jsdom' import fetch from 'node-fetch' const sessionParams = async () => { const res = await fetch('https://sia.unal.edu.co/ServiciosApp/facespublico/public/servicioPublico.jsf?taskflowId=task-flow-AC_CatalogoAsignaturas') const html = await res.text() const cookies = res.headers.get('set-cookie') // 用jsdom加载页面并执行所有JS const dom = new JSDOM(html, { runScripts: 'dangerously', resources: 'usable' }) // 等待JS执行完成后取值 await new Promise(resolve => setTimeout(resolve, 500)) const windowId = dom.window.document.querySelector('input[name="Adf-Window-Id"]').value const viewState = dom.window.document.querySelector('input[name="javax.faces.ViewState"]').value return { windowId, viewState, cookies } }
内容的提问来源于stack exchange,提问作者Rendxn
相关产品推荐
相关产品推荐

