Delta Lab中国 · 广州

Zotero Short Title Generator

• 17 分钟阅读 •

Zotero Short Title Generator

这个工具可以帮助您为 Zotero 中的文献条目自动生成 Short Title(短标题)。它使用阿里云的 Qwen 模型来分析文章标题和摘要,生成简洁且具有代表性的短标题。

功能特点

使用方法

  1. 安装依赖:
pip install sqlite3 requests
  1. 在代码中设置您的阿里云 API 密钥:
API_KEY = "YOUR_API_KEY_HERE"  # 请替换为您的 API 密钥
  1. 运行脚本:
python generate_short_titles_refactored.py
  1. 脚本会自动查找数据库中的文献条目,生成短标题并保存到数据库中

注意事项

代码结构

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()
文章标签:

上一篇 : 招聘信息抓取爬虫
下一篇 : 本地笔记到公开博客的无缝衔接
留言