&api_key=...
→ citations[]: { title: "MLA"|"APA"|"Chicago"|"Harvard"|"Vancouver"|"BibTeX", snippet }
links[]: { name, link }
```
**插件实现**:`normalizeSerpApiResult()` 把上述字段映射到统一 paper 结构;
`scholar_cite` 直接返回 `citations` + `links` 渲染成可粘贴文本。
### 2.5 HTML 抓取要点
真实 Scholar 结果页的结构(本次按此实现并写了 fixture 单测):
```html
```
实现上的两个坑(都已在单测里覆盖):
1. `gs_r` / `gs_rs`、`gs_fl` / `gs_flb`、`gs_ggs gs_fl` 互为前缀或共用 token,
用子串匹配会取错节点;必须按 class 列表的**完整 token** 匹配,并对嵌套 `` 做深度配对。
2. 同一个结果块里 `class="gs_ggs gs_fl"`(PDF 容器)出现在真正的页脚 `class="gs_fl gs_flb"` 之前,
因此选“页脚”时要按内容(含 `cites=` / `Cited by`)挑选。
反爬特征识别:响应里出现 `unusual traffic`、`not a robot`、`/sorry/`、`captcha`,
或状态码 429,一律判定为被拦截并抛出带解决建议的错误。
其他请求参数:`hl=en`(语言)、`as_sdt=0,5`(不含专利)、`as_ylo`/`as_yhi`(年份)、`start`(偏移,每页 10 条)。
### 2.6 参考
- SerpApi Google Scholar API:
- SerpApi 结果字段:
- SerpApi 引用格式接口:
- 第三方对“无官方 API”现状的综述:、
---
## 3. 调研结论 → 插件设计映射
| 调研结论 | 插件里的落点 |
| --- | --- |
| PubMed 有官方 API,免费且稳定 | `lib/pubmed.js`:`pubmed_search` / `pubmed_paper` / `pubmed_related`,默认启用 |
| 3 请求/秒、可申请到 10 请求/秒 | `RateLimiter` + `pubmedRateLimitMs`,自动按是否配置 `NCBI_API_KEY` 选择间隔 |
| `tool`/`email` 是政策要求 | 每次请求都带 `pubmedTool` / `pubmedEmail` |
| ESearch 只给 PMID,摘要要 EFetch | 检索流程固定为 esearch → esummary → efetch,可关掉 efetch 降成本 |
| EFetch XML 带巨大 ReferenceList | 改用 `rettype=medline&retmode=text` 并自写解析器(`lib/medline.js`) |
| Scholar 无官方 API,HTML 时通时断 | `scholarProvider=auto`:有 key 用 SerpApi,否则 HTML 并诚实报错 |
| SerpApi 有稳定的 JSON 与被引次数、引用格式 | `normalizeSerpApiResult()` + `scholar_cite` |
| HTML 结构易被前缀匹配搞错 | 完整 class token 匹配 + 嵌套 div 配对 + 反爬特征识别,配 fixture 单测 |
| 分页链接被转义成 `&start=`,且短页仍可能有下一页 | `scholarNextStart()` 判定 `truncated`,不再用条数推断 |
## 4. 本次验证记录
- 离线套件:`node test/run-all.mjs` → **33/33** 通过(MEDLINE 解析 7、Scholar HTML/SerpApi 解析与分页判定 6、
插件契约与五个工具的 execute + render + 输出 schema 校验 16、配置漂移 4)。
- 在线套件:`node test/live-scholar.mjs` → **5/5** 通过(真实 Scholar HTML:
解析结果、年份窗口、翻页不重叠、渲染、`scholar_cite` 拒绝 HTML 后端)。
该套件把“Google 拒绝请求 / 页面无结果块”记为 SKIP,把“页面里仍有结果块但解析出 0 条”记为失败。
- 在线套件:`node test/live-pubmed.mjs` → 出网正常时 **5/5** 通过(真实 E-utilities:
检索命中数、摘要/DOI 富集、日期窗口、单篇回读、相关文献);本机出网时通时断,
不通时套件打印 SKIP 与原因而不是伪装成通过。
- 连通性诊断:`node test/probe-scholar.mjs`(不改插件代码,直接看 status / 结果块数 / 是否被拦)。
**关于本机网络**:调研期间同一台机器上 `eutils.ncbi.nlm.nih.gov` 与 `scholar.google.com`
都出现过“前一次成功、后一次 `fetch failed`”的交替现象,说明沙箱/网络的出网能力本身不稳定。
因此两个在线套件都按“上游不可用 → SKIP”设计,只有解析/断言层面的错误才判失败。
## 5. 重试后新增的两个修复
重试 Google Scholar 抓取时,真实的 200 页面暴露了两个此前被 429 掩盖的问题,均已修复并加了单测:
1. **分页链接被转义**:`href="...&start=10"`,`[?&]start=` 匹配不到 → 改用 `(?:\?|&|&)start=`;
此前 `truncated` 在 HTML 模式下恒为 `false`。
2. **短页不等于最后一页**:实测 `start=10` 只解析出 2 条却仍有下一页,
改用“页内是否存在更大的 `start` 链接”(`scholarNextStart()`)判断 `truncated`。