Next.js API调用Tibia.com排行榜时的分页失效及请求超时问题修复求助
Next.js API调用Tibia.com排行榜时的分页失效及请求超时问题修复求助
问题背景
我最近在用Next.js开发一个API,用来获取Tibia游戏官网的排行榜数据,目前碰到两个头疼的问题:
- 分页失效:第一页的数据能正常展示,但从第二页开始,返回的排行榜列表直接是空的
- 请求超时:偶尔会触发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 += `¤tpage=${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) // ... 完整错误栈信息
问题分析
- 分页失效的核心原因:原代码在获取到Tibia返回的对应页码数据后,又手动对结果做了一次
slice截取。但实际上Tibia官网已经根据currentpage参数返回了对应页码的50条数据,二次截取会导致第二页时startIndex=50,而返回的列表只有50条,slice(50, 100)自然得到空数组。 - 请求超时的原因: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" }); } }
修复要点说明
- 解决分页失效:移除了原代码中手动对
highscoreList进行slice截取的逻辑,直接使用Tibia返回的对应页码数据即可。 - 解决请求超时:新增了带重试机制的
fetchUrl函数,设置5秒超时,最多重试3次,有效避免单次网络波动或临时限制导致的请求失败。 - 优化URL构建:改用
URLSearchParams来拼接查询参数,避免手动拼接时可能出现的格式错误,让代码更健壮。
备注:内容来源于stack exchange,提问作者Yirosh
相关产品推荐
相关产品推荐

