Zotero Short Title Generator
Zotero Short Title Generator
这个工具可以帮助您为 Zotero 中的文献条目自动生成 Short Title(短标题)。它使用阿里云的 Qwen 模型来分析文章标题和摘要,生成简洁且具有代表性的短标题。
功能特点
- 自动查找 Zotero 数据库中没有 Short Title 的文献条目
- 使用 AI 技术基于文章标题和摘要生成合适的短标题
- 批量处理并保存生成的短标题到数据库
- 自动备份数据库以防意外
使用方法
- 安装依赖:
pip install sqlite3 requests
- 在代码中设置您的阿里云 API 密钥:
API_KEY = "YOUR_API_KEY_HERE" # 请替换为您的 API 密钥
- 运行脚本:
python generate_short_titles_refactored.py
- 脚本会自动查找数据库中的文献条目,生成短标题并保存到数据库中
注意事项
- 脚本会在运行前自动创建数据库备份
- 生成的短标题会保存到 Zotero 数据库中,建议重启 Zotero 以刷新界面
- 如果处理大量文献,脚本会提示确认以避免意外操作
- 请确保 Zotero 已关闭,以避免数据库锁定问题
代码结构
find_all_articles_without_short_title(): 查找所有没有短标题的文献generate_short_title_with_ai(): 使用 AI 生成短标题batch_save_short_titles(): 批量保存短标题到数据库main(): 主函数,协调整个流程
import sqlite3
import os
import requests
import json
import time
def find_all_articles_without_short_title(db_path):
"""
一次性查找所有没有Short Title的文章
Args:
db_path (str): Zotero SQLite数据库文件路径
Returns:
list: 没有Short Title的文章列表
"""
if not os.path.exists(db_path):
print(f"数据库文件未找到: {db_path}")
return []
try:
# 使用只读模式打开数据库
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
cursor = conn.cursor()
# 获取字段ID
cursor.execute("SELECT fieldID FROM fields WHERE fieldName = 'title'")
title_field_result = cursor.fetchone()
title_field = title_field_result[0] if title_field_result else 1
cursor.execute("SELECT fieldID FROM fields WHERE fieldName = 'shortTitle'")
short_title_field_result = cursor.fetchone()
short_title_field = short_title_field_result[0] if short_title_field_result else 8
cursor.execute("SELECT fieldID FROM fields WHERE fieldName = 'abstractNote'")
abstract_field_result = cursor.fetchone()
abstract_field = abstract_field_result[0] if abstract_field_result else 2
# 查找有标题但没有Short Title的文章(确保Short Title字段不存在或为空)
query = """
SELECT
i.itemID,
it.typeName,
idv_title.value as title,
idv_abstract.value as abstract
FROM items i
LEFT JOIN itemTypes it ON i.itemTypeID = it.itemTypeID
LEFT JOIN itemData id_title ON i.itemID = id_title.itemID AND id_title.fieldID = ?
LEFT JOIN itemDataValues idv_title ON id_title.valueID = idv_title.valueID
LEFT JOIN itemData id_abstract ON i.itemID = id_abstract.itemID AND id_abstract.fieldID = ?
LEFT JOIN itemDataValues idv_abstract ON id_abstract.valueID = idv_abstract.valueID
WHERE i.itemTypeID NOT IN (
SELECT itemTypeID FROM itemTypes WHERE typeName IN ('note', 'attachment')
)
AND idv_title.value IS NOT NULL
AND i.itemID NOT IN (
SELECT DISTINCT id2.itemID
FROM itemData id2
JOIN itemDataValues idv2 ON id2.valueID = idv2.valueID
WHERE id2.fieldID = ?
AND idv2.value IS NOT NULL
AND TRIM(idv2.value) != ''
)
ORDER BY i.itemID
"""
cursor.execute(query, (title_field, abstract_field, short_title_field))
results = cursor.fetchall()
conn.close()
return results
except sqlite3.Error as e:
print(f"数据库错误: {e}")
return []
except Exception as e:
print(f"错误: {e}")
return []
def generate_short_title_with_ai(title, abstract, api_key, api_url="https://dashscope.aliyuncs.com/compatible-mode/v1/"):
"""
使用阿里云Qwen模型生成Short Title
Args:
title (str): 文章标题
abstract (str): 文章摘要
api_key (str): API密钥
api_url (str): API地址
Returns:
str: 生成的Short Title
"""
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
# 如果没有摘要,则使用空字符串
if not abstract:
abstract = ""
prompt = f"""你的任务是为给定的学术文章生成一个简洁的Short Title(短标题)。以下是文章的标题和摘要:
<文章标题>
{title}
</文章标题>
<文章摘要>
{abstract}
</文章摘要>
生成的Short Title应满足以下要求:
1. 只保留核心关键词,突出特定材料或者方法。
2. 长度控制在3 - 5个汉字。
3. 去除冗余词汇,如"研究"、"性能"、"效率"、"模拟"等等。
请只返回Short Title,不要包含其他解释文字。
"""
data = {
"model": "qwen-flash",
"messages": [
{
"role": "user",
"content": prompt
}
],
"max_tokens": 100,
"temperature": 0.6
}
try:
response = requests.post(f"{api_url}chat/completions", headers=headers, json=data, timeout=30)
if response.status_code == 200:
result = response.json()
if 'choices' in result and len(result['choices']) > 0:
short_title = result['choices'][0]['message']['content'].strip()
# 清理可能的引号
short_title = short_title.strip('"').strip("'")
return short_title
else:
print(f"API响应格式错误: {result}")
return None
else:
print(f"API请求失败,状态码: {response.status_code}")
print(f"响应内容: {response.text}")
return None
except requests.exceptions.RequestException as e:
print(f"API请求异常: {e}")
return None
except Exception as e:
print(f"生成Short Title时出错: {e}")
return None
def batch_save_short_titles(db_path, short_titles_data):
"""
批量保存Short Title到数据库
Args:
db_path (str): 数据库路径
short_titles_data (list): [(item_id, short_title), ...]
Returns:
int: 成功保存的数量
"""
if not short_titles_data:
return 0
success_count = 0
try:
conn = sqlite3.connect(db_path, timeout=60.0)
cursor = conn.cursor()
# 获取shortTitle字段ID
cursor.execute("SELECT fieldID FROM fields WHERE fieldName = 'shortTitle'")
short_title_field_result = cursor.fetchone()
short_title_field = short_title_field_result[0] if short_title_field_result else 8
for item_id, short_title in short_titles_data:
try:
# 检查是否已经存在Short Title
cursor.execute("SELECT itemID FROM itemData WHERE itemID = ? AND fieldID = ?",
(item_id, short_title_field))
existing = cursor.fetchone()
if existing:
print(f"文章 {item_id} 已经有Short Title,跳过")
continue
# 检查是否已存在相同的Short Title值
cursor.execute("SELECT valueID FROM itemDataValues WHERE value = ?", (short_title,))
existing_value = cursor.fetchone()
if existing_value:
# 使用已存在的valueID
value_id = existing_value[0]
else:
# 插入新的Short Title值
cursor.execute("INSERT INTO itemDataValues (value) VALUES (?)", (short_title,))
value_id = cursor.lastrowid
# 插入到itemData表
cursor.execute("INSERT INTO itemData (itemID, fieldID, valueID) VALUES (?, ?, ?)",
(item_id, short_title_field, value_id))
print(f"成功为文章 {item_id} 保存Short Title: {short_title}")
success_count += 1
except sqlite3.Error as e:
print(f"保存文章 {item_id} 的Short Title时出错: {e}")
continue
conn.commit()
conn.close()
return success_count
except sqlite3.Error as e:
print(f"批量保存Short Title到数据库时出错: {e}")
return success_count
except Exception as e:
print(f"批量保存Short Title时出错: {e}")
return success_count
def main():
# 配置信息
API_KEY = "YOUR_API_KEY_HERE" # 请替换为您的API密钥
API_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1/"
# 数据库路径 - 直接修改为硬编码路径
db_path = r"C:\Users\YourUsername\Zotero\zotero.sqlite" # 请修改为您的实际数据库路径
if not os.path.exists(db_path):
print(f"数据库文件未找到: {db_path}")
print("请修改脚本中的数据库路径为您本地的Zotero数据库路径")
return
print(f"使用数据库: {db_path}")
# 创建备份
backup_path = db_path + ".backup_batch_short_titles"
print(f"正在创建数据库备份: {backup_path}")
try:
with open(db_path, 'rb') as original:
with open(backup_path, 'wb') as backup:
backup.write(original.read())
print("备份创建成功!")
except Exception as e:
print(f"创建备份失败: {e}")
return
# 查找所有没有Short Title的文章
print("\n正在查找所有没有Short Title的文章...")
articles = find_all_articles_without_short_title(db_path)
if not articles:
print("没有找到需要生成Short Title的文章。")
return
print(f"找到 {len(articles)} 篇需要生成Short Title的文章")
# 询问用户是否继续
if len(articles) > 20:
response = input(f"\n发现 {len(articles)} 篇文章需要处理,这可能需要较长时间。是否继续?(y/n): ")
if response.lower() != 'y':
print("操作已取消。")
return
print("\n开始生成Short Title...")
generated_data = []
success_count = 0
for i, (item_id, type_name, title, abstract) in enumerate(articles, 1):
print(f"\n[{i}/{len(articles)}] 处理文章 ID: {item_id}")
print(f"类型: {type_name}")
print(f"标题: {title[:80]}{'...' if len(title) > 80 else ''}")
# 生成Short Title
short_title = generate_short_title_with_ai(title, abstract, API_KEY, API_URL)
if short_title:
print(f"生成的Short Title: {short_title}")
generated_data.append((item_id, short_title))
success_count += 1
else:
print("生成Short Title失败")
# 每处理10个或到达最后一个时,批量保存
if len(generated_data) >= 10 or i == len(articles):
if generated_data:
print(f"\n正在批量保存 {len(generated_data)} 个Short Title...")
saved_count = batch_save_short_titles(db_path, generated_data)
print(f"成功保存 {saved_count} 个Short Title")
generated_data = [] # 清空已保存的数据
# 添加延迟避免API限制
if i < len(articles):
time.sleep(1)
print(f"\n处理完成!成功生成 {success_count} 个Short Title。")
print("建议重启Zotero以刷新界面。")
if __name__ == "__main__":
main()