使用cheerio和axios爬取URL不变的分页页面获取不同年份总收益率咨询
网页爬取动态分页(URL无变化)实现方案
核心问题说明
你当前只能拿到默认年限数据的原因是:该站点切换年限属于前端动态交互场景,不会触发整页刷新,URL也不会更新,axios的静态GET请求只能拿到初始加载的默认年限HTML,cheerio无法处理动态交互后的内容更新。
方案1:抓取异步接口(优先推荐,性能更高)
这种站点切换年限时一般会向后端发送异步请求拉取对应数据,你可以直接模拟这些请求拿数据,不用解析HTML,效率更高:
- 操作步骤:
- 打开浏览器F12调试工具,切换到「网络」面板,筛选「Fetch/XHR」
- 手动切换不同年限选项,找到对应的数据请求,记录请求地址、请求方法、请求参数、请求头
- 用axios循环模拟不同年限参数的请求即可,部分接口返回结构化JSON,可直接提取数据,若返回HTML片段复用你原有的cheerio解析逻辑即可
- 示例代码逻辑:
const axios = require("axios"); const cheerio = require("cheerio"); const http = require("http"); // 从调试工具中获取到的所有可选年限参数,根据站点实际情况调整 const TIME_RANGES = ['1y', '3y', '5y', '10y']; // 你抓到的真实数据接口地址 const API_URL = 'https://www.imoney.my/your-captured-api-path'; // 数据解析逻辑复用你原有代码 function parseFunds(html) { const $ = cheerio.load(html); const funds = []; $(".list-item").each((_i, row) => { const $row = $(row); const fund = $row.find("[class*='product-title'] a").text().trim(); const price = $row.find("[class*='is-narrow product-profit'] b").text().trim(); const risk = $row.find("[class*='product-title'] [class*='font-xsm extra-info']").text().replace('/10','').trim(); const totalreturn = $row.find("[class*='product-return'] .font-lg b").text().replace('%','').trim(); funds.push({ fund, price, risk, totalreturn }); }); return funds; } http .createServer(async function (_, res) { try { const allData = {}; // 第一次请求首页拿cookie、csrf token等必要参数 const baseRes = await axios.get('https://www.imoney.my/unit-trust-investments', { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } }); // 若有csrf token可以从baseRes.data里提取,放到后续请求头中 for (const range of TIME_RANGES) { const apiRes = await axios.post(API_URL, { // 替换为你抓到的真实请求参数 time_range: range, page: 1 }, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'X-Requested-With': 'XMLHttpRequest', // 若需要cookie直接用baseRes.headers['set-cookie']带上 'Cookie': baseRes.headers['set-cookie']?.join('; ') } }); // 若接口返回JSON直接提取,返回HTML则调用parseFunds解析 allData[range] = parseFunds(apiRes.data); } res.statusCode = 200; res.write(JSON.stringify(allData, null, 4)); } catch (err) { console.error(err); res.statusCode = 400; res.write("Unable to process request."); } res.end(); }) .listen(8080);
方案2:无头浏览器模拟交互(适配复杂加密场景)
如果接口有加密、参数不好破解,直接用puppeteer模拟真实浏览器操作,点击切换年限后提取页面数据即可:
- 先安装依赖:
npm install puppeteer - 示例代码:
const puppeteer = require('puppeteer'); const http = require("http"); http .createServer(async function (_, res) { try { // 启动无头浏览器 const browser = await puppeteer.launch({ headless: 'new' }); const page = await browser.newPage(); // 访问目标页面,等待网络空闲 await page.goto('https://www.imoney.my/unit-trust-investments', { waitUntil: 'networkidle2' }); const allData = {}; // 获取所有年限选项的标识,替换为页面上实际的年限按钮选择器 const timeOptions = await page.$$eval('.time-filter button', btns => btns.map(btn => ({ value: btn.value, text: btn.textContent.trim() })) ); for (const option of timeOptions) { // 点击对应年限按钮 await page.click(`.time-filter button[value="${option.value}"]`); // 等待收益率数据加载完成 await page.waitForSelector('.list-item [class*="product-return"] .font-lg b', { timeout: 5000 }); // 提取当前页面的基金数据 const funds = await page.evaluate(() => { const list = []; document.querySelectorAll('.list-item').forEach(row => { const fund = row.querySelector("[class*='product-title'] a")?.textContent.trim() || ''; const price = row.querySelector("[class*='is-narrow product-profit'] b")?.textContent.trim() || ''; const risk = row.querySelector("[class*='product-title'] [class*='font-xsm extra-info']")?.textContent.replace('/10','').trim() || ''; const totalreturn = row.querySelector("[class*='product-return'] .font-lg b")?.textContent.replace('%','').trim() || ''; list.push({ fund, price, risk, totalreturn }); }); return list; }); allData[option.text] = funds; } await browser.close(); res.statusCode = 200; res.write(JSON.stringify(allData, null, 4)); } catch (err) { console.error(err); res.statusCode = 400; res.write("Unable to process request."); } res.end(); }) .listen(8080);
注意:代码中的选择器需要根据站点实际DOM结构调整,你可以用浏览器F12的元素选择器直接复制对应元素的选择器替换即可。
内容的提问来源于stack exchange,提问作者Anas Mutaleb
相关产品推荐
相关产品推荐

