引言:老片经典台词的魅力与价值
老片经典台词是电影艺术的精华,它们往往浓缩了时代精神、人物情感和文化内涵。这些台词不仅在当时引起共鸣,更在岁月的洗礼下历久弥新,成为流行文化的重要组成部分。例如,《乱世佳人》中的”明天又是新的一天”(Tomorrow is another day)传递出坚韧不拔的乐观精神;《教父》中的”我将给他一个他无法拒绝的条件”(I’m gonna make him an offer he can’t refuse)则展现了权力与威胁的艺术。这些台词之所以经典,是因为它们简洁有力、富有哲理,能够跨越时空与观众产生情感连接。
对于电影爱好者、研究者或普通观众而言,查询老片经典台词的需求日益增长。无论是为了重温经典、撰写影评,还是寻找特定场景的对话,一个高效的台词查询系统都显得尤为重要。本文将详细介绍如何构建一个老片经典台词查询系统,包括数据收集、存储、检索和展示等完整流程,并提供详尽的代码示例,帮助读者从零开始打造属于自己的台词库。
系统架构概述
一个完整的老片经典台词查询系统通常包含以下几个核心模块:
- 数据收集模块:负责从各种来源获取电影台词数据,包括电影基本信息、台词文本、出现时间等。
- 数据存储模块:设计合理的数据库结构来存储海量台词数据,确保查询效率。
- 检索引擎模块:实现高效的搜索算法,支持关键词、演员、电影名等多维度查询。
- 用户界面模块:提供友好的交互界面,方便用户输入查询条件并展示结果。
- API接口模块:为其他应用提供数据接口,实现系统集成。
我们将采用Python作为主要开发语言,结合SQLite数据库和Flask框架构建一个轻量级但功能完整的查询系统。这种技术栈易于上手,适合个人项目和中小型应用。
数据收集与处理
数据来源
收集老片经典台词的第一步是确定可靠的数据来源。以下是几个常用渠道:
- 公开API:如OMDb API(The Open Movie Database)提供电影基本信息,但台词数据有限。
- 电影字幕文件:SRT格式的字幕文件包含完整的对话内容,是台词数据的主要来源。
- 电影剧本网站:如Scripts.com、IMSDB等网站提供电影剧本的文本版本。
- 手动整理:对于特别经典的电影,可以通过观看影片并手动记录的方式收集。
数据格式标准化
为了便于存储和查询,我们需要将收集到的台词数据标准化为统一的格式。一个典型的台词记录应包含以下字段:
- movie_id:电影唯一标识
- movie_title:电影标题
- year:上映年份
- character:说话角色
- dialogue:台词文本
- timestamp:出现时间(可选)
- scene:场景描述(可选)
数据收集示例代码
以下是一个从SRT字幕文件提取台词的Python脚本示例。该脚本读取SRT文件,解析时间戳和对话内容,并将其转换为结构化数据。
import re
from datetime import timedelta
def parse_srt(file_path):
"""
解析SRT字幕文件,提取台词数据
:param file_path: SRT文件路径
:return: 包含台词数据的列表,每个元素为字典
"""
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
# SRT格式正则表达式:序号 + 时间戳 + 内容
pattern = re.compile(r'(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n(.*?)(?=\n\n|\Z)', re.DOTALL)
matches = pattern.findall(content)
dialogues = []
for match in matches:
index, start_time, end_time, text = match
# 清理文本:移除换行符和多余空格
cleaned_text = re.sub(r'\n', ' ', text).strip()
# 尝试识别角色(简单规则:大写字母开头的短句可能是角色名)
# 实际应用中可能需要更复杂的NLP处理或人工标注
character = "Unknown"
if cleaned_text and cleaned_text[0].isupper() and len(cleaned_text.split()) <= 3:
character = cleaned_text
cleaned_text = ""
dialogues.append({
'index': int(index),
'start_time': start_time,
'end_time': end_time,
'character': character,
'dialogue': cleaned_text
})
return dialogues
# 示例使用
if __name__ == "__main__":
srt_file = "example_movie.srt"
try:
data = parse_srt(srt_file)
print(f"成功解析 {len(data)} 条台词")
# 打印前3条
for item in data[:3]:
print(f"时间: {item['start_time']} - {item['end_time']}")
print(f"角色: {item['character']}")
print(f"台词: {item['dialogue']}\n")
except FileNotFoundError:
print(f"错误:文件 {srt_file} 不存在")
except Exception as e:
print(f"解析出错: {e}")
代码说明:
- 使用正则表达式匹配SRT文件的结构(序号、时间戳、内容)。
- 清理文本中的换行符和多余空格。
- 尝试通过简单规则识别角色名(实际应用中可能需要更复杂的NLP技术)。
- 返回结构化的台词数据列表。
数据清洗与增强
原始数据往往需要进一步清洗和增强才能用于查询系统:
- 文本标准化:统一大小写、去除标点、处理特殊字符。
- 角色识别:通过脚本或人工方式补充角色信息。
- 情感标注:为台词添加情感标签(如幽默、悲伤、愤怒)。
- 关键词提取:自动提取台词中的关键词,便于检索。
以下是一个简单的文本标准化函数:
import string
def standardize_text(text):
"""
标准化文本:转小写、去标点、去多余空格
:param text: 原始文本
:return: 标准化后的文本
"""
# 转小写
text = text.lower()
# 去除标点
text = text.translate(str.maketrans('', '', string.punctuation))
# 去除多余空格
text = ' '.join(text.split())
return text
# 示例
original = "I'm gonna make him an offer he can't refuse."
standardized = standardize_text(original)
print(f"原始: {original}")
print(f"标准化: {standardized}")
数据库设计与实现
数据库选择
对于老片经典台词查询系统,SQLite是一个理想的选择,因为它轻量级、无需服务器、易于部署。对于更大规模的应用,可以考虑PostgreSQL或MySQL。
数据表结构
我们需要设计两个主要表:movies(电影信息)和dialogues(台词内容)。
-- 电影信息表
CREATE TABLE movies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
year INTEGER,
director TEXT,
genre TEXT,
imdb_rating REAL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 台词表
CREATE TABLE dialogues (
id INTEGER PRIMARY KEY AUTOINCREMENT,
movie_id INTEGER NOT NULL,
character_name TEXT,
dialogue_text TEXT NOT NULL,
start_time TEXT,
end_time TEXT,
scene_description TEXT,
FOREIGN KEY (movie_id) REFERENCES movies(id)
);
-- 为常用查询字段创建索引
CREATE INDEX idx_dialogues_text ON dialogues(dialogue_text);
CREATE INDEX idx_dialogues_movie ON dialogues(movie_id);
CREATE INDEX idx_dialogues_character ON dialogues(character_name);
CREATE INDEX idx_movies_title ON movies(title);
使用Python操作SQLite
以下代码展示了如何使用Python的sqlite3模块创建数据库、插入数据和执行查询。
import sqlite3
from contextlib import closing
class MovieQuoteDB:
def __init__(self, db_path='movie_quotes.db'):
self.db_path = db_path
self.init_database()
def init_database(self):
"""初始化数据库,创建表"""
with closing(sqlite3.connect(self.db_path)) as conn:
cursor = conn.cursor()
# 创建电影表
cursor.execute('''
CREATE TABLE IF NOT EXISTS movies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
year INTEGER,
director TEXT,
genre TEXT,
imdb_rating REAL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# 创建台词表
cursor.execute('''
CREATE TABLE IF NOT EXISTS dialogues (
id INTEGER PRIMARY KEY AUTOINCREMENT,
movie_id INTEGER NOT NULL,
character_name TEXT,
dialogue_text TEXT NOT NULL,
start_time TEXT,
end_time TEXT,
scene_description TEXT,
FOREIGN KEY (movie_id) REFERENCES movies(id)
)
''')
# 创建索引
cursor.execute('CREATE INDEX IF NOT EXISTS idx_dialogues_text ON dialogues(dialogue_text)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_dialogues_movie ON dialogues(movie_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_movies_title ON movies(title)')
conn.commit()
def add_movie(self, title, year=None, director=None, genre=None, imdb_rating=None):
"""添加电影信息,返回电影ID"""
with closing(sqlite3.connect(self.db_path)) as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO movies (title, year, director, genre, imdb_rating)
VALUES (?, ?, ?, ?, ?)
''', (title, year, director, genre, imdb_rating))
conn.commit()
return cursor.lastrowid
def add_dialogue(self, movie_id, character_name, dialogue_text, start_time=None, end_time=None, scene_description=None):
"""添加台词"""
with closing(sqlite3.connect(self.db_path)) as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO dialogues (movie_id, character_name, dialogue_text, start_time, end_time, scene_description)
VALUES (?, ?, ?, ?, ?, ?)
''', (movie_id, character_name, dialogue_text, start_time, end_time, scene_description))
conn.commit()
def search_quotes(self, keyword, movie_title=None, character_name=None, limit=20):
"""
搜索台词
:param keyword: 搜索关键词
:param movie_title: 可选,电影标题过滤
:param character_name: 可选,角色名过滤
:param limit: 返回结果数量限制
:return: 包含台词信息的列表
"""
with closing(sqlite3.connect(self.db_path)) as conn:
cursor = conn.cursor()
# 构建查询
query = '''
SELECT d.id, m.title, m.year, d.character_name, d.dialogue_text, d.start_time
FROM dialogues d
JOIN movies m ON d.movie_id = m.id
WHERE d.dialogue_text LIKE ?
'''
params = [f'%{keyword}%']
if movie_title:
query += ' AND m.title LIKE ?'
params.append(f'%{movie_title}%')
if character_name:
query += ' AND d.character_name LIKE ?'
params.append(f'%{character_name}%')
query += ' LIMIT ?'
params.append(limit)
cursor.execute(query, params)
results = cursor.fetchall()
# 格式化结果
formatted_results = []
for row in results:
formatted_results.append({
'id': row[0],
'movie_title': row[1],
'year': row[2],
'character': row[3],
'dialogue': row[4],
'timestamp': row[5]
})
return formatted_results
# 示例使用
if __name__ == "__main__":
db = MovieQuoteDB()
# 添加电影《教父》
godfather_id = db.add_movie("The Godfather", 1972, "Francis Ford Coppola", "Crime,Drama", 9.2)
# 添加经典台词
db.add_dialogue(godfather_id, "Don Corleone", "I'm gonna make him an offer he can't refuse.", "00:45:30,000", "00:45:32,500", "办公室谈判场景")
db.add_dialogue(godfather_id, "Michael Corleone", "It's not personal, it's strictly business.", "01:15:10,000", "01:15:12,500", "餐厅复仇场景")
# 搜索台词
results = db.search_quotes("offer")
for result in results:
print(f"电影: {result['movie_title']} ({result['year']})")
print(f"角色: {result['character']}")
print(f"台词: {result['dialogue']}\n")
代码说明:
MovieQuoteDB类封装了所有数据库操作。init_database方法创建必要的表和索引。add_movie和add_dialogue方法用于添加数据。search_quotes方法实现核心搜索功能,支持多条件过滤。- 使用
contextlib.closing确保数据库连接正确关闭。
检索引擎优化
基础搜索 vs 模糊搜索
基础的LIKE查询在处理大量数据时可能效率低下,且对拼写错误不友好。我们可以引入模糊搜索技术:
- 全文索引:SQLite的FTS5扩展提供高效的全文搜索。
- 模糊匹配算法:如Levenshtein距离、Jaro-Winkler距离。
- 同义词扩展:将”love”扩展为”adore, affection, fondness”等。
使用SQLite FTS5实现全文搜索
以下代码演示如何使用FTS5创建虚拟表并实现高效搜索。
class FTSQuoteDB(MovieQuoteDB):
"""扩展基础数据库类,支持全文搜索"""
def init_database(self):
super().init_database()
with closing(sqlite3.connect(self.db_path)) as conn:
cursor = conn.cursor()
# 创建FTS5虚拟表
cursor.execute('''
CREATE VIRTUAL TABLE IF NOT EXISTS dialogues_fts
USING fts5(
dialogue_text,
character_name,
movie_title,
content=dialogues,
content_rowid=id
)
''')
# 创建触发器,保持FTS表与主表同步
cursor.execute('''
CREATE TRIGGER IF NOT EXISTS dialogues_ai AFTER INSERT ON dialogues BEGIN
INSERT INTO dialogues_fts(rowid, dialogue_text, character_name, movie_title)
SELECT id, dialogue_text, character_name, (SELECT title FROM movies WHERE id = dialogues.movie_id)
FROM dialogues WHERE id = new.id;
END;
''')
cursor.execute('''
CREATE TRIGGER IF NOT EXISTS dialogues_ad AFTER DELETE ON dialogues BEGIN
DELETE FROM dialogues_fts WHERE rowid = old.id;
END;
''')
conn.commit()
def add_dialogue(self, movie_id, character_name, dialogue_text, start_time=None, end_time=None, scene_description=None):
"""重写添加方法,确保FTS表同步"""
# 先调用父类方法
super().add_dialogue(movie_id, character_name, dialogue_text, start_time, end_time, scene_description)
# FTS表通过触发器自动更新,无需手动操作
def search_quotes_fts(self, keyword, limit=20):
"""
使用FTS5进行全文搜索
:param keyword: 搜索关键词
:param limit: 结果限制
:return: 搜索结果列表
"""
with closing(sqlite3.connect(self.db_path)) as conn:
cursor = conn.cursor()
# 使用MATCH操作符进行全文搜索
query = '''
SELECT d.id, m.title, m.year, d.character_name, d.dialogue_text, d.start_time
FROM dialogues_fts fts
JOIN dialogues d ON fts.rowid = d.id
JOIN movies m ON d.movie_id = m.id
WHERE dialogues_fts MATCH ?
ORDER BY rank
LIMIT ?
'''
cursor.execute(query, (keyword, limit))
results = cursor.fetchall()
formatted_results = []
for row in results:
formatted_results.append({
'id': row[0],
'movie_title': row[1],
'year': row[2],
'character': row[3],
'dialogue': row[4],
'timestamp': row[5]
})
return formatted_results
# 示例使用
if __name__ == "__main__":
db = FTSQuoteDB()
# 添加更多数据
casablanca_id = db.add_movie("Casablanca", 1942, "Michael Curtiz", "Romance,Drama", 8.5)
db.add_dialogue(casablanca_id, "Rick Blaine", "Here's looking at you, kid.", "01:30:45,000", "01:30:47,500", "机场告别场景")
db.add_dialogue(casablanca_id, "Rick Blaine", "We'll always have Paris.", "01:35:20,000", "01:35:22,500", "酒吧回忆场景")
# 全文搜索
results = db.search_quotes_fts("looking")
for result in results:
print(f"电影: {result['movie_title']} ({result['year']})")
print(f"角色: {result['character']}")
print(f"台词: {result['dialogue']}\n")
代码说明:
FTSQuoteDB继承自MovieQuoteDB,扩展全文搜索功能。- 创建FTS5虚拟表
dialogues_fts,包含台词文本、角色名和电影标题。 - 使用触发器自动同步主表和FTS表的数据。
search_quotes_fts方法使用MATCH操作符进行高效搜索,支持词干提取和排名。
模糊匹配算法
当用户输入可能有拼写错误时,模糊匹配非常有用。以下是一个基于Levenshtein距离的简单实现。
def levenshtein_distance(s1, s2):
"""
计算两个字符串之间的Levenshtein距离
:param s1: 字符串1
:param s2: 字符串2
:return: 编辑距离
"""
if len(s1) < len(s2):
return levenshtein_distance(s2, s1)
if len(s2) == 0:
return len(s1)
previous_row = range(len(s2) + 1)
for i, c1 in enumerate(s1):
current_row = [i + 1]
for j, c2 in enumerate(s2):
insertions = previous_row[j + 1] + 1
deletions = current_row[j] + 1
substitutions = previous_row[j] + (c1 != c2)
current_row.append(min(insertions, deletions, substitutions))
previous_row = current_row
return previous_row[-1]
def fuzzy_search(keyword, dialogue_list, max_distance=2):
"""
模糊搜索函数
:param keyword: 搜索关键词
:param dialogue_list: 台词列表(从数据库获取)
:param max_distance: 最大允许距离
:return: 匹配的台词列表
"""
results = []
for dialogue in dialogue_list:
# 计算关键词与台词的编辑距离
distance = levenshtein_distance(keyword.lower(), dialogue['dialogue'].lower())
if distance <= max_distance:
# 计算相似度分数(0-1)
similarity = 1 - (distance / max(len(keyword), len(dialogue['dialogue'])))
dialogue['similarity'] = similarity
results.append(dialogue)
# 按相似度排序
results.sort(key=lambda x: x['similarity'], reverse=True)
return results
# 示例使用
if __name__ == "__main__":
# 模拟数据库查询结果
sample_dialogues = [
{'dialogue': "I'm gonna make him an offer he can't refuse.", 'movie_title': 'The Godfather'},
{'dialogue': "Here's looking at you, kid.", 'movie_title': 'Casablanca'},
{'dialogue': "We'll always have Paris.", 'movie_title': 'Casablanca'}
]
# 模糊搜索"offr"(故意拼写错误)
results = fuzzy_search("offr", sample_dialogues)
for result in results:
print(f"匹配度: {result['similarity']:.2f} - {result['movie_title']}: {result['dialogue']}")
代码说明:
levenshtein_distance函数计算两个字符串的编辑距离。fuzzy_search函数将每个台词与关键词比较,计算相似度。- 结果按相似度排序,返回最可能的匹配项。
- 这种方法适合在数据库查询结果基础上进行后处理,或在小规模数据集上使用。
Web API接口开发
使用Flask构建RESTful API
为了让系统更易用,我们可以通过Web API提供服务。Flask是一个轻量级的Python Web框架,适合快速开发。
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
from contextlib import closing
app = Flask(__name__)
CORS(app) # 允许跨域请求
class QuoteAPI:
def __init__(self, db_path='movie_quotes.db'):
self.db_path = db_path
def search_quotes(self, keyword, movie=None, character=None, limit=20):
"""API使用的搜索方法"""
with closing(sqlite3.connect(self.db_path)) as conn:
cursor = conn.cursor()
# 使用FTS5搜索
query = '''
SELECT d.id, m.title, m.year, d.character_name, d.dialogue_text, d.start_time
FROM dialogues_fts fts
JOIN dialogues d ON fts.rowid = d.id
JOIN movies m ON d.movie_id = m.id
WHERE dialogues_fts MATCH ?
'''
params = [keyword]
# 添加过滤条件
if movie:
query += ' AND m.title LIKE ?'
params.append(f'%{movie}%')
if character:
query += ' AND d.character_name LIKE ?'
params.append(f'%{character}%')
query += ' LIMIT ?'
params.append(limit)
cursor.execute(query, params)
results = cursor.fetchall()
return [{
'id': row[0],
'movie_title': row[1],
'year': row[2],
'character': row[3],
'dialogue': row[4],
'timestamp': row[5]
} for row in results]
# 初始化API
api = QuoteAPI()
@app.route('/api/search', methods=['GET'])
def search():
"""
搜索台词API
参数:
keyword: 搜索关键词(必填)
movie: 电影标题过滤(可选)
character: 角色名过滤(可选)
limit: 结果数量限制(可选,默认20)
"""
keyword = request.args.get('keyword')
if not keyword:
return jsonify({'error': 'keyword parameter is required'}), 400
movie = request.args.get('movie')
character = request.args.get('character')
limit = int(request.args.get('limit', 20))
try:
results = api.search_quotes(keyword, movie, character, limit)
return jsonify({
'status': 'success',
'count': len(results),
'results': results
})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/api/random', methods=['GET'])
def random_quote():
"""随机获取一条台词"""
try:
with closing(sqlite3.connect(api.db_path)) as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT d.id, m.title, m.year, d.character_name, d.dialogue_text, d.start_time
FROM dialogues d
JOIN movies m ON d.movie_id = m.id
ORDER BY RANDOM()
LIMIT 1
''')
row = cursor.fetchone()
if row:
result = {
'id': row[0],
'movie_title': row[1],
'year': row[2],
'character': row[3],
'dialogue': row[4],
'timestamp': row[5]
}
return jsonify({'status': 'success', 'result': result})
else:
return jsonify({'status': 'error', 'message': 'No quotes found'}), 404
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/api/movies', methods=['GET'])
def list_movies():
"""获取所有电影列表"""
try:
with closing(sqlite3.connect(api.db_path)) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, title, year, director FROM movies ORDER BY year DESC')
movies = [{'id': row[0], 'title': row[1], 'year': row[2], 'director': row[3]} for row in cursor.fetchall()]
return jsonify({'status': 'success', 'movies': movies})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
if __name__ == '__main__':
# 确保数据库存在
db = MovieQuoteDB()
app.run(debug=True, host='0.0.0.0', port=5000)
代码说明:
- 使用Flask创建RESTful API,提供搜索、随机获取和电影列表接口。
CORS中间件允许前端跨域调用。QuoteAPI类封装数据库操作,与Web层解耦。- 参数验证和错误处理确保API稳定性。
- 使用
RANDOM()函数实现随机台词获取。
API使用示例
启动服务后,可以通过以下方式调用API:
搜索台词:
GET /api/search?keyword=offer&movie=Godfather&limit=5响应:
{ "status": "success", "count": 1, "results": [ { "id": 1, "movie_title": "The Godfather", "year": 1972, "character": "Don Corleone", "dialogue": "I'm gonna make him an offer he can't refuse.", "timestamp": "00:45:30,000" } ] }随机台词:
GET /api/random响应:
{ "status": "success", "result": { "id": 2, "movie_title": "Casablanca", "year": 1942, "character": "Rick Blaine", "dialogue": "Here's looking at you, kid.", "timestamp": "01:30:45,000" } }电影列表:
GET /api/movies响应:
{ "status": "success", "movies": [ {"id": 1, "title": "The Godfather", "year": 1972, "director": "Francis Ford Coppola"}, {"id": 2, "title": "Casablanca", "year": 1942, "director": "Michael Curtiz"} ] }
用户界面设计
简单命令行界面
对于快速测试和原型开发,命令行界面(CLI)是最简单的方式。
def cli_interface():
"""命令行界面"""
db = MovieQuoteDB()
while True:
print("\n" + "="*50)
print("老片经典台词查询系统")
print("="*50)
print("1. 搜索台词")
print("2. 随机台词")
print("3. 电影列表")
print("4. 退出")
choice = input("\n请选择操作 (1-4): ").strip()
if choice == '1':
keyword = input("输入搜索关键词: ").strip()
if keyword:
results = db.search_quotes(keyword)
if results:
print(f"\n找到 {len(results)} 条结果:")
for i, result in enumerate(results, 1):
print(f"\n{i}. 电影: {result['movie_title']} ({result['year']})")
print(f" 角色: {result['character']}")
print(f" 台词: {result['dialogue']}")
if result['timestamp']:
print(f" 时间: {result['timestamp']}")
else:
print("未找到匹配的台词。")
elif choice == '2':
with closing(sqlite3.connect(db.db_path)) as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT d.id, m.title, m.year, d.character_name, d.dialogue_text, d.start_time
FROM dialogues d
JOIN movies m ON d.movie_id = m.id
ORDER BY RANDOM()
LIMIT 1
''')
row = cursor.fetchone()
if row:
print("\n随机台词:")
print(f"电影: {row[1]} ({row[2]})")
print(f"角色: {row[3]}")
print(f"台词: {row[4]}")
if row[5]:
print(f"时间: {row[5]}")
elif choice == '3':
with closing(sqlite3.connect(db.db_path)) as conn:
cursor = conn.cursor()
cursor.execute('SELECT title, year, director FROM movies ORDER BY year DESC')
movies = cursor.fetchall()
if movies:
print("\n电影列表:")
for i, (title, year, director) in enumerate(movies, 1):
print(f"{i}. {title} ({year}) - {director}")
else:
print("暂无电影记录。")
elif choice == '4':
print("感谢使用,再见!")
break
else:
print("无效选择,请重新输入。")
if __name__ == "__main__":
cli_interface()
Web前端界面(HTML + JavaScript)
对于更友好的用户体验,可以构建一个简单的Web界面。以下是一个完整的HTML文件示例,包含搜索、展示和随机台词功能。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>老片经典台词查询</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 30px;
}
.search-box {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
input, select, button {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
input[type="text"] {
flex: 1;
min-width: 200px;
}
button {
background-color: #3498db;
color: white;
border: none;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
button.secondary {
background-color: #95a5a6;
}
button.secondary:hover {
background-color: #7f8c8d;
}
.results {
margin-top: 20px;
}
.quote-item {
background: #f8f9fa;
padding: 15px;
margin-bottom: 15px;
border-radius: 5px;
border-left: 4px solid #3498db;
}
.quote-item .movie {
font-weight: bold;
color: #2c3e50;
margin-bottom: 5px;
}
.quote-item .character {
color: #7f8c8d;
font-style: italic;
margin-bottom: 8px;
}
.quote-item .dialogue {
font-size: 1.1em;
line-height: 1.5;
}
.quote-item .timestamp {
color: #95a5a6;
font-size: 0.9em;
margin-top: 5px;
}
.loading {
text-align: center;
color: #7f8c8d;
padding: 20px;
}
.error {
background: #e74c3c;
color: white;
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
}
.stats {
background: #e8f4f8;
padding: 10px;
border-radius: 5px;
margin-bottom: 15px;
text-align: center;
}
.filters {
display: flex;
gap: 10px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.filters input {
flex: 1;
min-width: 150px;
}
.random-section {
background: #f8f9fa;
padding: 20px;
border-radius: 5px;
margin-top: 20px;
text-align: center;
}
.random-section h3 {
margin-top: 0;
color: #2c3e50;
}
.random-quote {
font-size: 1.2em;
margin: 15px 0;
padding: 15px;
background: white;
border-radius: 5px;
border-left: 4px solid #e74c3c;
}
</style>
</head>
<body>
<div class="container">
<h1>🎬 老片经典台词查询</h1>
<!-- 搜索区域 -->
<div class="search-section">
<div class="search-box">
<input type="text" id="keyword" placeholder="输入关键词(如:offer, love, Paris)" autofocus>
<button onclick="searchQuotes()">搜索台词</button>
<button class="secondary" onclick="getRandomQuote()">随机台词</button>
</div>
<div class="filters">
<input type="text" id="movieFilter" placeholder="电影标题过滤(可选)">
<input type="text" id="characterFilter" placeholder="角色名过滤(可选)">
<select id="limitSelect">
<option value="10">10条</option>
<option value="20" selected>20条</option>
<option value="50">50条</option>
<option value="100">100条</option>
</select>
</div>
</div>
<!-- 统计信息 -->
<div id="stats" class="stats" style="display: none;"></div>
<!-- 错误信息 -->
<div id="error" class="error" style="display: none;"></div>
<!-- 结果区域 -->
<div id="results" class="results"></div>
<!-- 随机台词区域 -->
<div id="randomSection" class="random-section" style="display: none;">
<h3>🎲 随机经典台词</h3>
<div id="randomQuote" class="random-quote"></div>
<button onclick="getRandomQuote()">再换一条</button>
</div>
</div>
<script>
const API_BASE = 'http://localhost:5000/api';
// 显示错误信息
function showError(message) {
const errorDiv = document.getElementById('error');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
setTimeout(() => errorDiv.style.display = 'none', 5000);
}
// 显示统计信息
function showStats(count) {
const statsDiv = document.getElementById('stats');
statsDiv.textContent = `找到 ${count} 条台词`;
statsDiv.style.display = 'block';
}
// 渲染搜索结果
function renderResults(results) {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '';
if (results.length === 0) {
resultsDiv.innerHTML = '<div class="loading">未找到匹配的台词</div>';
return;
}
results.forEach(quote => {
const item = document.createElement('div');
item.className = 'quote-item';
item.innerHTML = `
<div class="movie">${quote.movie_title} (${quote.year})</div>
<div class="character">${quote.character || '未知角色'}</div>
<div class="dialogue">${quote.dialogue}</div>
${quote.timestamp ? `<div class="timestamp">⏱️ ${quote.timestamp}</div>` : ''}
`;
resultsDiv.appendChild(item);
});
}
// 渲染随机台词
function renderRandomQuote(quote) {
const randomSection = document.getElementById('randomSection');
const randomQuoteDiv = document.getElementById('randomQuote');
randomQuoteDiv.innerHTML = `
<div class="movie">${quote.movie_title} (${quote.year})</div>
<div class="character">${quote.character || '未知角色'}</div>
<div class="dialogue">${quote.dialogue}</div>
${quote.timestamp ? `<div class="timestamp">⏱️ ${quote.timestamp}</div>` : ''}
`;
randomSection.style.display = 'block';
}
// 搜索台词
async function searchQuotes() {
const keyword = document.getElementById('keyword').value.trim();
const movie = document.getElementById('movieFilter').value.trim();
const character = document.getElementById('characterFilter').value.trim();
const limit = document.getElementById('limitSelect').value;
if (!keyword) {
showError('请输入搜索关键词');
return;
}
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '<div class="loading">搜索中...</div>';
try {
// 构建URL参数
const params = new URLSearchParams();
params.append('keyword', keyword);
if (movie) params.append('movie', movie);
if (character) params.append('character', character);
params.append('limit', limit);
const response = await fetch(`${API_BASE}/search?${params}`);
const data = await response.json();
if (data.status === 'success') {
renderResults(data.results);
showStats(data.count);
} else {
showError(data.message || '搜索失败');
}
} catch (error) {
showError('网络错误: ' + error.message);
}
}
// 获取随机台词
async function getRandomQuote() {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '';
document.getElementById('stats').style.display = 'none';
try {
const response = await fetch(`${API_BASE}/random`);
const data = await response.json();
if (data.status === 'success') {
renderRandomQuote(data.result);
} else {
showError(data.message || '获取失败');
}
} catch (error) {
showError('网络错误: ' + error.message);
}
}
// 回车键支持
document.getElementById('keyword').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
searchQuotes();
}
});
// 页面加载时获取随机台词
window.addEventListener('load', function() {
// 可选:自动加载随机台词
// getRandomQuote();
});
</script>
</body>
</html>
使用说明:
- 将上述HTML代码保存为
index.html文件。 - 确保Flask API服务正在运行(
python app.py)。 - 在浏览器中打开
index.html文件。 - 输入关键词进行搜索,或点击”随机台词”按钮。
高级功能扩展
1. 情感分析
使用自然语言处理技术为台词添加情感标签,如积极、消极、中性、幽默等。
from textblob import TextBlob
import nltk
# 下载必要的NLTK数据(首次运行需要)
# nltk.download('vader_lexicon')
# nltk.download('punkt')
def analyze_sentiment(text):
"""
使用TextBlob进行情感分析
:param text: 台词文本
:return: 情感标签
"""
analysis = TextBlob(text)
polarity = analysis.sentiment.polarity
if polarity > 0.3:
return "积极"
elif polarity < -0.3:
return "消极"
else:
return "中性"
# 示例
print(analyze_sentiment("I love this beautiful day!")) # 积极
print(analyze_sentiment("I hate this terrible situation.")) # 消极
print(analyze_sentiment("The weather is okay.")) # 中性
2. 台词关联分析
分析台词之间的关联,找出经常一起出现的短语或主题。
from collections import defaultdict
import re
def find_common_phrases(dialogues, min_length=2, min_count=3):
"""
找出常见的短语组合
:param dialogues: 台词列表
:param min_length: 最小单词数
:param min_count: 最小出现次数
:return: 常见短语字典
"""
phrase_count = defaultdict(int)
for dialogue in dialogues:
# 移除标点,转小写
text = re.sub(r'[^\w\s]', '', dialogue.lower())
words = text.split()
# 生成2-3个词的短语
for i in range(len(words) - min_length + 1):
for j in range(min_length, min_length + 2):
if i + j <= len(words):
phrase = ' '.join(words[i:i+j])
phrase_count[phrase] += 1
# 过滤高频短语
common_phrases = {phrase: count for phrase, count in phrase_count.items()
if count >= min_count and len(phrase.split()) >= min_length}
return dict(sorted(common_phrases.items(), key=lambda x: x[1], reverse=True))
# 示例
dialogues = [
"I'm gonna make him an offer he can't refuse.",
"It's not personal, it's strictly business.",
"Here's looking at you, kid.",
"We'll always have Paris.",
"I'm gonna make him an offer."
]
print(find_common_phrases(dialogues))
3. 语音合成(TTS)
使用文本转语音技术让台词”说出来”。
import pyttsx3
def speak_quote(text, rate=150):
"""
使用pyttsx3朗读台词
:param text: 台词文本
:param rate: 语速
"""
engine = pyttsx3.init()
engine.setProperty('rate', rate)
engine.say(text)
engine.runAndWait()
# 示例
if __name__ == "__main__":
speak_quote("I'm gonna make him an offer he can't refuse.")
部署与维护
部署选项
- 本地部署:适合个人使用,简单快捷。
- 云服务器:使用AWS、阿里云等,适合公开服务。
- Serverless:使用AWS Lambda或阿里云函数计算,按需付费。
数据备份与恢复
定期备份数据库至关重要:
import shutil
from datetime import datetime
def backup_database(db_path, backup_dir='backups'):
"""备份数据库"""
import os
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_file = os.path.join(backup_dir, f'backup_{timestamp}.db')
shutil.copy2(db_path, backup_file)
print(f"备份成功: {backup_file}")
# 定时任务示例(Linux crontab)
# 0 2 * * * /usr/bin/python3 /path/to/backup_script.py
性能监控
使用简单的日志记录监控系统性能:
import logging
import time
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('query_log.txt'),
logging.StreamHandler()
]
)
def log_query_performance(func):
"""装饰器:记录查询性能"""
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
duration = end_time - start_time
logging.info(f"查询耗时: {duration:.4f}秒 - 参数: {args}, {kwargs}")
return result
return wrapper
# 应用装饰器
@log_query_performance
def search_quotes(self, keyword, movie=None, character=None, limit=20):
# 原有搜索逻辑
pass
总结
本文详细介绍了如何构建一个老片经典台词查询系统,从数据收集、处理、存储到检索和展示的完整流程。我们提供了丰富的Python代码示例,包括:
- 数据收集:从SRT文件解析台词,文本标准化。
- 数据库设计:SQLite表结构和索引优化。
- 检索引擎:基础搜索、全文索引(FTS5)和模糊匹配算法。
- Web API:使用Flask构建RESTful接口。
- 用户界面:命令行和Web前端实现。
- 高级功能:情感分析、关联分析和语音合成。
- 部署维护:备份、监控和性能优化。
这个系统可以根据需求灵活扩展,例如添加更多电影、集成机器学习模型进行智能推荐,或开发移动应用。通过本文的指导,读者可以快速搭建一个功能完善的台词查询工具,重温经典电影的魅力。
下一步建议:
- 收集更多电影数据,丰富台词库。
- 实现用户认证和收藏功能。
- 添加相似台词推荐系统。
- 开发浏览器插件,实现网页上的即时台词查询。
希望本文能帮助您成功构建自己的老片经典台词查询系统!
