## 3. Response Schema - **省略 `fields` 只回 `paperId` + `title`**;`paperId` 任何情况下都返回。 - 点号子字段只对四个字段开放:`authors.*`、`citations.*`、`references.*`、`embedding.specter_v2`。 `tldr.text`、`externalIds.DOI` 这类写法官方就回 400,本地同样 400。 - 未知字段一次性回 400,不静默忽略:`{"error": "Unrecognized or unsupported fields: [bogus]"}`。 - 整对象请求覆盖同名字段的子字段请求,与书写顺序无关:`fields=citations.year,citations` 与 `fields=citations,citations.year` 都回默认的 `paperId,title`。 - `fields` 是**列表参数**:逗号分隔,同一键给两次也合并(`fields=title&fields=year` 等价 `fields=title,year`)。合并发生在逗号切分**之前**,所以 `fields=title,&fields=year` 那种 悬空逗号不会粘成不存在的字段名——空字段名会被丢掉。 论文对象的字段(值可能为 `null`,见下): ``` paperId corpusId externalIds url title abstract venue publicationVenue year publicationDate referenceCount citationCount influentialCitationCount isOpenAccess openAccessPdf fieldsOfStudy s2FieldsOfStudy publicationTypes journal citationStyles authors tldr embedding textAvailability citations references ``` - `citations` / `references` 是**论文数组**(默认每条 `paperId` + `title`,各最多 1000 条); `fields=citations.title` 把子字段投影到每条上。 - `authors` 默认每条 `authorId` + `name`;`authorId` 恒返回。 - `url` 是数据集原值,不保证等于 `https://www.semanticscholar.org/paper/`(见 §1.5 末尾)。 **「没有值」的形状不一样,别统一按 `is None` 判空**(本快照实测): | 字段 | 缺失形状 | 数量 | |---|---|---| | `venue` | 空串 `""`(恒为字符串,没有一篇是 `null`) | 9,018,379(45.3%) | | `fieldsOfStudy` | `[]` | 32,228 | | `publicationTypes` | `null` | 9,617,355(48.1%) | | `publicationDate` | `null` | 5,700,418 | | `year` | `null` | 15,469 | **同一篇论文的同一字段,从哪个出口取都是同一个对象**:详情、`batch`、`search` / `bulk` / `match` 的行、`/citations` 与 `/references` 的邻居行、`/paper/{id}/authors` 与 `/author/{id}/papers` 这八个出口共用一份投影,没有「检索侧的精简副本」。 唯一例外是 `matchScore`——只在 `match` 的行上出现。 **错误文案模板**(状态码均 400,除注明外): | 场景 | `error` | |---|---| | 数值参数非法 | `Unacceptable query params: [offset=-1]` | | 数值参数不是数字 | `Invalid value for parameter 'limit': 'abc'` | | search limit 超限 | `'limit' must be <= 100` | | search 翻页越界 | `offset + limit must be <= 1000` | | bulk limit 超限 | `'limit' must be <= 1000` | | bulk limit=0 | `Unacceptable query params: [limit=0]` | | 列表端点 limit 超限 | `'limit' must be <= 1000` | | 列表端点翻页越界 | `offset + limit must be < 10000` | | batch ids 为空 | `Invalid request body: 'ids' must be a non-empty array` | | batch ids 超上限 | `Too many ids: 501 (max 500)` | | 缺 query | `Missing required parameter: query` | - 过滤值是**精确匹配**:`venue` / `fieldsOfStudy` / `publicationTypes` 写了数据集里不存在的值 (含缩写、错拼)不报错,结果是 0 条。 - `fieldsOfStudy=Other` / `publicationTypes=Other` 指**「不在这两张枚举表里的值」**,不是字面 写着 Other;本快照取值全部落在表内,所以这个条件一条都匹不上。两处 `total` 口径不同(§1.1): `bulk` 回 `total: 0`,`search` 的 `total` 仍是**过滤前**的 BM25 命中数、只有 `data` 为空。 - bulk 翻页只认 `token`,`offset` 在这里无效(传了忽略)。 - 列表响应里 `next` 只在**还有下一页**时出现(没有更多数据时整个键都没有,不是 `null`)。 **与官方接口的已知差异**(「官方」列取自官方接口文档;本环境没有 API key,标**未实测**的几行只比到文档): | 项 | 官方 | 本地 | |---|---|---| | 数据范围 | 全学科、实时更新 | 仅 CS,静态快照 | | `paperId` | 40 位 sha | 同;2,450 篇(0.012%)无主 sha,回退成 corpusid 字符串 | | `url` | 原文链接 | 数据集原值。无主 sha 的那 2,450 篇 url 尾部 sha 不是它自己的(1,458 指向别的论文、992 查不到),别拿它反推 `paperId`,见 §1.5 | | `embedding`(含 `embedding.specter_v2`) | SPECTER2 向量 | 字段认,值恒 `null` | | `openAccessPdf` | OA 链接 | 恒 `null`——`isOpenAccess=true` 的也一样(500 行切片里 68 篇如此),判是否开放获取只看 `isOpenAccess` | | `citationStyles` / `textAvailability` | 有 | 恒 `null` | | `contexts` / `intents` / `contextsWithIntent` | 有 | 恒 `[]` | | `authors[].affiliations` / `homepage` / `externalIds` | 有 | `[]` / `null` / `{}` | | `venue` | 认 ISO4 缩写 | 只认数据集里的完整名称,精确匹配;缩写形状的 `journal.name` 一条都筛不到(3,009 vs 0),见 §1.5 | | `journal` | 结构化对象 | 数据集原样透传:可能 `null`(4,288,986 篇)、可能没有 `name` 键(3,678,324 篇),键集合只会是 `{name, pages, volume}` 的子集,见 §1.5 | | `publicationVenue` | `{id, name, type, issn, url, alternate_names}` | 只有 `{id}` 或 `{id, name}`,且 `name` 从 `venue` 借,不是官网刊物规范名,见 §1.5 | | `search` 的 `total` | 估算 | 过滤前的 BM25 命中数 | | bulk `token` 下标越过集合末尾 | 文档没写(未实测) | 200 + 空 `data` + `token: null`,不报错,见 §1.2 | | `batch` 的 `ids` 元素形态 | 文档只写「字符串数组」(其它形态未实测) | 字符串与 JSON 整数都按 §1.5 解析,布尔/浮点/对象/数组该槽给 `null`,一槽一条不去重,见 §1.9 | | `/paper/{id}/authors` 的 `total` | 文档没写是否去重(未实测) | 槽位数、重复署名各占一行,见 §1.8 | | `search` 的排序 | LightGBM 重排(22 维特征,其中 6 维靠语言模型;点击数据训练) | 同构的线性重排(词面覆盖、分字段词面强度、标题整串命中、venue、引用数、新近度等,权重用官方 top-10 拟合)。top-10 与官方重合 35.7%,**不是复刻** | | `author` 统计 | 全量口径 | 本地 CS 子集口径 | | `author/{id}/papers` 的过滤与排序 | 文档列出 `sort` / `year` / `publicationDateOrYear`(未实测) | 只认 `publicationDateOrYear`,`sort` / `year` / `venue` / `publicationTypes` / `minCitationCount` 静默忽略,见 §2 | | 速率限制 / API key | 有 | 速率限制无;访问密钥必填(见 §0) | | 查询规模上界 | 文档没写(未实测) | 论文侧三条:括号 64 层 / 折叠后 256 分支 / 一条查询 2,000 万条 postings(`search` 与 `bulk` 共用);作者侧两条:单个姓名词 6 万项 / 500 万行、一条查询(去重后)800 万行。超限一律 400,见 §1.1、§1.2、§2 | | 数据目录问题 | 不存在这一层(官方自己托管数据) | 本地分两类:**数据层没建/半截/世代不一致 → 500**,文案点名缺哪层、该跑哪个重建脚本;**查询太宽 → 400**,改参数能救。同一端点两种码都会出现,别把 500 当「换个写法再试」,见 §0 | **响应体量不设上限**:一个请求里同时点「很多 id × 数组型字段」,本地会把整包攒完再回 (`citations.abstract` 这类数组子字段实测每篇可达 1.3 MB,500 篇就是几百 MB 量级)。批量取详情时把 `fields` 收窄,别在 batch 里点 `citations` / `references`。 **文本形状**(全量扫描,不是抽样): | 形状 | 数量 | 出现在 | 响应里的样子 | |---|---|---|---| | C0 控制字符(U+0000–U+001F) | 2,556 处、分布 318 篇(另有 2 处在正文以外字段) | 标题、摘要(PDF 抽取带进来,如 `re\u0007quires`) | 按 JSON 规范转义成 `\u00xx`,`json.loads` 后仍是那个控制字符 | | 裸行分隔符 U+2028 / U+2029 | 标题里 2,375 个;正文以外字段另有 38 个 | 标题 | **原样透传**(UTF-8 三字节),不转义 | | 孤立代理项(`\udXXX`) | 0 | — | 不存在,所以不会出现「编不出 UTF-8」的 500 | 第二行是唯一要小心的:U+2028/U+2029 在 JSON 字符串里合法,在 JavaScript 里却是**行终止符**, `eval("(" + text + ")")` 或 JSONP 会当场语法错误。用 `JSON.parse` 或标准 JSON 库就没问题。 例(实测):`CorpusId:157672` 标题含 U+2028,`CorpusId:139658` 标题以 U+2029 开头。