xngg1021

literature-analysis

文献分析:相似论文检索、查重比对、矛盾与反证、作者档案、模拟审稿、谬误标注.

xngg1021 1 Updated 1mo ago

Resources

2
GitHub

Install

npx skillscat add xngg1021/academic-research-kernel/literature-analysis

Install via the SkillsCat registry.

SKILL.md

文献分析 Skill

围绕目标论文或学术概念开展分析,涵盖相似论文检索、文本查重比对、矛盾与反证查找、作者学术档案、模拟同行评议以及逻辑谬误标注。本技能与 academic-source-verification 互补:后者专注于核验论文真实性、撤稿记录与获取全文,本技能专注于对文献展开实质性研究分析。

核心原则:每一步结论都给出数据来源(哪个 API、哪个字段、哪段原文),能自动化的自动化,不能自动化的(审稿、谬误判断)交给模型按模板执行并保留可核对引文。

When to Use

  • 用户发一个概念/研究问题,要求"找相似题材的论文"
  • 用户发论文链接或 PDF 文件,要求"找同款研究""查重""看有没有人做过"
  • 用户要求"查找这篇论文的反证""有没有论文反对这个结论"
  • 用户要求"模拟审稿""假装是审稿人审一下这篇"
  • 用户要求"标注这篇文章的逻辑谬误"
  • 用户给出作者姓名,要求查其学术档案
  • 用户要求构建批量文献综述对比矩阵、反查候选投稿期刊,或生成规范的 BibTeX 条目
  • 用户要求双语对照精读学术论文、梳理研究空白,或检索官方开源代码与数据集开展复现

Prerequisites(渠道分层)

按三类渠道聚合,默认只用免费层,白嫖层与付费层由用户自行接入(key 写进环境变量,禁止写进技能文件)。

免费/公开渠道(有配额及配置要求):OpenAlex(匿名 $0.10/日、免费 key $1/日预算,超过预算或 100 req/s 返回 429)、Crossref(无 key,含多语言元数据查询)、Unpaywall(带 email 参数)、arXiv、Europe PMC(api.europepmc.org,无 key)、PubMed E-utilities(无 key,生物医学)、DOAJ。母语文献补充渠道:中文走知网/万方网页检索(无公开 API);日文走 CiNii(按当前具体 API 核实 appid 要求);俄文与东欧文献经 Crossref 收录。

可选账户渠道(访问条件须核对):Semantic Scholar(key 有独立配额(初始 1 req/s);contexts 可用性按当前端点授权/数据判断,不保证 key 解锁或内容齐全)、Scite(试用/访问条件以当前账户为准,查询引用立场)、Dimensions 免费版(仅网页无 API)、CiNii API(日本文献,按当前端点要求配置)。

付费层(自备 key,自行接入):Scopus API、Web of Science API、Dimensions API、Scite Pro。AI 生成成分检测的免费/付费渠道在 academic-writing 技能的工作流 D。

所有请求带 User-Agent(含 mailto)。URL 一律用 https://api.openalex.org/works/...(注意 api 域名;实体 id 形如 https://openalex.org/W... 是实体标识;机器请求使用 API 域名,不保证网页地址的 HTTP 状态)。

OpenAlex 当前查询约定(2026-09-06 核查)

所有 URL 示例均为请求模板,实际用 urllib.parse.urlencode 编码参数;免费 key 用 OPENALEX_API_KEY 环境变量构造 Authorization: Bearer 头。匿名可技术调用但预算较低,按查询类型计费,不能把预算换成固定无限请求数。官方当前每页最多 100;采用 per_page 与 group_by,未证实旧连字符别名有确定移除日期。

取多页从 cursor=* 开始,把 meta.next_cursor 原样编码带回;直到 null、空结果或显式预算上限。达到上限必须标“部分样本”,不能称全部引用者。计数/趋势比较统一 corpus=core(如需扩展则各查询同时用 corpus=all)、日期窗口与文献类型,记录查询时间。

Concepts 已弃用且不再维护,不作为主要路径;旧数据回顾只能显式标 legacy。真正 bibliographic coupling 需比较两文 referenced_works 的交集/并集;co-citation 则需引用者同时引用两篇的计数,两者均不可由 related_works 代替。

官方依据:认证与限额、当前查询速查、字段语义、分页。

工作流 A:相似论文查找

输入有三种形态,先归一化为 DOI 或概念文本:

  1. 概念文本(如"检索增强生成中的幻觉抑制")→ OpenAlex 概念搜索:
    GET https://api.openalex.org/works?search=<概念>&per_page=10&sort=relevance_score:desc&select=title,publication_year,cited_by_count,doi
  2. 论文链接/DOI → 先查该论文本体,再取三层相似:
    • related_works(算法推荐:与目标共享较多 topics 的近期论文,不是引用耦合)
    • topics ID 交叉(用 topics[].id 比较,primary_topic 表示主主题;score 为分类得分,不是论文相似概率)
    • 标题词组合搜索(用论文标题关键词再跑一次概念搜索)
  3. PDF 文件 → pymupdf 提首页标题与摘要文本,用标题/摘要作概念搜索。

输出格式:每个候选论文单列一行(包含标题、年份、被引数、DOI 以及相似判定依据),标明相似依据的来源层次(如文本检索、算法推荐或主题交叉),并依据相关强度排序。概念检索命中按 relevance_score 排序即可。

多路候选合并与去重已抽为脚本(来源层次保留,DOI/标题归一):

python "${HERMES_SKILL_DIR}/scripts/collect_corpus.py" --input layers.json    # 合并 text-search/algorithmic-related/topic-cross 候选
python "${HERMES_SKILL_DIR}/scripts/collect_corpus.py" --query "<概念>"       # 在线概念搜索
python "${HERMES_SKILL_DIR}/scripts/deduplicate_works.py" --input corpus.json # DOI/标题归一去重

工作流 B:查重比对(本地句级)

诚实边界先说明:真正的全网查重需要商业服务(iThenticate/Turnitin),本 skill 做的是"目标论文 vs 候选相似论文集合"的本地句子级比对,用于发现与指定集合的逐字/近似重叠,不声称覆盖全网。

步骤:

  1. 用工作流 A 拿到候选相似论文列表(10~20 篇),能下 OA 全文的尽量下。
  2. 目标论文提全文:doc = pymupdf.open(path); text = " ".join(p.get_text() for p in doc)。
  3. 句子切分与规范化:按句号或换行符分句,滤除空白字符、页码以及参考文献列表,全部转为小写,并过滤长度不足 20 个字符的短句。
  4. 对每篇候选论文做同样的句子集合。
  5. 比对:完全相同的句子(逐字重复)与高度相似句子(difflib 的 SequenceMatcher.ratio() > 0.85)。
  6. 报告:重叠句对数、占目标论文总句数的比例、重叠句子原文与出处段落。
# fragment: API reference; supply the named input variables and required imports.
from difflib import SequenceMatcher
def overlap_ratio(a, b):
    return SequenceMatcher(None, a, b).ratio()

判定参考:单篇候选与目标的重叠率 >5% 或存在连续 3 句以上逐字相同 → 标黄;>15% → 标红。同时报告版本与发表时间、引号/引用和公共方法文本;这些阈值仅作本地筛查提示,时间先后与句子重叠不能单独证明抄袭或方向。目标句子每句只计一次,避免重复匹配使比例超过 100%。

工作流 C:矛盾与反证查找

两层方法,顺序执行:

  1. 引用池甄别(主路径):分页获取目标论文引用者作为候选池,设定预算/页数上限并报告已取数量:
    GET https://api.openalex.org/works?filter=cites:<OpenAlex-WID>&per_page=25&select=id,title,abstract_inverted_index,publication_year,doi
    引用池分页获取与语料内引文网络已抽为 scripts/build_citation_graph.py(预算上限内分页,达到上限标 truncated 只称部分样本):
    python "${HERMES_SKILL_DIR}/scripts/build_citation_graph.py" --wid <OpenAlex-WID> --pages 4
    python "${HERMES_SKILL_DIR}/scripts/build_citation_graph.py" --input works.json   # 离线建语料内引文网络
    摘要三层兜底(缺失率依领域/时间而异,不能从三篇抽样外推):第一层 abstract_inverted_index 重建;第二层缺失时查 Crossref works/{DOI} 的 message.abstract;第三层仍缺则标“证据不足”,尝试 OA 全文(academic-source-verification);标题仅用于召回排序,不能据此确定支持/批评/无法复现。兜底后由模型逐篇判断引用性质(支持/中立/批评/无法复现),只保留批评与无法复现类,并标注每篇的判断依据(摘要/全文;仅标题者列待核)。
  2. 否定词搜索(辅助):用否定句式搜同主题论文:
    search=fail to replicate <主题>、search=challenges <主题> findings、search=<主题> irreproducible
    这类召回偏调查综述,需要从命中里人工筛选真正构成反证的研究。

输出:反证论文列表,每篇附"反证类型"(方法质疑/结论矛盾/无法复现/边界反驳)与证据句(引用原文或摘要句)。诚实声明:反证查找的召回率受摘要可获取性限制,付费墙内的批评可能漏掉;有 Semantic Scholar key 时加查 citations?fields=contexts 用引用上下文提高召回。

工作流 D:作者档案

从论文的 authorships 拿到 author.id(如 A5023888391),然后:

  1. 档案本体:GET https://api.openalex.org/authors/<A-ID> → display_name、works_count、cited_by_count、h_index(在 summary_stats 里)、last_known_institutions。
  2. 代表作品:GET https://api.openalex.org/works?filter=authorships.author.id:<A-ID>&sort=cited_by_count:desc&per_page=10&select=title,cited_by_count,publication_year
  3. 合作网络(可选):filter=authorships.author.id:<A-ID> 加 select=authorships,统计共同作者出现频次。

工作流 E:模拟审稿

模板与评分表见 references/peer-review-template.md。要点:先声明这是模拟审稿、不替代真实同行评审;按模板逐节给出"优点/问题/建议";终评给 1~5 分(强拒/拒/大修/小修/接收)并列出决定性的三条理由;每一条批评必须定位到论文段落(引原文)。

工作流 F:逻辑谬误标注

清单见 references/fallacy-checklist.md。要点:逐段扫描论证结构;标注格式为"段落定位 + 谬误类型 + 为什么成立 + 改写建议";统计谬误类型分布;只标注确定成立的,疑似但拿不准的列"存疑"区,不强行定罪。

工作流 G:批量综述矩阵

20~50 篇论文压成五列对比表(方法/样本/核心结论/局限),按主题聚类分组。执行细节与输出规范见 references/review-matrix.md。矩阵骨架(摘要重建 + 五列表 + 覆盖声明)已抽为 scripts/build_review_matrix.py;方法/样本/结论/局限四列仍由模型逐篇填:

python "${HERMES_SKILL_DIR}/scripts/build_review_matrix.py" --input corpus.json --query "<检索词>" --date <YYYY-MM-DD>

工作流 H:期刊匹配推荐

用论文 topics ID 筛选 works,再按 primary_location.source.id 聚合候选刊源,输出候选期刊与命中理由。分区与影响因子不在数据内,必须声明需另行核对。见 references/journal-matching.md。

工作流 I:BibTeX 导出

从 OpenAlex 元数据生成 BibTeX 条目(含中文文献的 biblatex-gb7714 建议)。见 references/bibtex-guide.md。条目生成已抽为 scripts/export_bibtex.py(tex_escape、姓名格式化与 key 命名规则内置;未知字段留 TODO 不伪造):

python "${HERMES_SKILL_DIR}/scripts/export_bibtex.py" --input works.json --source crossref   # 或 --source openalex

工作流 J:双语对照精读

外文论文分节要点翻译 + 关键句原文 + 全文统一术语表,支持任意语对(默认译为中文,用户可指定目标语言)。见 references/bilingual-reading.md。

工作流 K:研究空白分析

主题组合的论文密度与时间趋势对比,输出交叉区冷热度。措辞只许说"密度低",禁止断言"存在空白"。见 references/research-gap.md。

工作流 L:复现辅助

找官方代码与数据集。旧 Papers with Code v1 API 不作可靠依赖,先核查当前服务状态,走 GitHub 搜索 + HuggingFace + 网页检索。见 references/reproduction.md。

Helper Scripts

scripts/ 内置各工作流的确定性层,均可 --help 查看参数;网络访问集中在明确标记的 live 函数:

脚本 对应工作流 功能
scripts/collect_corpus.py A 多路候选合并(文本检索/算法推荐/主题交叉),保留来源层次
scripts/deduplicate_works.py A/B DOI/标题归一去重(纯离线)
scripts/build_citation_graph.py C 引用者候选池分页获取与语料内引文网络
scripts/build_review_matrix.py G 摘要重建 + 五列矩阵骨架 + 覆盖声明(纯离线)
scripts/export_bibtex.py I Crossref/OpenAlex 元数据 → BibTeX 条目(纯离线)

Procedure

  1. 识别输入形态:概念文本 / 链接 / PDF / 作者名 / 明确指令(审稿、谬误、查重)。
  2. 归一化:链接→DOI(https://api.openalex.org/works/https://doi.org/{DOI}),PDF→标题与摘要文本。
  3. 选择工作流(A~L),组合任务按 A→B→C 顺序推进(找相似→查重→反证)。
  4. 每个结论附数据来源与证据句;无法自动化的判断(批评甄别、谬误认定、审稿意见)附论文原文位置。
  5. 结果用纯文本回报:清单式、可核对、区分"数据事实"与"模型判断"。
  6. 输出语言:默认跟随用户输入语言(用户用什么语言问就用什么语言答),用户明确指定时按指定语言;论文标题与引文一律保留原文,译名首次出现时附原文。

Pitfalls

  1. OpenAlex 请求格式:API 域名是 api.openalex.org;work 实体 id(https://openalex.org/W...)只能当 id 用,直接请求会 403。filter 值用 id 尾段(如 cites:W2919115771、author.id:A5023888391)。
  2. Semantic Scholar 的可用性依限额与配置而异。匿名可尝试公开端点,持续 429 时报告未查到而非空结果;key 不保证 contexts 字段存在。
  3. 查重的诚实边界:本地比对只能覆盖你下载到全文的候选集合,不能声称全网查重。报告里必须写明覆盖范围。
  4. 反证召回有限:引用论文的摘要经常拿不到——OpenAlex 的 abstract_inverted_index 可缺失,三篇抽测不能估计总体缺失率,Crossref 的 abstract 也可能为空(Nature 2015 论文实测为空)。拿不到摘要则取全文或标证据不足,标题只作候选召回,并在报告里标注每篇反证的判断依据。报"未发现反证"时措辞为"在可获取的 N 篇引用文献中未发现批评性引用"。
  5. 相似度不等于抄袭:主题算法推荐(related_works)是正常的学术邻近关系,不能当成抄袭信号。工作流 B 也只提供文本重叠证据,抄袭结论需要语境、引用与版本证据。
  6. 审稿与谬误判断是模型判断,不是数据事实:输出必须分栏标注"API 数据"与"模型判断",后者保留可核对引文,让用户自行复核。
  7. 速率礼貌:OpenAlex 有日预算和 100 req/s 上限,使用 OPENALEX_API_KEY 环境变量;限制总页数并对 429 有界退避,检查剩余额度;同一会话内结果可复用,不要重复请求相同端点。

Verification

# external-test: true
import json, os, time
from urllib.request import Request, urlopen
from urllib.parse import urlsplit, urlencode, quote
from urllib.error import HTTPError

def get(url):
    headers = {'User-Agent': 'academic-research-kernel/1.2'}
    if urlsplit(url).netloc == 'api.openalex.org' and os.environ.get('OPENALEX_API_KEY'):
        headers['Authorization'] = 'Bearer ' + os.environ['OPENALEX_API_KEY']
    for attempt in range(3):
        try:
            with urlopen(Request(url, headers=headers), timeout=20) as response:
                return json.load(response)
        except HTTPError as error:
            if error.code not in {429, 500, 502, 503, 504} or attempt == 2:
                raise
            # 有界退避;长预算封锁交给调用者报告,不循环耗尽免费服务。
            retry = error.headers.get('Retry-After', '')
            delay = float(retry) if retry.isdigit() else 2**attempt
            if delay > 10:
                raise
            time.sleep(delay)

wid = 'W2741809807'
w = get('https://api.openalex.org/works/' + wid + '?' + urlencode({
    'select': 'id,title,doi,topics,primary_topic,related_works,authorships,is_retracted'}))
assert w['id'].endswith('/' + wid) and w['authorships'] and w['topics']
assert all(t['id'].split('/')[-1].startswith('T') for t in w['topics'])
assert isinstance(w['related_works'], list)
query = {'filter': 'topics.id:' + w['topics'][0]['id'].split('/')[-1],
         'per_page': 1, 'cursor': '*', 'select': 'id,topics', 'corpus': 'core'}
first = get('https://api.openalex.org/works?' + urlencode(query))
assert first['results'] and first['meta']['next_cursor']
query['cursor'] = first['meta']['next_cursor']
second = get('https://api.openalex.org/works?' + urlencode(query))
assert second['results'] and first['results'][0]['id'] != second['results'][0]['id']
assert all(any(t['id'] == w['topics'][0]['id'] for t in r['topics']) for r in first['results'] + second['results'])
topic_ids = [t['id'].split('/')[-1] for t in w['topics'][:2]]
both = get('https://api.openalex.org/works?' + urlencode({
    'filter': ','.join('topics.id:' + t for t in topic_ids), 'per_page': 1,
    'select': 'id,topics', 'corpus': 'core'}))
assert both['results'] and all(set(topic_ids) <= {t['id'].split('/')[-1] for t in r['topics']} for r in both['results'])
groups = get('https://api.openalex.org/works?' + urlencode({
    'filter': 'topics.id:' + topic_ids[0], 'group_by': 'primary_location.source.id',
    'per_page': 5, 'corpus': 'core'}))
assert groups['group_by'] and all(isinstance(g['count'], int) for g in groups['group_by'])
search = get('https://api.openalex.org/works?' + urlencode({
    'search': 'attention is all you need', 'per_page': 1, 'select': 'id,title', 'sort': 'relevance_score:desc'}))
assert search['results'] and search['results'][0]['title']
author_id = w['authorships'][0]['author']['id'].split('/')[-1]
author_works = get('https://api.openalex.org/works?' + urlencode({
    'filter': 'authorships.author.id:' + author_id, 'per_page': 1, 'select': 'id,authorships'}))
assert author_works['results'] and any(a['author']['id'].endswith('/'+author_id) for a in author_works['results'][0]['authorships'])
print('literature-analysis topics, cursor, topic AND, source grouping, search and author filter passed')