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

Next.js API调用Tibia.com排行榜时的分页失效及请求超时问题修复求助

Next.js API调用Tibia.com排行榜时的分页失效及请求超时问题修复求助

问题背景

我最近在用Next.js开发一个API,用来获取Tibia游戏官网的排行榜数据,目前碰到两个头疼的问题:

  1. 分页失效:第一页的数据能正常展示,但从第二页开始,返回的排行榜列表直接是空的
  2. 请求超时:偶尔会触发Socket连接超时的错误,导致API返回500内部错误

原API代码

import axios from "axios";
import * as cheerio from "cheerio";

export default async function handler(req, res) {
  const { world, category, vocation, beprotection, worldtypes, page } = req.query;

  try {
    let url = "https://www.tibia.com/community/?subtopic=highscores";

    // 手动拼接查询参数
    if (world) url += `&world=${world}`;
    if (beprotection) url += `&beprotection=${beprotection}`;
    if (category) url += `&category=${category}`;
    if (vocation) url += `&profession=${vocation}`;
    if (page) url += `&currentpage=${page}`;
    if (worldtypes) url += `&worldtypes[]=${worldtypes}`;

    const response = await axios.get(url);
    const $ = cheerio.load(response.data);

    const highscoreList = [];
    const queryPerPage = 50;

    // 解析页面中的排行榜数据
    $(
      "table.TableContent > tbody > tr[style*='background-color:#F1E0C6'], table.TableContent > tbody > tr[style*='background-color:#D4C0A1']"
    ).each((index, element) => {
      const columns = $(element).find("td");
      const rank = columns.eq(0).text().trim();
      const name = columns.eq(1).text().trim();
      const vocation = columns.eq(2).text().trim();
      const world = columns.eq(3).text().trim();
      const level = parseInt(columns.eq(4).text().trim());
      const points = columns.eq(5).text().trim();

      const highscoreEntry = {
        rank,
        name,
        vocation,
        world,
        level,
        points,
      };
      highscoreList.push(highscoreEntry);
    });

    const totalRecords =
      highscoreList.length < queryPerPage ? highscoreList.length : 1000;
    const totalPages = Math.ceil(totalRecords / 50);

    // 手动进行二次分页截取
    const startIndex = (page - 1) * queryPerPage;
    const endIndex = startIndex + queryPerPage;
    const paginatedList = highscoreList.slice(startIndex, endIndex);

    const highscorePage = {
      current_page: page,
      total_pages: totalPages,
      total_records: totalRecords,
    };

    const output = {
      highscores: {
        world,
        category,
        vocation,
        highscore_age: 20,
        highscore_list: paginatedList,
        highscore_page: highscorePage,
      },
    };

    res.status(200).json(output);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: "Internal Server Error" });
  }
}

错误现象

  • 第一页正常返回(page=1):能拿到完整的50条排行榜数据
// 20231130114831
// http://localhost:3000/api/v1/community/highscores?world=Astera&page=1
{
  "highscores": {
    "world": "Astera",
    "highscore_age": 20,
    "highscore_list": [
      {
        "rank": "1",
        "name": "Hero Reis",
        "vocation": "Royal Paladin",
        "world": "Astera",
        "level": 1924,
        "points": "118,392,612,632"
      },
      // ... 剩余49条数据
    ],
    "highscore_page": {
      "current_page": "1",
      "total_pages": 20,
      "total_records": 1000
    }
  }
}
  • 第二页及以上返回空列表(page=2):
// 20231130115024
// http://localhost:3000/api/v1/community/highscores?world=Astera&page=2
{
  "highscores": {
    "world": "Astera",
    "highscore_age": 20,
    "highscore_list": [],
    "highscore_page": {
      "current_page": "2",
      "total_pages": 20,
      "total_records": 1000
    }
  }
}
  • 偶尔触发超时错误:
AxiosError: Socket connection timeout
at AxiosError.from (file:///C:/Users/shynz/Desktop/Apps/Web/tibia-tools-site/node_modules/axios/lib/core/AxiosError.js:89:14)
// ... 完整错误栈信息

问题分析

  1. 分页失效的核心原因:原代码在获取到Tibia返回的对应页码数据后,又手动对结果做了一次slice截取。但实际上Tibia官网已经根据currentpage参数返回了对应页码的50条数据,二次截取会导致第二页时startIndex=50,而返回的列表只有50条,slice(50, 100)自然得到空数组。
  2. 请求超时的原因:Tibia官网可能有请求频率限制,或者网络波动导致单次请求超时,原代码没有重试机制,直接返回错误。

修复后的完整代码

import axios from "axios";
import * as cheerio from "cheerio";

const MAX_RETRIES = 3;

// 带重试机制的请求函数
const fetchUrl = async (url) => {
  let retries = 0;
  try {
    // 设置5秒超时时间
    const response = await axios.get(url, { timeout: 5000 });
    return response.data;
  } catch (error) {
    if (retries < MAX_RETRIES) {
      retries++;
      console.error(`Retrying (${retries}/${MAX_RETRIES})...`);
      return fetchUrl(url);
    } else {
      throw error;
    }
  }
};

export default async function handler(req, res) {
  const { world, category, vocation, beprotection, worldtypes, page } = req.query;

  try {
    let url = "https://www.tibia.com/community/?subtopic=highscores";
    // 使用URLSearchParams构建查询参数,避免手动拼接的格式错误
    const queryParams = new URLSearchParams();

    if (world) queryParams.append("world", world);
    if (beprotection) queryParams.append("beprotection", beprotection);
    if (category) queryParams.append("category", category);
    if (vocation) queryParams.append("profession", vocation);
    if (page) queryParams.append("currentpage", page);
    if (worldtypes) queryParams.append("worldtypes[]", worldtypes);

    url += `&${queryParams.toString()}`;

    // 调用带重试的请求函数
    const htmlData = await fetchUrl(url);
    const $ = cheerio.load(htmlData);

    const highscoreList = [];
    const queryPerPage = 50;

    $(
      "table.TableContent > tbody > tr[style*='background-color:#F1E0C6'], table.TableContent > tbody > tr[style*='background-color:#D4C0A1']"
    ).each((index, element) => {
      const columns = $(element).find("td");
      const rank = columns.eq(0).text().trim();
      const name = columns.eq(1).text().trim();
      const vocation = columns.eq(2).text().trim();
      const world = columns.eq(3).text().trim();
      const level = parseInt(columns.eq(4).text().trim());
      const points = columns.eq(5).text().trim();

      const highscoreEntry = {
        rank,
        name,
        vocation,
        world,
        level,
        points,
      };
      highscoreList.push(highscoreEntry);
    });

    const totalRecords =
      highscoreList.length < queryPerPage ? highscoreList.length : 1000;
    const totalPages = Math.ceil(totalRecords / 50);

    const highscorePage = {
      current_page: page,
      total_pages: totalPages,
      total_records: totalRecords,
    };

    const output = {
      highscores: {
        world,
        category,
        vocation,
        highscoreList,
        highscore_page: highscorePage,
      },
    };

    res.status(200).json(output);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: "Internal Server Error" });
  }
}

修复要点说明

  1. 解决分页失效:移除了原代码中手动对highscoreList进行slice截取的逻辑,直接使用Tibia返回的对应页码数据即可。
  2. 解决请求超时:新增了带重试机制的fetchUrl函数,设置5秒超时,最多重试3次,有效避免单次网络波动或临时限制导致的请求失败。
  3. 优化URL构建:改用URLSearchParams来拼接查询参数,避免手动拼接时可能出现的格式错误,让代码更健壮。

备注:内容来源于stack exchange,提问作者Yirosh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 07:07:57