稍候片刻,月出文自明。
{
"code": 0,
"data": {
"tabs": [{
"key": "special",
"label": "",
"poolType": "E_CharacterGachaPoolType_Special"
},
{
"key": "rerun",
"label": "",
"poolType": "E_CharacterGachaPoolType_Rerun"
},
{
"key": "normal",
"label": "",
"poolType": "E_CharacterGachaPoolType_Standard"
}
],
"beginnerPullCount": 40
},
"msg": ""
}// seq_id=26&pool_type=E_CharacterGachaPoolType_Standard
{
"code": 0,
"data": {
"list": [
//...
{
"poolId": "standard",
"poolName": "基础寻访",
"charId": "chr_0019_karin",
"charName": "秋栗",
"rarity": 4,
"isFree": false,
"isNew": false,
"gachaTs": "1769073164712",
"seqId": "21"
}
],
"hasMore": false
},
"msg": ""
}
// pool_type=E_CharacterGachaPoolType_Beginner
{
"code": 0,
"data": {
"list": [
{
"poolId": "beginner",
"poolName": "启程寻访",
"charId": "chr_0022_bounda",
"charName": "萤石",
"rarity": 4,
"isFree": false,
"isNew": false,
"gachaTs": "1769072324787",
"seqId": "20" //该卡池最新的抽卡记录
},
// ...
],
"hasMore": true
},
"msg": ""
}{
"code": 0,
"data": [
{ "poolId": "weponbox_1_0_1", "poolName": "熔铸申领" },
{ "poolId": "weaponbox_constant_2", "poolName": "星声申领" }
// ...
],
"msg": ""
}{
"code": 0,
"data": {
"list": [
{
"poolId": "weponbox_1_0_1",
"poolName": "熔铸申领",
"weaponId": "wpn_funnel_0010",
"weaponName": "骑士精神",
"weaponType": "E_WeaponType_Wand",
"rarity": 6,
"isNew": true,
"gachaTs": "1769238381938",
"seqId": "47"
},
// ...
],
"hasMore": true
},
"msg": ""
}// 获取角色池记录
async function fetchCharacterRecords(token: string, serverId: string) {
const metaUrl = `https://ef-webview.hypergryph.com/api/record/char/meta?lang=zh-cn&token=${encodeURIComponent(token)}&server_id=${encodeURIComponent(serverId)}`;
const metaResponse = await fetch(metaUrl);
if (!metaResponse.ok) throw new Error("获取角色池元数据失败");
const { code, data: meta } = await metaResponse.json();
if (code !== 0 || !Array.isArray(meta?.tabs)) throw new Error("无效的角色池元数据");
const allRecords = [];
for (const pool of meta.tabs) {
if (typeof pool?.poolType !== "string" || !pool.poolType ||
(pool.poolId != null && typeof pool.poolId !== "string")) {
throw new Error("无效的角色子池");
}
let cursor: number | null = null;
let hasMore = true;
while (hasMore) {
const seqIdParam = cursor !== null ? `&seq_id=${cursor}` : "";
const poolIdParam = pool.poolId ? `&pool_id=${encodeURIComponent(pool.poolId)}` : "";
const url = `https://ef-webview.hypergryph.com/api/record/char?lang=zh-cn${seqIdParam}&pool_type=${encodeURIComponent(pool.poolType)}${poolIdParam}&token=${encodeURIComponent(token)}&server_id=${encodeURIComponent(serverId)}`;
const response = await fetch(url);
if (!response.ok) throw new Error("获取记录失败");
const { code, data } = await response.json();
if (code !== 0 || !Array.isArray(data?.list) ||
typeof data.hasMore !== "boolean" || (!data.list.length && data.hasMore)) {
throw new Error("官方接口返回错误或无效分页");
}
if (data.list.length > 0) {
allRecords.push(...data.list);
hasMore = data.hasMore;
if (hasMore) {
const seqIds = data.list.map((item: { seqId: string }) => Number(item.seqId));
if (seqIds.some((id: number) => !Number.isSafeInteger(id) || id <= 0)) {
throw new Error("序号超出安全整数范围或格式无效");
}
const nextCursor = Math.min(...seqIds);
if (cursor !== null && nextCursor >= cursor) throw new Error("分页游标未前进");
cursor = nextCursor;
await new Promise(resolve => setTimeout(resolve, 300));
}
} else {
hasMore = false;
}
}
}
return allRecords;
}
// 获取武器池记录
async function fetchWeaponRecords(token: string, serverId: string) {
const allRecords = [];
const poolListUrl = `https://ef-webview.hypergryph.com/api/record/weapon/pool?lang=zh-cn&token=${encodeURIComponent(token)}&server_id=${encodeURIComponent(serverId)}`;
const poolListResponse = await fetch(poolListUrl);
if (!poolListResponse.ok) throw new Error("获取武器池列表失败");
const { code, data: weaponPools } = await poolListResponse.json();
if (code !== 0 || !Array.isArray(weaponPools)) throw new Error("无效的武器池列表");
for (const pool of weaponPools) {
if (typeof pool.poolId !== "string" || !pool.poolId) throw new Error("武器池缺少标识");
let cursor: number | null = null;
let hasMore = true;
while (hasMore) {
const seqIdParam = cursor !== null ? `&seq_id=${cursor}` : "";
const url = `https://ef-webview.hypergryph.com/api/record/weapon?lang=zh-cn${seqIdParam}&pool_id=${encodeURIComponent(pool.poolId)}&token=${encodeURIComponent(token)}&server_id=${encodeURIComponent(serverId)}`;
const response = await fetch(url);
if (!response.ok) throw new Error("获取记录失败");
const { code, data } = await response.json();
if (code !== 0 || !Array.isArray(data?.list) ||
typeof data.hasMore !== "boolean" || (!data.list.length && data.hasMore)) {
throw new Error("官方接口返回错误或无效分页");
}
if (data.list.length > 0) {
allRecords.push(...data.list);
hasMore = data.hasMore;
if (hasMore) {
const seqIds = data.list.map((item: { seqId: string }) => Number(item.seqId));
if (seqIds.some((id: number) => !Number.isSafeInteger(id) || id <= 0)) {
throw new Error("序号超出安全整数范围或格式无效");
}
const nextCursor = Math.min(...seqIds);
if (cursor !== null && nextCursor >= cursor) throw new Error("分页游标未前进");
cursor = nextCursor;
await new Promise(resolve => setTimeout(resolve, 300));
}
} else {
hasMore = false;
}
}
}
return allRecords;
}
2026-10-06 更新:本文最初写于开服时,而目前官方 API 返回的数据结构、相关数据的获取和处理都经过了较大变化。现补上赠礼机制、重构池等信息。
今天《明日方舟:终末地》公测了。体验了一段时间之后,想着将抽卡数据导入抽卡分析应用中。但是可能是游戏刚开服的缘故,找了一轮都没能找到导出抽卡数据的工具,最终只能自己动手实现这个功能。下面分享主要思路,如果你需要实现这个功能希望对你有所帮助。
URL 里都有什么
首先,和其他游戏一样,我们可以通过抓包软件抓取或者一些更方便的方法1获取到这样的抽卡记录链接:
角色池
针对角色池,我们主要关注 pool_type 和 seq_id 这两个参数:
pool_type 是卡池枚举值,可以通过下面 API 获取抽卡记录页面提供的角色池:
返回数据:
目前仅能取如下卡池之一:
而与原神,鸣潮等游戏不同,终末地的抽卡记录链接通常带有 seq_id 参数。
通过观察上面两个不同卡池的记录,我们可以发现 seq_id 参数的一些特征:
根据这些特征我们可以知道:在游戏里的抽卡记录展示页中,我们从第一页开始往后翻(从新记录到旧记录)的过程就是抽卡记录链接 seq_id 参数递减的过程。
在链接返回的抽卡记录中,还会带有一个 hasMore 参数。其代表的是当前返回结果的最小 seqId 之下是否还有数据,反映到抽卡页面就是 是否还能翻下一页。
此外,终末地的卡池有赠礼机制。当期特许寻访累计达到 60 次,会额外获得 1 本寻访情报书,接口也会添加一条 kind: "gift_intel_book" 的奖励记录。在处理抽卡数据的时候需要对其进行过滤,否则会出现接口返回的记录条数与抽卡次数不一致。
武器池
与角色池相似,武器池同样可以通过两个 API 获取记录:
获取武器池列表
先通过下面的链接获取用户存在抽卡记录的武器池列表:
返回的数据结构:
这里可以看到武器池分为几种类型:
这里有意思的是,武器限定池的 weapon 前缀拼写为了 wepon
获取武器抽卡记录
获取具体武器池的抽卡记录链接与角色池抽卡记录链接格式有所不同,武器池的抽卡记录链接将 pool_id 参数代替了 pool_type 参数:
其返回的数据结构为:
武器记录的字段与角色记录略有不同:
武器池的 seq_id 特性与角色池相同,所有武器子卡池共享同一条 seqId 序列。
至此,我们已经基本搞清楚了《明日方舟:终末地》抽卡记录链接的逻辑。
主要思路
根据上面的介绍,我们能构想出通过一个有效的终末地抽卡记录链接来获取当前可以查询的所有记录的方法:
角色池
武器池
代码实现
这样我们就实现了获取《明日方舟:终末地》的全部卡池的可查询记录。