Pyppeteer设置1000ms超时后page.goto仍无限挂起问题排查及解决方案求助
这个问题我之前踩过类似的坑,尤其是对付那些做了反爬优化或者持续保持网络连接的网站,你的情况大概率和这几个核心原因有关:
1. networkidle0 等待条件永远无法满足
你在page.goto()里用了waitUntil: 'networkidle0',这个规则要求500毫秒内完全没有任何网络请求才会判定导航完成。但像ig.com.br这类网站,可能会持续发送心跳请求、WebSocket连接或者SSE(服务器推送事件)来维持会话——这些高频的小请求会一直触发网络活动,让networkidle0的条件永远达不到。
而你设置的timeout:1000其实只作用于初始导航阶段(比如DNS解析、TCP握手、服务器返回首屏内容),后续等待网络空闲的阶段并不会被这个timeout覆盖,这就直接导致程序无限挂起在等待逻辑里。
2. 网站反爬机制针对headless浏览器做了拦截
很多网站会检测访问来源是否是headless模式的浏览器(Pyppeteer默认的headless模式有不少特征可以被识别),一旦检测到,可能会故意卡住页面渲染流程,或者不返回完整的页面资源,让Pyppeteer一直处于等待页面就绪的状态。
3. Asyncio层面的潜在阻塞(相对少见)
虽然直接关联asyncio的概率不高,但如果页面的JS执行出现死循环,或者Pyppeteer内部的事件循环被异常阻塞,也可能导致整个任务无法终止。不过这种情况更多是极端案例,前两个原因才是主流问题。
解决方法:确保20秒内完成或抛出错误
方法1:换用更宽松的waitUntil条件
把networkidle0换成networkidle2(允许500毫秒内最多2个网络请求)或者domcontentloaded(DOM结构加载完成就停止等待),避开持续心跳请求的干扰:
await page.goto(url, {'waitUntil': 'domcontentloaded','timeout': 20000})
方法2:用asyncio.wait_for强制全局超时
不管Pyppeteer内部的timeout逻辑,直接用asyncio的超时包裹整个导航操作,确保即使内部逻辑卡住,也能在指定时间内抛出错误终止任务:
async def test(): url = 'https://ig.com.br/' browser = await launch(headless=True) page = await browser.newPage() page.setDefaultNavigationTimeout(20000) await page.setUserAgent(fake_useragent.UserAgent().random) try: # 强制20秒超时,覆盖所有等待逻辑 await asyncio.wait_for( page.goto(url, {'waitUntil': 'networkidle0'}), timeout=20.0 ) except asyncio.TimeoutError: print("请求超时,强制终止") finally: await browser.close() asyncio.run(test())
方法3:优化headless浏览器配置,模拟真实环境
给浏览器添加一些参数,减少被反爬识别的概率:
browser = await launch( headless=True, args=[ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36' ] )
(也可以固定使用主流UA,避免fake_useragent随机到被标记的异常UA)
方法4:提前处理页面弹窗
有些网站会在加载时弹出alert或确认框,headless模式下这些弹窗不会显示,但会卡住页面渲染。可以提前监听弹窗事件并自动关闭:
page.on('dialog', lambda dialog: asyncio.create_task(dialog.accept()))
内容的提问来源于stack exchange,提问作者Milano

