180 lines
5.7 KiB
Python
180 lines
5.7 KiB
Python
|
|
"""从线上 ECDICT 词库下载并导入 MySQL。
|
|||
|
|
|
|||
|
|
数据源: https://github.com/skywind3000/ECDICT (开源英汉词典,约 340 万词条)
|
|||
|
|
|
|||
|
|
用法(在 backend 目录下):
|
|||
|
|
python -m scripts.import_dictionary_online
|
|||
|
|
python -m scripts.import_dictionary_online --preset full
|
|||
|
|
python -m scripts.import_dictionary_online --preset standard --skip-download
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import sqlite3
|
|||
|
|
import sys
|
|||
|
|
import zipfile
|
|||
|
|
from pathlib import Path
|
|||
|
|
from typing import Optional
|
|||
|
|
from urllib.request import urlretrieve
|
|||
|
|
|
|||
|
|
BACKEND_DIR = Path(__file__).resolve().parent.parent
|
|||
|
|
CACHE_DIR = BACKEND_DIR / "data" / "cache"
|
|||
|
|
ECDICT_ZIP_URL = (
|
|||
|
|
"https://github.com/skywind3000/ECDICT/releases/download/1.0.28/ecdict-sqlite-28.zip"
|
|||
|
|
)
|
|||
|
|
ECDICT_ZIP_PATH = CACHE_DIR / "ecdict-sqlite-28.zip"
|
|||
|
|
ECDICT_DB_PATH = CACHE_DIR / "stardict.db"
|
|||
|
|
|
|||
|
|
sys.path.insert(0, str(BACKEND_DIR))
|
|||
|
|
|
|||
|
|
from database import SessionLocal # noqa: E402
|
|||
|
|
from scripts.dictionary_importer import upsert_entries # noqa: E402
|
|||
|
|
|
|||
|
|
PRESET_SQL = {
|
|||
|
|
# 常用词:Collins / 牛津核心词,或词频排名前 5 万
|
|||
|
|
"standard": """
|
|||
|
|
translation IS NOT NULL AND translation != ''
|
|||
|
|
AND (
|
|||
|
|
collins >= 1 OR oxford = 1
|
|||
|
|
OR (frq IS NOT NULL AND frq <= 50000)
|
|||
|
|
)
|
|||
|
|
""",
|
|||
|
|
# 核心词:Collins 星级或牛津 3000
|
|||
|
|
"core": """
|
|||
|
|
translation IS NOT NULL AND translation != ''
|
|||
|
|
AND (collins >= 1 OR oxford = 1)
|
|||
|
|
""",
|
|||
|
|
# 全量(约 338 万,耗时较长)
|
|||
|
|
"full": """
|
|||
|
|
translation IS NOT NULL AND translation != ''
|
|||
|
|
""",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def download(url: str, dest: Path) -> None:
|
|||
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|||
|
|
print(f"正在下载 ECDICT SQLite(约 207MB)…")
|
|||
|
|
print(f" {url}")
|
|||
|
|
|
|||
|
|
def progress(block_num: int, block_size: int, total_size: int) -> None:
|
|||
|
|
if total_size <= 0:
|
|||
|
|
return
|
|||
|
|
done = block_num * block_size
|
|||
|
|
pct = min(100, done * 100 // total_size)
|
|||
|
|
mb = done / 1024 / 1024
|
|||
|
|
total_mb = total_size / 1024 / 1024
|
|||
|
|
print(f"\r 进度: {pct:3d}% ({mb:.1f}/{total_mb:.1f} MB)", end="", flush=True)
|
|||
|
|
|
|||
|
|
urlretrieve(url, dest, reporthook=progress)
|
|||
|
|
print()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def ensure_ecdict_db(skip_download: bool) -> Path:
|
|||
|
|
if ECDICT_DB_PATH.exists():
|
|||
|
|
return ECDICT_DB_PATH
|
|||
|
|
|
|||
|
|
if not ECDICT_ZIP_PATH.exists():
|
|||
|
|
if skip_download:
|
|||
|
|
print(f"错误: 未找到本地词库,请先下载或去掉 --skip-download", file=sys.stderr)
|
|||
|
|
sys.exit(1)
|
|||
|
|
download(ECDICT_ZIP_URL, ECDICT_ZIP_PATH)
|
|||
|
|
|
|||
|
|
print(f"正在解压 {ECDICT_ZIP_PATH.name} …")
|
|||
|
|
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|||
|
|
with zipfile.ZipFile(ECDICT_ZIP_PATH, "r") as zf:
|
|||
|
|
zf.extract("stardict.db", CACHE_DIR)
|
|||
|
|
print(f"词库就绪: {ECDICT_DB_PATH}")
|
|||
|
|
return ECDICT_DB_PATH
|
|||
|
|
|
|||
|
|
|
|||
|
|
def count_entries(db_path: Path, where: str) -> int:
|
|||
|
|
conn = sqlite3.connect(db_path)
|
|||
|
|
try:
|
|||
|
|
cur = conn.execute(f"SELECT COUNT(*) FROM stardict WHERE {where}")
|
|||
|
|
return int(cur.fetchone()[0])
|
|||
|
|
finally:
|
|||
|
|
conn.close()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def iter_ecdict_rows(db_path: Path, where: str, limit: Optional[int]):
|
|||
|
|
conn = sqlite3.connect(db_path)
|
|||
|
|
conn.row_factory = sqlite3.Row
|
|||
|
|
try:
|
|||
|
|
sql = f"""
|
|||
|
|
SELECT word, phonetic, translation
|
|||
|
|
FROM stardict
|
|||
|
|
WHERE {where}
|
|||
|
|
ORDER BY COALESCE(frq, 9999999), word
|
|||
|
|
"""
|
|||
|
|
if limit:
|
|||
|
|
sql += f" LIMIT {int(limit)}"
|
|||
|
|
cur = conn.execute(sql)
|
|||
|
|
while True:
|
|||
|
|
rows = cur.fetchmany(5000)
|
|||
|
|
if not rows:
|
|||
|
|
break
|
|||
|
|
for row in rows:
|
|||
|
|
yield {
|
|||
|
|
"word": row["word"],
|
|||
|
|
"phonetic": row["phonetic"],
|
|||
|
|
"translation": row["translation"],
|
|||
|
|
}
|
|||
|
|
finally:
|
|||
|
|
conn.close()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def import_from_ecdict(
|
|||
|
|
db_path: Path,
|
|||
|
|
preset: str,
|
|||
|
|
limit: Optional[int],
|
|||
|
|
batch_size: int,
|
|||
|
|
) -> int:
|
|||
|
|
where = PRESET_SQL[preset]
|
|||
|
|
total_expected = count_entries(db_path, where)
|
|||
|
|
if limit:
|
|||
|
|
total_expected = min(total_expected, limit)
|
|||
|
|
print(f"预设: {preset},预计导入 {total_expected:,} 条")
|
|||
|
|
|
|||
|
|
session = SessionLocal()
|
|||
|
|
imported = 0
|
|||
|
|
buffer: list[dict] = []
|
|||
|
|
try:
|
|||
|
|
for row in iter_ecdict_rows(db_path, where, limit):
|
|||
|
|
buffer.append(row)
|
|||
|
|
if len(buffer) >= batch_size:
|
|||
|
|
imported += upsert_entries(session, buffer, source="ecdict", batch_size=batch_size)
|
|||
|
|
buffer.clear()
|
|||
|
|
print(f"\r 已导入 {imported:,} / {total_expected:,}", end="", flush=True)
|
|||
|
|
if buffer:
|
|||
|
|
imported += upsert_entries(session, buffer, source="ecdict", batch_size=batch_size)
|
|||
|
|
print()
|
|||
|
|
except Exception:
|
|||
|
|
session.rollback()
|
|||
|
|
raise
|
|||
|
|
finally:
|
|||
|
|
session.close()
|
|||
|
|
return imported
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> None:
|
|||
|
|
parser = argparse.ArgumentParser(description="从线上 ECDICT 下载并导入离线翻译词典")
|
|||
|
|
parser.add_argument(
|
|||
|
|
"--preset",
|
|||
|
|
choices=list(PRESET_SQL.keys()),
|
|||
|
|
default="standard",
|
|||
|
|
help="standard=常用约 83 万 | core=核心约 1.4 万 | full=全量约 338 万(默认 standard)",
|
|||
|
|
)
|
|||
|
|
parser.add_argument("--limit", type=int, default=None, help="最多导入条数(调试用)")
|
|||
|
|
parser.add_argument("--batch-size", type=int, default=2000, help="MySQL 批量写入大小")
|
|||
|
|
parser.add_argument("--skip-download", action="store_true", help="跳过下载,使用本地 cache")
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
|
|||
|
|
db_path = ensure_ecdict_db(args.skip_download)
|
|||
|
|
imported = import_from_ecdict(db_path, args.preset, args.limit, args.batch_size)
|
|||
|
|
print(f"导入完成: {imported:,} 条(source=ecdict, preset={args.preset})")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|