背景痛点:文献管理里的“体力活”
写论文最怕什么?不是没思路,而是把时间耗在“搬砖”环节:
- 手动敲ISBN、DOI,浏览器标签页切到飞起;
- PDF下载后忘了重命名,硬盘里躺着一堆“123456.pdf”;
- 导师突然问“去年那篇对比实验放哪了”,结果Zotero里标签乱成麻,搜五分钟才找到。
这些重复劳动,平均一篇文献要折腾 5–7 分钟。做系统综述时,动辄 300 篇起步,光整理就花掉两个工作日。于是我把“让 AI 替我打工”提上日程——用 ChatGPT 读摘要、写概要,用 Zotero 当仓库,中间用 Python 脚本把两者串成一条自动化流水线。跑通后,同样 300 篇,全程无人值守,20 分钟收工,效率提升 10 倍不止。
技术选型:为什么 JSON-RPC 更顺手
Zotero 官方给出两种接口:
- REST:直接 HTTP 调
/users/123/items,返回 JSON,分页头信息在 Link 字段; - JSON-RPC:走
/api/json,把多条操作塞进一个 payload,一次往返批量回包。
ChatGPT 端只有 REST,但支持 HTTP/2,流式返回。为了两边都能“批量化”,我选了 JSON-RPC 模式对接 Zotero,把 50 条创建请求压成一个包,减少 RTT;ChatGPT 侧用 aiohttp 开 HTTP/2,流式拿摘要,网络空等时间几乎为零。实测在 200 M 校园网环境下,300 篇文献的元数据补全,总耗时从 14 分钟降到 90 秒。
核心实现:三步搭好流水线
1. Zotero API 鉴权(OAuth2.0)
注册应用后拿到client_id/secret,用requests-oauthlib走标准码流。注意权限范围一定勾选library:write,否则后续写条目会 403。拿到access_token后塞进X-Zotero-API-Key头即可,token 有效期 1 h,脚本里用refresh_token自动续杯,别等 401 再刷新,容易踩并发坑。
2. ChatGPT 提示工程设计
摘要生成不是“请总结”,而是给模板、给字数、给输出格式,让模型“填空”:
你是一名计算机领域研究员。请用中文写 80–100 字短文,包含:研究目的、方法、主要结果。禁止出现第一人称。 论文标题:{title} 摘要:{abstract}把 prompt 长度压到 120 token 以内,既省费用又提速。temperature 设 0.3,保证每次结果稳定,方便后面做缓存。
3. 元数据校验算法
ISBN 统一转 13 位,按1-3-1-3-1加权模 10 校验;DOI 正则^10\.\d{4,}/.+即可。校验失败直接丢进“待人工”文件夹,不阻塞流水线;通过后再写库,避免脏数据污染 Zotero。
代码示例:异步、重试、缓存一次到位
下面给出核心片段,完整项目已放 GitHub,文末链接自取。
# zotero_client.py import aiohttp, asyncio, hmac, time, os from typing import List, Dict ZOTERO_API = "https://api.zotero.org" REFRESH_URL = "https://www.zotero.org/oauth/token" class ZoteroClient: def __init__(self, api_key: str, user_id: str): self.key = api_key self.uid = user_id self.seqm = 0 # JSON-RPC 序号 async def create_items(self, items: List[Dict]) -> Dict: """批量创建条目,返回成功/失败列表""" payload = { "jsonrpc": "2.0", "method": "items.create", "params": {"items": items}, "id": self.seqm, } self.seqm += 1 headers = { "Authorization": f"Bearer {self.key}", "Content-Type": "application/json", "Zotero-API-Version": "3", } async with aiohttp.ClientSession() as session: async with session.post( f"{ZOTERO_API}/users/{self.uid}/items", json=payload, headers=headers, raise_for_status=True, ) as r: return await r.json()# chatgpt_summarizer.py import aiohttp, backoff from typing import Optional CHATGPT_URL = "https://api.openai.com/v1/chat/completions" class Summarizer: def __init__(self, api_key: str): self.headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } @backoff.on_exception( backoff.expo, aiohttp.ClientError, max_tries=5, max_time=60, ) async def summarize(self, title: str, abstract: str) -> str: payload = { "model": "gpt-3.5-turbo", "temperature": 0.3, "max_tokens": 150, "messages": [ { "role": "user", "content": f"你是一名计算机领域研究员。请用中文写 80–100 字短文,包含:研究目的、方法、主要结果。禁止出现第一人称。\n论文标题:{title}\n摘要:{abstract}", } ], } async with aiohttp.ClientSession() as session: async with session.post( CHATGPT_URL, headers=self.headers, json=payload ) as r: r.raise_for_status() data = await r.json() return data["choices"][0]["message"]["content"].strip()# cache.py import redis.asyncio as redis import json, hashlib class Cache: def __init__(self, url: str = "redis://localhost:6379/0"): self.cli = redis.from_url(url) async def get_summary(self, title: str, abstract: str) -> Optional[str]: key = "sum:" + hashlib.md5(f"{title}{abstract}".encode()).hexdigest() val = await self.cli.get(key) return json.loads(val) if val else None async def set_summary(self, title: str, abstract: str, summary: str, ex=86400): key = "sum:" + hashlib.md5(f"{title}{abstract}".encode()).hexdigest() await self.cli.set(key, json.dumps(summary), ex=ex)主流程用asyncio.gather把“校验 → 查缓存 → 调 ChatGPT → 写 Zotero”四步包在一起,并发度 20,既打满带宽又不触发 429。
生产建议:把玩具变成工业品
性能优化
- 批量大小:Zotero 单次最多 50 条,ChatGPT 并发 20 请求,再高开 429。
- 速率限制:OpenAI 60 次/分钟,脚本里用
asyncio.Semaphore(20)控流;Zotero 官方 100 次/15 秒,超限时在响应头Backoff里给秒数,按指数退退避即可。
安全防护
API 密钥不进代码库,用 Vault 的kv引擎存,脚本启动时通过VAULT_TOKEN拉取,本地开发也走vault agent代理,防止硬编码泄露。
监控方案
Prometheus 埋点三件套:
zotero_requests_total:按状态码计数;openai_tokens_consumed:实时费用;pipeline_latency_seconds:单篇文献端到端耗时。
Grafana 配好面板, latency 突增就告警,通常是缓存击穿或网络抖动,一眼定位。
延伸思考:LLM 的学术诚信红线
AI 生成摘要再爽,也不能直接塞进论文。我的做法是:
- 脚本在 Zotero 的“Extra”字段写“AI_summary_v1”,方便后续筛选;
- 正式引用前必须人工通读原文,确认无曲解;
- 对涉及人类受试者或敏感数据的论文,直接跳过摘要生成,防止模型幻觉泄露隐私。
把边界划清楚,AI 才是助手,不是枪手。
如果你也想把“读文献”变成“看报告”,可以照上面的代码先跑通 MVP,再逐步加缓存、监控、Vault。更省事的做法是直接体验火山引擎的从0打造个人豆包实时通话AI动手实验,里面把 ASR、LLM、TTS 串成一条低延迟语音对话链路,思路跟本文的“流水线”如出一辙,我跟着做完,发现很多异步、重试的 trick 可以直接搬过来用。小白也能 30 分钟跑通,不妨边学边改,把你的专属“文献小秘书”再升级成“语音小助教”。