如何在Nightmare.js中传递evaluate返回变量实现多页表格提取
问题诊断
你遇到的核心问题是Nightmare.js的evaluate()运行在独立的浏览器上下文,无法直接访问Node.js环境的变量,同时你在generator函数中没有正确保存和传递上一次evaluate()返回的结果。另外代码里还有几个细节问题需要修正:
- 第一次
evaluate()只返回了totalPageNumber,但没返回第一页的表格HTML,导致后续没有初始值可拼接; - CSS选择器里的
>是HTML转义字符,在JS字符串中应该直接用>; - 循环硬编码了
i <= 11,应该用实际获取的总页数; - 传递参数给
evaluate()时,没有正确接收返回值并更新变量。
修正后的完整代码
function *run() { let nightmare = Nightmare({ show: true }); // 登录并跳转到目标页面 yield nightmare .goto('https://www.svgame168.com/IGKiosk/guest/login.aspx') .select('#ctl00_ddlLanguage', 'th-TH') .wait(3000) .insert('input[name="ctl00$ContentPlaceHolder1$Login1$UserName"]', userName) .insert('input[name="ctl00$ContentPlaceHolder1$Login1$Password"]', password) .click('#ctl00_ContentPlaceHolder1_Login1_LoginButton') .wait(3000) .goto('https://www.svgame168.com/IGKiosk/playermgt/Default.aspx') .click('#ctl00_ContentPlaceHolder1_searchBtn') .wait(6000); // 第一次获取:总页数 + 第一页表格HTML let initialData = yield nightmare.evaluate(function () { const nextPageExist = document.evaluate("//a[contains(., '>')]", document, null, XPathResult.ANY_TYPE, null).iterateNext(); const tableHtml = document.querySelector("#ctl00_ContentPlaceHolder1_dataGridView").outerHTML; // 修正CSS选择器里的转义字符 const totalPageEl = document.querySelector("#main_content_table > tbody > tr > td > table:nth-child(5) > tbody > tr > td > table > tbody > tr > td > div:nth-child(4) > span > div:nth-child(1)"); const totalPageNumber = totalPageEl ? parseInt(totalPageEl.textContent.replace(/\D/g, ''), 10) : 1; if (nextPageExist) { nextPageExist.className += " nextPageExist1"; } return { totalPageNumber, tableHtml }; }); let result = initialData.tableHtml; const totalPages = initialData.totalPageNumber; // 循环处理剩余页面 for (let i = 2; i <= totalPages; i++) { console.log("Currently on page " + i); yield nightmare.click(".nextPageExist" + (i - 1)).wait(6000); // 传递当前result到浏览器上下文,拼接新页面的表格HTML result = yield nightmare.evaluate(function (currentResult) { const nextPageExist = document.evaluate("//a[text()='>']", document, null, XPathResult.ANY_TYPE, null).iterateNext(); const newTableHtml = document.querySelector("#ctl00_ContentPlaceHolder1_dataGridView").outerHTML; const updatedResult = currentResult + newTableHtml; if (nextPageExist) { // 修正CSS选择器 const pageNumberEl = document.querySelector("#main_content_table > tbody > tr > td > table:nth-child(5) > tbody > tr > td > table > tbody > tr > td > div:nth-child(4) > span > div:nth-child(2) > span"); const pageNumber = pageNumberEl ? pageNumberEl.textContent.replace(/\D/g, '') : i; nextPageExist.className += " nextPageExist" + pageNumber; } return updatedResult; }, result); // 这里把Node环境的result传递给浏览器上下文的evaluate函数 } // 关闭Nightmare并处理结果 yield nightmare.end(); try { console.log(tabletojson.convert(result)); } catch (error) { console.error('Conversion failed:', error); } }
关键修正点解释
- 分离初始数据获取:第一次
evaluate()返回包含总页数和第一页HTML的对象,确保有拼接的初始值; - 正确传递参数:在
evaluate()函数后添加result参数,Nightmare会自动把Node环境的变量序列化后传入浏览器上下文,函数内部通过currentResult接收; - 持续更新结果:循环中每次yield
evaluate()后,把返回的新HTML字符串赋值给result,实现页面内容的累加; - 修正选择器问题:把CSS和XPath中的
>换成实际的>,JS字符串不需要HTML转义; - 动态适配页数:用实际获取的
totalPageNumber代替硬编码的11,适配不同数据量; - 优化错误处理:把结果转换逻辑放在generator内部,避免then链的嵌套问题。
额外提示
- 尽量减少固定时间的
wait(),换成wait(selector)等待目标元素加载完成(比如wait("#ctl00_ContentPlaceHolder1_dataGridView")),这样更可靠; - 如果表格结构复杂,
tabletojson可能需要配置参数来正确解析,建议先打印result确认HTML结构是否完整。
内容的提问来源于stack exchange,提问作者Lucerna Sempiternus
相关产品推荐
相关产品推荐

