Heroku上Puppeteer脚本获取stale数据问题求助
Puppeteer在Heroku下载Stale Data问题修复方案
问题现象
- 在Heroku运行Puppeteer脚本,流程为:登录公司网站→导航至报表页面→点击触发AJAX请求的标签页加载对应数据
headless=false模式下,UI正常显示标签页切换后的新数据,但下载文件始终是页面初始加载的旧数据- 手动在服务器Chromium中点击下载按钮,可获取正确的新数据
- 已尝试添加固定延迟、
waitForRequestIdle等方法,问题仍存在;且脚本一周前可正常运行
可能原因
- AJAX请求未完全同步:标签页切换后UI渲染完成,但后端数据接口的响应未完全同步到下载按钮的绑定数据源
- AngularJS digest cycle未完成:页面基于AngularJS开发(代码中存在
ng-click),自动点击操作可能在框架的更新周期完成前触发下载 - 原生click事件缺失上下文:使用
page.evaluate中的原生click()未模拟完整用户交互,导致页面未触发正确的数据更新逻辑
修复方案与代码修改
1. 等待标签页切换的目标AJAX请求完成
替换固定延迟为监听并等待报表数据的AJAX请求完成,确保数据完全加载:
// 点击标签页前,监听目标数据请求(替换为实际接口关键词) const dataRequest = page.waitForRequest(request => request.url().includes('report-data') && request.method() === 'POST' // 根据实际请求调整 ); await page.waitForSelector('ul.nav.nav-tabs > li:nth-child(4) > a', { visible: true }); await page.click('ul.nav.nav-tabs > li:nth-child(4) > a'); await dataRequest; // 等待数据请求响应完成
2. 等待AngularJS框架更新周期完成
针对AngularJS页面,确保框架的$digest或$apply完成后再进行下一步操作:
await page.evaluate(async () => { await new Promise(resolve => { if (window.angular) { // 获取根作用域,等待异步更新完成 const rootScope = angular.element(document.body).scope(); rootScope.$applyAsync(resolve); } else { resolve(); } }); });
3. 使用Puppeteer原生page.click替代原生DOM点击
避免在page.evaluate中直接调用click(),改用Puppeteer的page.click模拟完整用户交互:
// 替换原打开过滤器的evaluate代码 await page.waitForSelector('.col-3.col-md-6.text-right.ng-binding button:nth-child(3)', { visible: true }); await page.click('.col-3.col-md-6.text-right.ng-binding button:nth-child(3)'); // 替换原下载按钮的evaluate代码 await page.waitForSelector('.col-3.col-md-6.text-right.ng-binding button:nth-child(2)', { visible: true }); await page.click('.col-3.col-md-6.text-right.ng-binding button:nth-child(2)');
4. 等待数据元素可见确认
添加等待报表数据行出现的逻辑,确保页面已渲染新数据:
// 替换原delay(3000) await page.waitForSelector('.report-table-row-class', { visible: true }); // 替换为实际数据行选择器
完整修改后代码示例
// 点击标签页并等待数据请求 const dataRequest = page.waitForRequest(request => request.url().includes('report-data') && request.method() === 'POST' ); await page.waitForSelector('ul.nav.nav-tabs > li:nth-child(4) > a', { visible: true }); await page.click('ul.nav.nav-tabs > li:nth-child(4) > a'); await dataRequest; console.log(7) // 打开过滤器(改用page.click) await page.waitForSelector('.col-3.col-md-6.text-right.ng-binding button:nth-child(3)', { visible: true }); await page.click('.col-3.col-md-6.text-right.ng-binding button:nth-child(3)'); console.log(8) // 勾选ActivityReference过滤器 await page.evaluate(() => { const labels = Array.from(document.querySelectorAll('label.form-check-label')); const targetLabel = labels.find(label => label.textContent.includes('ActivityReference')); if (targetLabel) targetLabel.click(); }); console.log(9) // 关闭过滤器弹窗 await page.waitForSelector('button[ng-click="close()"]', { visible: true }); await page.click('button[ng-click="close()"]'); console.log(10) // 等待Angular更新和数据渲染 await page.evaluate(async () => { await new Promise(resolve => { if (window.angular) { const rootScope = angular.element(document.body).scope(); rootScope.$applyAsync(resolve); } else { resolve(); } }); }); await page.waitForSelector('.report-table-row-class', { visible: true }); // 替换为实际选择器 // 触发下载(改用page.click) await page.waitForSelector('.col-3.col-md-6.text-right.ng-binding button:nth-child(2)', { visible: true }); await page.click('.col-3.col-md-6.text-right.ng-binding button:nth-child(2)'); // 等待文件下载完成(可根据文件大小调整) await page.waitForTimeout(5000);
额外检查点
- 确认Heroku上的Chromium配置正确,添加启动参数:
--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage - 检查下载路径的权限,确保Puppeteer有权限写入下载文件
- 排查网站是否近期更新了前端逻辑,导致原有的选择器或交互逻辑失效
内容的提问来源于stack exchange,提问作者Estevan
相关产品推荐
相关产品推荐

