- name
- zotero-scholar
- description
- 将论文保存到 Zotero 文库,请按照 userid:apiKey 的格式配置 ZOTERO_CREDENTIALS 环境变量。
- homepage
- https://www.zotero.org
- metadata
Zotero Scholar
专业的文献入库助手。可以将论文元数据、PDF 链接以及 AI 生成的总结一键保存到你的 Zotero 库中。
使用示例
可以读取环境变量 ZOTERO_CREDENTIALS 中的 Zotero 凭据,格式为 userid:apiKey。
使用环境变量运行
uv run {baseDir}/scripts/save_paper.py \
--title "Attention Is All You Need" \
--authors "Vaswani et al." \
--url "https://arxiv.org/abs/1706.03762"参数说明
| 参数 | 说明 |
|---|---|
--title | 论文标题 |
--authors | 作者列表(逗号分隔) |
--url | 论文链接 (用于排重) |
--abstract | 论文摘要 |
--summary | (AI 生成) 简短总结或 Insight |
--tags | 标签列表(逗号分隔) |
save_paper.py文件内容: #!/usr/bin/env python3
/// script
requires-python = ">=3.10"
dependencies = ["pyzotero>=1.6.0"]
///
import argparse import os import sys from pyzotero import zotero
def main(): parser = argparse.ArgumentParser(description="保存论文到 Zotero 文献库") parser.add_argument("--title", required=True, help="论文标题") parser.add_argument("--authors", required=True, help="作者列表,以逗号分隔") parser.add_argument("--url", required=True, help="论文链接(用于查重)") parser.add_argument("--abstract", help="论文摘要") parser.add_argument("--summary", help="AI 生成的摘要") parser.add_argument("--tags", help="标签列表,以逗号分隔")
args = parser.parse_args()
library_id = None api_key = None
zotero_creds = os.environ.get('ZOTERO_CREDENTIALS') if zotero_creds and ':' in zotero_creds: try: parts = zotero_creds.strip().split(':') if len(parts) == 2: library_id = parts[0].strip() api_key = parts[1].strip() except: pass
library_type = 'user' # 默认使用个人文献库
if not library_id or not api_key: print("错误:需要 Zotero 凭据。", file=sys.stderr) print("请设置 ZOTERO_CREDENTIALS='userID:apiKey'(用于 UI 配置)", file=sys.stderr) sys.exit(1)
try: zot = zotero.Zotero(library_id, library_type, api_key)
# 通过 URL 检查是否已存在 # 使用搜索查询匹配 URL print(f"正在检查是否已存在该条目:{args.url}...") items = zot.items(q=args.url, limit=1)
if items: print(f"该条目已存在:{items[0].get('data', {}).get('title', '未知')}") # 暂时跳过更新,避免覆盖用户数据 print(f"Zotero 链接:https://www.zotero.org/{library_id}/items/{items[0]['key']}") return
# 创建新条目 template = zot.item_template('journalArticle') template['title'] = args.title
# 格式化作者信息 creators = [] for author in args.authors.split(','): name_parts = author.strip().split(' ') if len(name_parts) > 1: creators.append({'creatorType': 'author', 'firstName': ' '.join(name_parts[:-1]), 'lastName': name_parts[-1]}) else: creators.append({'creatorType': 'author', 'lastName': author.strip(), 'firstName': ''}) template['creators'] = creators
template['url'] = args.url if args.abstract: template['abstractNote'] = args.abstract
# 添加标签 if args.tags: template['tags'] = [{'tag': t.strip()} for t in args.tags.split(',')]
print(f"正在创建条目:{args.title}...") resp = zot.create_items([template])
if resp.get('successful'): new_item = resp['successful']['0'] item_key = new_item['key'] print(f"条目创建成功!")
# 将摘要添加为笔记 if args.summary: print("正在添加摘要笔记...") note_template = zot.item_template('note') note_template['note'] = f"<h3>AI 摘要</h3><p>{args.summary}</p>" note_template['parentItem'] = item_key zot.create_items([note_template]) print("笔记已添加。")
# 下载并附加 PDF if 'arxiv.org' in args.url: try: import urllib.request import tempfile
# 将摘要链接转换为 PDF 链接 pdf_url = args.url.replace('/abs/', '/pdf/') if not pdf_url.endswith('.pdf'): pdf_url += '.pdf'
print(f"正在下载 PDF...")
# 设置 User-Agent 以支持下载 opener = urllib.request.build_opener() opener.addheaders = [('User-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/120.0.0.0')] urllib.request.install_opener(opener)
# 创建安全的文件名 safe_title = "".join(c for c in args.title if c.isalnum() or c in (" ", "-", "_")).strip() safe_title = safe_title[:50] # 限制长度 safe_filename = f"{safe_title}.pdf"
# 使用临时目录,但指定文件名 with tempfile.TemporaryDirectory() as td: pdf_path = os.path.join(td, safe_filename) urllib.request.urlretrieve(pdf_url, pdf_path)
print(f"正在上传 PDF 附件({safe_filename})...") zot.attachment_simple([pdf_path], item_key) print("PDF 已附加。")
except Exception as e: print(f"附加 PDF 失败:{e}", file=sys.stderr)
print("注意: 请在 Zotero 客户端点击 '同步' (Sync) 按钮以获取云端 PDF 文件。") print(" 如果是 Web API 写入,本地客户端必须同步才能看到附件内容。")
else: print(f"创建条目失败:{resp}", file=sys.stderr) sys.exit(1)
except Exception as e: print(f"错误:{e}", file=sys.stderr) sys.exit(1)
if __name__ == "__main__": main()