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

React/Next.js导出Excel仅含表头无数据问题求助

React/Next.js爬虫导出Excel仅表头无数据问题排查

我想用React/Next.js爬取网站数据并导出成Excel,要求和手动录入的格式一致,但现在导出的Excel只有表头,没有行数据,请问哪里错了?


组件代码

'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import cheerio from 'cheerio';
import Excel from 'exceljs';
import { saveAs } from 'file-saver';
import { Space, Input, InputNumber, Button } from 'antd';

export default function Home({ data }) {
  const $ = cheerio.load(data ?? '<></>');
  const router = useRouter();
  const [number, setNumber] = useState(1);
  const [url, setUrl] = useState('');

  const workbook = new Excel.Workbook();
  const worksheet = workbook.addWorksheet('World Countries');

  const countryName = $(`h3.country-name:nth-child(${number})`).text();
  const countryCapital = $(`span.country-capital:nth-child(${number})`).text();
  const countryPopulation = $(`span.country-population:nth-child(${number})`).text();
  const countryArea = $(`span.country-area:nth-child(${number})`).text();

  worksheet.columns = [
    { key: 'countryName', header: 'Country Name', width: '30' },
    { key: 'countryCapital', header: 'Country Capital', width: '30' },
    { key: 'countryPopulation', header: 'Country Population', width: '30' },
    { key: 'countryArea', header: 'Country Area', width: '30' },
  ];

  useEffect(() => {
    router.push({
      pathname: '/',
      query: url && `url=${decodeURIComponent(url)}`
    })
  }, [url]);

  const getExcelFile = async () => {
    worksheet.addRow({ countryName, countryCapital, countryPopulation, countryArea });
    const buffer = await workbook.xlsx.writeBuffer();
    const fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
    const fileExtension = '.xlsx';
    const blob = new Blob([buffer], { type: fileType });
    saveAs(blob, `world-countries${fileExtension}`);
  }

  return (
    <main className="flex min-h-screen flex-col items-center justify-between bg-slate-800">
      <h1 className='text-center'>Website Scraper</h1>
      <Space.Compact>
        <Input placeholder='输入目标网址...' value={url} onChange={(e) => setUrl(e.target.value)} />
        <InputNumber value={number} onChange={(number) => setNumber(number)} />
        <Button type="primary" htmlType='button' onClick={getExcelFile}>导出Excel</Button>
      </Space.Compact>
    </main>
  )
}

export async function getServerSideProps({ query }) {
  const { url } = query;
  const response = await fetch(url);
  const data = await response.text();
  return {
    props: { data },
  };
}

输入字段截图

输入字段截图


问题原因及解决方案

核心问题

  1. 数据未实时更新:countryName等变量在组件初始化时计算,后续number或data变化时不会重新计算,导致导出时使用的是初始空值或过时数据。
  2. Excel实例复用问题:工作簿和工作表在组件顶层声明,每次组件渲染都会重建,但导出时可能使用的是旧实例,或者多次导出会重复添加数据。
  3. 路由参数传递错误:useEffect中拼接query参数的方式不正确,可能导致后端获取的url无效,爬不到数据。
  4. 选择器可能错误:nth-child选择器是基于父元素所有子元素的索引,而非同类型元素,容易选不到目标节点。

修复方案

1. 实时获取最新数据

将数据获取逻辑移到getExcelFile函数内,确保每次导出时使用最新的number和data:

const getExcelFile = async () => {
  // 每次导出时重新查询最新数据,Cheerio的eq从0开始索引
  const targetIndex = number - 1;
  const countryName = $(`h3.country-name`).eq(targetIndex).text();
  const countryCapital = $(`span.country-capital`).eq(targetIndex).text();
  const countryPopulation = $(`span.country-population`).eq(targetIndex).text();
  const countryArea = $(`span.country-area`).eq(targetIndex).text();

  // 每次导出创建新的工作簿,避免数据重复或实例冲突
  const workbook = new Excel.Workbook();
  const worksheet = workbook.addWorksheet('World Countries');
  worksheet.columns = [
    { key: 'countryName', header: 'Country Name', width: 30 },
    { key: 'countryCapital', header: 'Country Capital', width: 30 },
    { key: 'countryPopulation', header: 'Country Population', width: 30 },
    { key: 'countryArea', header: 'Country Area', width: 30 },
  ];
  
  // 仅当数据非空时添加行
  if (countryName) {
    worksheet.addRow({ countryName, countryCapital, countryPopulation, countryArea });
  }

  const buffer = await workbook.xlsx.writeBuffer();
  const fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
  const fileExtension = '.xlsx';
  const blob = new Blob([buffer], { type: fileType });
  saveAs(blob, `world-countries${fileExtension}`);
}

2. 修复路由参数传递

正确传递query参数,避免拼接字符串导致的编码问题:

useEffect(() => {
  if (url) {
    router.push({
      pathname: '/',
      query: { url: encodeURIComponent(url) }
    });
  }
}, [url, router]);

同时在getServerSideProps中处理解码:

export async function getServerSideProps({ query }) {
  const { url } = query;
  if (!url) return { props: { data: '' } };
  
  try {
    const decodedUrl = decodeURIComponent(url);
    const response = await fetch(decodedUrl);
    const data = await response.text();
    return { props: { data } };
  } catch (err) {
    console.error('爬取失败:', err);
    return { props: { data: '' } };
  }
}

3. 验证选择器正确性

使用eq()替代nth-child(),确保选中第N个目标元素:

// 原选择器
// $(`h3.country-name:nth-child(${number})`).text()
// 替换为
$(h3.country-name).eq(number - 1).text()

如果目标元素的父结构特殊,需要根据实际HTML结构调整选择器,确保能精准匹配到数据节点。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:47:12