Python实现的在线词典学习工具
源码最初来自网络,根据实际情况进行了修改。
主要功能:
单词查询
通过Bing词典在线获取单词释义(正则提取网页meta描述),支持回车键快速查询
内置网络请求重试和异常处理机制
在线网页
生词本管理
SQLite数据库(wordlist.db)存储单词(含释义、添加时间、难度等级)
支持:
批量删除单词
多选导出
按时间倒序展示
数据刷新
文档导出
全量/选择性导出为TXT文档
自动生成带格式的文档:时间戳标注
运行界面
此程序用到了Python中的一些库和模块,下面是简要解释:
requests 是一个用于发送HTTP请求的第三方库。它让你可以非常轻松地与Web服务交互。这个库不是Python标准库的一部分,因此需要安装。
re 是Python标准库中的正则表达式模块,用于字符串匹配和查找。不需要额外安装。
ttkbootstrap 是一个为Tkinter提供Bootstrap样式的第三方库,使得创建具有现代外观的GUI应用程序变得更加容易。这不是Python标准库的一部分,因此需要安装。
tkinter是Python的标准GUI库,所以不需要额外安装。
sqlite3 是Python标准库中的一个模块,允许你直接使用SQLite数据库。不需要额外安装。
os 是Python标准库中的一个模块,提供了与操作系统交互的功能。不需要额外安装。
datetime 是Python标准库中的一个模块,用于处理日期和时间。这里仅导入了datetime类。不需要额外安装。
webbrowser 是Python标准库中的一个模块,用于提供对系统浏览器的控制。不需要额外安装。
总结起来,你需要安装的是requests和ttkbootstrap这两个库。你可以通过以下命令来安装它们(假设你还没有用pip安装)。
源码如下:
import requests
import re
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap.dialogs import Messagebox, Querybox
from tkinter import filedialog
import sqlite3
import os
from datetime import datetime
import webbrowser
class DictionaryApp:
def __init__(self):
# 初始化配置
self.url = "https://cn.bing.com/dict/search"
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
# 数据库连接
self.db_file = 'wordlist.db'
self.conn = sqlite3.connect(self.db_file)
self.cursor = self.conn.cursor()
self._init_db()
# 临时存储列表
self.temp_words = []
# 创建主界面
self.root = ttk.Window(title="在线词典工具", themename="litera")
self.root.geometry("800x600")
self._setup_ui()
def _init_db(self):
"""初始化数据库表结构"""
try:
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS words (
id INTEGER PRIMARY KEY AUTOINCREMENT,
word TEXT UNIQUE,
meaning TEXT,
add_time TEXT,
difficulty INTEGER DEFAULT 1
)
""")
self.conn.commit()
except sqlite3.Error as e:
Messagebox.show_error(f"数据库初始化失败: {str(e)}")
def _setup_ui(self):
"""设置用户界面"""
# 顶部标题
ttk.Label(
self.root,
text="词典工具",
font=("微软雅黑", 16, "bold"),
bootstyle="primary"
).pack(pady=10)
# 搜索区域
search_frame = ttk.Frame(self.root)
search_frame.pack(fill=X, padx=10, pady=5)
self.search_entry = ttk.Entry(
search_frame,
width=40,
font=("微软雅黑", 12)
)
self.search_entry.pack(side=LEFT, padx=5)
self.search_entry.bind("<Return>", lambda e: self.search_word())
ttk.Button(
search_frame,
text="在线查询",
command=self.search_word,
bootstyle="primary"
).pack(side=LEFT, padx=5)
ttk.Button(
search_frame,
text="在线网页",
command=self.search_online,
bootstyle="info"
).pack(side=LEFT, padx=5)
# 结果显示区域
result_frame = ttk.Frame(self.root)
result_frame.pack(fill=BOTH, expand=True, padx=10, pady=5)
self.result_text = ttk.ScrolledText(
result_frame,
font=("微软雅黑", 11),
wrap=WORD,
height=15
)
self.result_text.pack(fill=BOTH, expand=True)
self.result_text.config(state=DISABLED)
# 操作按钮区域
btn_frame = ttk.Frame(self.root)
btn_frame.pack(fill=X, padx=10, pady=10)
ttk.Button(
btn_frame,
text="添加到生词本",
command=self.add_to_vocabulary,
bootstyle="success"
).pack(side=LEFT, padx=5)
ttk.Button(
btn_frame,
text="清空结果",
command=self.clear_results,
bootstyle="warning"
).pack(side=LEFT, padx=5)
ttk.Button(
btn_frame,
text="管理生词本",
command=self.manage_vocabulary,
bootstyle="secondary"
).pack(side=LEFT, padx=5)
ttk.Button(
btn_frame,
text="生词本导出为TXT",
command=self.export_to_txt,
bootstyle="primary-outline"
).pack(side=RIGHT, padx=5)
# 状态栏
self.status_var = ttk.StringVar()
self.status_var.set("就绪")
ttk.Label(
self.root,
textvariable=self.status_var,
relief=SUNKEN,
anchor=W
).pack(fill=X, side=BOTTOM, ipady=2)
# 窗口关闭事件
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
def search_word(self):
"""查询单词:Bing词典API在线获取单词释义(正则提取网页meta描述)"""
word = self.search_entry.get().strip()
if not word:
Messagebox.show_warning("请输入要查询的单词", parent=self.root)
return
self.status_var.set(f"正在查询: {word}...")
self.root.update()
try:
result = self._search_word(word)
self._display_result(word, result)
self.status_var.set(f"查询完成: {word}")
except requests.ConnectionError:
Messagebox.show_error("网络连接失败,请检查您的网络连接", parent=self.root)
self.status_var.set("查询失败: 网络连接错误")
except requests.Timeout:
Messagebox.show_error("查询超时,请稍后再试", parent=self.root)
self.status_var.set("查询失败: 连接超时")
except requests.RequestException as e:
Messagebox.show_error(f"请求错误: {str(e)}", parent=self.root)
self.status_var.set("查询失败: 请求错误")
except Exception as e:
Messagebox.show_error(f"查询失败: {str(e)}", parent=self.root)
self.status_var.set("查询失败: 未知错误")
def _search_word(self, word):
"""实际执行查询"""
params = {"q": word}
try:
resp = self.session.get(self.url, params=params, timeout=10)
resp.raise_for_status()
# 使用正则提取释义
pattern = re.compile(
r'<meta name="description" content=".*的释义,(?P<meaning>.*?)" />'
)
match = pattern.search(resp.text)
if match:
meaning = match.group("meaning")
return meaning
return None
except requests.exceptions.Timeout:
raise requests.Timeout("查询超时,服务器响应时间过长")
except requests.exceptions.ConnectionError:
raise requests.ConnectionError("网络连接失败,请检查网络设置")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
return None
raise requests.RequestException(f"HTTP错误: {e.response.status_code}")
except requests.exceptions.RequestException as e:
raise requests.RequestException(f"请求错误: {str(e)}")
def _display_result(self, word, meaning):
"""显示查询结果"""
self.result_text.config(state=NORMAL)
self.result_text.delete(1.0, END)
if meaning:
# 添加单词
self.result_text.insert(END, f"单词: ", "bold")
self.result_text.insert(END, f"{word}\n", "word")
# 添加释义
self.result_text.insert(END, "\n释义:\n", "bold")
meanings = meaning.split(',')
for i, m in enumerate(meanings, 1):
self.result_text.insert(END, f"{i}. {m}\n")
# 临时存储
self.temp_words = [(word, meaning)]
else:
self.result_text.insert(END, f"未找到单词 '{word}' 的释义\n", "error")
self.result_text.config(state=DISABLED)
def add_to_vocabulary(self):
"""添加到生词本"""
if not self.temp_words:
Messagebox.show_warning("没有可添加的单词", parent=self.root)
return
success = 0
for word, meaning in self.temp_words:
try:
self.cursor.execute(
"INSERT OR IGNORE INTO words (word, meaning, add_time) VALUES (?, ?, ?)",
(word, meaning, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
)
success += 1
except sqlite3.Error as e:
self.status_var.set(f"添加单词 '{word}' 失败: {str(e)}")
continue
self.conn.commit()
Messagebox.show_info(
f"成功添加 {success}/{len(self.temp_words)} 个单词到生词本",
parent=self.root
)
self.temp_words = []
def clear_results(self):
"""清空结果"""
self.result_text.config(state=NORMAL)
self.result_text.delete(1.0, END)
self.result_text.config(state=DISABLED)
self.temp_words = []
self.status_var.set("已清空结果")
def manage_vocabulary(self):
"""管理生词本"""
manage_window = ttk.Toplevel(title="生词本管理")
manage_window.geometry("900x600")
# 创建树形表格
columns = ("word", "meaning", "add_time")
tree = ttk.Treeview(
manage_window,
columns=columns,
show="headings",
selectmode="extended",
bootstyle="primary"
)
# 设置列
tree.heading("word", text="单词", anchor=W)
tree.heading("meaning", text="释义", anchor=W)
tree.heading("add_time", text="添加时间", anchor=W)
tree.column("word", width=150, minwidth=100)
tree.column("meaning", width=500, minwidth=300)
tree.column("add_time", width=150, minwidth=100)
# 添加滚动条
scrollbar = ttk.Scrollbar(
manage_window,
orient=VERTICAL,
command=tree.yview
)
tree.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=RIGHT, fill=Y)
tree.pack(fill=BOTH, expand=True, padx=5, pady=5)
# 加载数据
self._load_vocabulary_data(tree)
# 操作按钮区域
btn_frame = ttk.Frame(manage_window)
btn_frame.pack(fill=X, padx=5, pady=5)
ttk.Button(
btn_frame,
text="删除选中",
command=lambda: self._delete_selected_words(tree),
bootstyle="danger"
).pack(side=LEFT, padx=5)
ttk.Button(
btn_frame,
text="导出选中",
command=lambda: self._export_selected_words(tree),
bootstyle="success"
).pack(side=LEFT, padx=5)
ttk.Button(
btn_frame,
text="刷新列表",
command=lambda: self._load_vocabulary_data(tree),
bootstyle="info"
).pack(side=RIGHT, padx=5)
def _load_vocabulary_data(self, tree):
"""加载生词本数据到表格"""
try:
# 清空现有数据
for item in tree.get_children():
tree.delete(item)
# 获取数据并显示
rows = self.cursor.execute("""
SELECT word, meaning, add_time FROM words
ORDER BY add_time DESC
""").fetchall()
for row in rows:
tree.insert("", END, values=row)
except sqlite3.Error as e:
Messagebox.show_error(f"加载数据失败: {str(e)}", parent=tree.winfo_toplevel())
def _delete_selected_words(self, tree):
"""删除选中的单词"""
selected_items = tree.selection()
if not selected_items:
Messagebox.show_warning("请先选择要删除的单词", parent=tree.winfo_toplevel())
return
if not Messagebox.yesno(
"确认删除",
f"确定要删除这 {len(selected_items)} 个单词吗?",
parent=tree.winfo_toplevel()
):
return
deleted = 0
try:
for item in selected_items:
values = tree.item(item)['values']
if values: # 确保有值
word = values[0] # 获取单词
self.cursor.execute("DELETE FROM words WHERE word=?", (word,))
deleted += 1
self.conn.commit()
Messagebox.show_info(
f"成功删除 {deleted} 个单词",
parent=tree.winfo_toplevel()
)
# 刷新显示
self._load_vocabulary_data(tree)
except sqlite3.Error as e:
Messagebox.show_error(f"删除失败: {str(e)}", parent=tree.winfo_toplevel())
def _export_selected_words(self, tree):
"""导出选中的单词"""
selected_items = tree.selection()
if not selected_items:
Messagebox.show_warning("请先选择要导出的单词", parent=tree.winfo_toplevel())
return
words = []
for item in selected_items:
values = tree.item(item)['values']
if values: # 确保有值
words.append({
"word": values[0],
"meaning": values[1],
"time": values[2]
})
if not words:
Messagebox.show_warning("没有可导出的单词", parent=tree.winfo_toplevel())
return
# 调用导出功能
default_filename = f"选中单词_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
# 让用户选择保存位置
from tkinter import filedialog
filepath = filedialog.asksaveasfilename(
initialfile=default_filename,
defaultextension=".txt",
filetypes=[("文本文档", "*.txt")],
parent=tree.winfo_toplevel()
)
if not filepath:
return
try:
with open(filepath, 'w', encoding='utf-8') as f:
# 写入标题
f.write("选中的单词\n")
f.write("="*50 + "\n\n")
# 写入统计信息
f.write(f"共 {len(words)} 个单词 | 导出时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
# 写入单词内容
for item in words:
f.write(f"■ {item['word']} ({item['time']})\n")
f.write(f" {item['meaning']}\n")
f.write("-"*50 + "\n")
Messagebox.show_info(
f"成功导出 {len(words)} 个单词到:\n{filepath}",
parent=tree.winfo_toplevel()
)
# 询问是否打开文件
if Messagebox.yesno(
"确认打开",
"导出成功,是否现在打开文件?",
parent=tree.winfo_toplevel()
):
webbrowser.open(filepath)
except IOError as e:
Messagebox.show_error(f"写入文件失败: {str(e)}", parent=tree.winfo_toplevel())
except Exception as e:
Messagebox.show_error(f"导出失败: {str(e)}", parent=tree.winfo_toplevel())
def export_to_txt(self):
"""导出全部单词到TXT"""
words = []
try:
rows = self.cursor.execute("""
SELECT word, meaning, add_time FROM words
ORDER BY word COLLATE NOCASE
""").fetchall()
for row in rows:
words.append({
"word": row[0],
"meaning": row[1],
"time": row[2]
})
except sqlite3.Error as e:
Messagebox.show_error(f"加载数据失败: {str(e)}", parent=self.root)
return
if not words:
Messagebox.show_warning("生词本为空,没有可导出的单词", parent=self.root)
return
self._export_words_to_file(words)
def _export_words_to_file(self, words):
"""实际执行导出到TXT文件"""
default_filename = f"单词表_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
# 让用户选择保存位置 - 修复Querybox错误
from tkinter import filedialog
filepath = filedialog.asksaveasfilename(
initialfile=default_filename,
defaultextension=".txt",
filetypes=[("文本文档", "*.txt")],
parent=self.root
)
if not filepath:
return
try:
with open(filepath, 'w', encoding='utf-8') as f:
# 写入标题
f.write("我的生词本\n")
f.write("="*50 + "\n\n")
# 写入统计信息
f.write(f"共 {len(words)} 个单词 | 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
# 写入单词内容
for item in words:
f.write(f"■ {item['word']} ({item['time']})\n")
f.write(f" {item['meaning']}\n")
f.write("-"*50 + "\n")
Messagebox.show_info(
f"成功导出 {len(words)} 个单词到:\n{filepath}",
parent=self.root
)
# 询问是否打开文件
if Messagebox.show_question(
"导出成功,是否现在打开文件?",
parent=self.root
) == "是":
webbrowser.open(filepath)
except IOError as e:
Messagebox.show_error(f"写入文件失败: {str(e)}", parent=self.root)
except Exception as e:
Messagebox.show_error(f"导出失败: {str(e)}", parent=self.root)
def search_online(self):
"""在浏览器中在线搜索:直连Bing词典(原始页面查看)"""
word = self.search_entry.get().strip()
if not word:
Messagebox.show_warning("请输入要查询的单词", parent=self.root)
return
url = f"https://cn.bing.com/dict/search?q={word}"
try:
webbrowser.open(url)
except Exception as e:
Messagebox.show_error(f"打开浏览器失败: {str(e)}", parent=self.root)
def on_close(self):
"""关闭窗口时的清理工作"""
try:
self.conn.commit()
self.conn.close()
self.session.close()
self.root.destroy()
except Exception as e:
print(f"关闭时出错: {str(e)}")
self.root.destroy()
if __name__ == '__main__':
app = DictionaryApp()
app.root.mainloop()