You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NodeList元素批量点击下载失效:仅第二个文件可正常下载

问题分析与解决:Puppeteer批量下载文件仅第二个生效

问题场景

我用Puppeteer爬取某网站文件,目标页面的文件结构如下:

<div id="CourseContent">
    <div class="tab-content coursecontentnotes" id="content_210485ac8-16e8-11ed-9075-0a45a3083c9c">

        <!-- Text Content -->
        <!-- End Text Content -->
        <!-- Video Content -->
        <!-- Video Content End -->
        <!-- File Content Start -->
        
                    <div class="content-type-area">
                        <div class="row">
                            <div class="col-md-12">
                                <div href="javascript:void(0)" class="link-preview" onclick="downloadcoursedoc(&#39;4d2d6bd0-077a-4bda-80be-cf7483c9f606&#39;)">
                                     <span class="pesu-icon-unlink" aria-hidden="true"></span>
                                        Web Form 2.0 &amp; Form Controls
                                     <span class="pesu-icon-arr-right pull-right" aria-hidden="true"></span>
                                </div>
                            </div>
                        </div>
                    </div>
             
            <!-- File Content End -->
            <!-- Web Link Content End -->    
            <!-- Web Link Content End -->
            <!-- Text Content -->
            <!-- End Text Content -->
            <!-- Video Content -->
            <!-- Video Content End -->
            <!-- File Content Start -->
            
                    <div class="content-type-area">
                        <div class="row">
                            <div class="col-md-12">
                                <div href="javascript:void(0)" class="link-preview" onclick="downloadcoursedoc(&#39;a7bdfc1e-d7cd-40ac-9b02-a688fc684e6e&#39;)">
                                     <span class="pesu-icon-unlink" aria-hidden="true"></span>
                                        HTML5 (New Tags, Inputs, Elements and Controls),
                                     <span class="pesu-icon-arr-right pull-right" aria-hidden="true"></span>
                                </div>
                            </div>
                        </div>
                    </div>
             
            <!-- File Content End -->
            <!-- Web Link Content End -->    
            <!-- Web Link Content End -->
            
        <!-- END of for loop -->

        <!-- For Study Material -->
        <!-- End of Study Material -->
        <!-- For Forums -->
        <!-- For Forums End -->
    
    </div>
</div>

我在page.evaluate中使用的代码:

let downloadObj = document.querySelectorAll(
    "#CourseContent .tab-content .content-type-area div"
);
// filter selected elements belonging to same content row
downloadObj = Array.from(downloadObj).filter((el) =>
    el.querySelector(".col-md-12")
);
downloadObj.forEach((ele) => {
    eval(ele.querySelector(".link-preview").getAttribute("onclick"));
});

遇到的问题:批量执行时只有第二个文件能成功下载,但手动执行eval(downloadObj[0].querySelector(".link-preview").getAttribute("onclick"))时,第一个文件可以正常下载,怀疑是否和点击操作打开新标签页有关?


原因分析

确实和新标签页切换有关。当通过eval执行downloadcoursedoc函数时,该函数大概率会打开新标签页触发下载。在forEach循环中,第一个函数执行后,Puppeteer的page上下文会被新打开的标签页抢占,后续代码尝试访问原页面的DOM元素时,会因为上下文切换而失效,最终只有第二个文件的执行逻辑能在未被干扰的环境下完成。


解决方案

方案1:阻止新标签页打开,强制在当前上下文执行

重写window.open方法,将下载操作转为当前页面内的a标签点击,避免页面切换:

await page.evaluate(() => {
    // 重写window.open,拦截新标签页并转为本地下载
    window.open = (url) => {
        const a = document.createElement('a');
        a.href = url;
        a.download = '';
        a.click();
    };

    // 优化选择器,直接定位所有下载元素
    const downloadLinks = document.querySelectorAll(
        "#CourseContent .tab-content .content-type-area .link-preview"
    );
    
    downloadLinks.forEach(el => {
        // 提取函数参数并直接调用,替代eval
        const docId = el.getAttribute("onclick").match(/downloadcoursedoc\('([^']+)'\)/)[1];
        downloadcoursedoc(docId);
    });
});

方案2:逐个处理新标签页,完成后切回原页面

如果必须保留新标签页逻辑,可监听新标签页事件,处理完成后关闭并切回原页面:

const originalPage = page;
// 获取所有下载元素的选择器
const downloadElements = await originalPage.$$(
    "#CourseContent .tab-content .content-type-area .link-preview"
);

for (const el of downloadElements) {
    // 同时等待新标签页打开和点击操作
    const [newPage] = await Promise.all([
        originalPage.waitForTarget(target => target.opener() === originalPage.target()),
        el.$eval(".link-preview", node => {
            const docId = node.getAttribute("onclick").match(/downloadcoursedoc\('([^']+)'\)/)[1];
            downloadcoursedoc(docId);
        })
    ]);
    
    // 等待下载完成(可根据实际情况调整等待时长)
    await newPage.waitForTimeout(1500);
    await newPage.close();
    // 切回原页面继续处理下一个
    await originalPage.bringToFront();
}

方案3:直接构造下载链接,跳过前端点击逻辑(最可靠)

分析downloadcoursedoc函数的实际接口,直接构造下载URL,绕开前端交互逻辑:

await page.evaluate(async () => {
    // 根据网站实际下载接口构造URL(需自行验证接口规则)
    const getDownloadUrl = (docId) => {
        return `https://your-site-domain/api/download?docId=${docId}`;
    };

    const downloadLinks = document.querySelectorAll(
        "#CourseContent .tab-content .content-type-area .link-preview"
    );
    
    for (const el of downloadLinks) {
        const docId = el.getAttribute("onclick").match(/downloadcoursedoc\('([^']+)'\)/)[1];
        const downloadUrl = getDownloadUrl(docId);
        
        // 创建a标签触发下载
        const a = document.createElement('a');
        a.href = downloadUrl;
        a.download = el.textContent.trim();
        a.click();
        
        // 等待短时间避免并发下载冲突
        await new Promise(resolve => setTimeout(resolve, 600));
    }
});

额外优化建议

  • 避免使用eval:eval存在安全风险且调试困难,建议通过正则提取函数参数后直接调用downloadcoursedoc。
  • 优化DOM选择器:原选择器会匹配大量无关元素,直接定位.link-preview可以减少过滤步骤,提升效率。

内容的提问来源于stack exchange,提问作者Adnan Zaki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 11:25:30