first
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
"""
|
||||
人教社电子教材交互式终端下载器 (cli.py)
|
||||
支持:
|
||||
1. 交互式菜单筛选(学段 -> 学科 -> 年级)
|
||||
2. 批量全选下载或勾选指定教材下载
|
||||
3. 命令行参数一键过滤下载 (例如: python cli.py --xd "小学(六三学制)" --xk "语文" --nj "一年级")
|
||||
4. 关键词全局模糊搜索下载
|
||||
5. 缓存清理与目录强制刷新
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
from typing import List, Dict
|
||||
from pep_core import PepCatalog, PepDownloader, XD_ORDER, XK_ORDER_PREFIX, NJ_ORDER, sort_xk_key, sort_nj_key
|
||||
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
|
||||
def print_banner():
|
||||
print("=" * 65)
|
||||
print(" 📚 人民教育出版社 (PEP) 电子教材终端下载工具")
|
||||
print("=" * 65)
|
||||
|
||||
|
||||
def select_from_list(prompt: str, options: List[str], allow_all: bool = True) -> str:
|
||||
"""辅助函数:提供多选编号菜单"""
|
||||
items = ["全部"] + options if allow_all and "全部" not in options else options
|
||||
print(f"\n👉 请选择【{prompt}】:")
|
||||
for i, item in enumerate(items, 1):
|
||||
print(f" [{i}] {item}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input(f"请输入序号 (1-{len(items)}, 默认 1): ").strip()
|
||||
if not choice:
|
||||
return items[0]
|
||||
idx = int(choice)
|
||||
if 1 <= idx <= len(items):
|
||||
return items[idx - 1]
|
||||
print(f"[-] 输入超出范围,请输入 1 到 {len(items)} 之间的数字。")
|
||||
except ValueError:
|
||||
print("[-] 请输入有效的数字序号。")
|
||||
|
||||
|
||||
def interactive_mode():
|
||||
"""交互式导航筛选模式"""
|
||||
print_banner()
|
||||
print("[*] 正在加载教材分类结构...")
|
||||
structure = PepCatalog.get_structure()
|
||||
all_xds = list(structure.keys())
|
||||
|
||||
print("\n请选择检索模式:")
|
||||
print(" [1] 分类层级筛选(学段 ➔ 学科 ➔ 年级)")
|
||||
print(" [2] 关键词全局搜索(如输入:'必修一'、'高一数学'、'生物')")
|
||||
|
||||
mode_choice = input("请输入模式编号 (1/2, 默认 1): ").strip()
|
||||
|
||||
matched_books = []
|
||||
|
||||
if mode_choice == "2":
|
||||
kw = input("\n🔍 请输入搜索关键词: ").strip()
|
||||
if not kw:
|
||||
print("[-] 关键词不能为空!")
|
||||
return
|
||||
matched_books = PepCatalog.filter_books(keyword=kw)
|
||||
else:
|
||||
# 1. 选择学段(已按规定排序)
|
||||
selected_xd = select_from_list("学段", all_xds, allow_all=True)
|
||||
|
||||
# 获取该学段下的学科和年级
|
||||
if selected_xd == "全部":
|
||||
s_set = set(s for x in structure.values() for s in x["subjects"])
|
||||
all_subjects = sorted(list(s_set), key=sort_xk_key)
|
||||
g_set = set(g for x in structure.values() for g in x["grades"])
|
||||
all_grades = sorted(list(g_set), key=sort_nj_key)
|
||||
else:
|
||||
all_subjects = structure[selected_xd]["subjects"]
|
||||
all_grades = structure[selected_xd]["grades"]
|
||||
|
||||
# 2. 选择学科(已按规定排序)
|
||||
selected_xk = select_from_list("学科", all_subjects, allow_all=True)
|
||||
|
||||
# 3. 选择年级(已按规定排序)
|
||||
selected_nj = select_from_list("年级", all_grades, allow_all=True)
|
||||
|
||||
matched_books = PepCatalog.filter_books(xd=selected_xd, xk=selected_xk, nj=selected_nj)
|
||||
|
||||
if not matched_books:
|
||||
print("\n[-] 未匹配到任何符合条件的教材!")
|
||||
return
|
||||
|
||||
print(f"\n✅ 共检索到 {len(matched_books)} 本教材:")
|
||||
print("-" * 65)
|
||||
for idx, b in enumerate(matched_books, 1):
|
||||
xd = b.get("xd", "")
|
||||
xk = b.get("xk", "")
|
||||
nj = b.get("nj", "")
|
||||
cc = b.get("cc", "")
|
||||
title = b.get("title", "")
|
||||
print(f" [{idx:2d}] [{xd}|{xk}|{nj}{cc}] 《{title}》 (ID: {b['id']})")
|
||||
print("-" * 65)
|
||||
|
||||
print("\n请选择下载范围:")
|
||||
print(" • 直接按 Enter 或输入 'all': 下载当前列表中的【全部】教材")
|
||||
print(" • 输入单个序号(如 '3'): 只下载第 3 本")
|
||||
print(" • 输入多个序号(如 '1,3,5' 或范围 '1-4'): 批量下载指定教材")
|
||||
print(" • 输入 'q': 退出")
|
||||
|
||||
select_str = input("\n请输入下载指令: ").strip().lower()
|
||||
if select_str == "q":
|
||||
print("[*] 已取消操作。")
|
||||
return
|
||||
|
||||
to_download = []
|
||||
if not select_str or select_str == "all":
|
||||
to_download = matched_books
|
||||
else:
|
||||
indices = set()
|
||||
for part in select_str.split(","):
|
||||
part = part.strip()
|
||||
if "-" in part:
|
||||
try:
|
||||
s, e = map(int, part.split("-"))
|
||||
for i in range(s, e + 1):
|
||||
indices.add(i)
|
||||
except ValueError:
|
||||
pass
|
||||
elif part.isdigit():
|
||||
indices.add(int(part))
|
||||
|
||||
for i in sorted(list(indices)):
|
||||
if 1 <= i <= len(matched_books):
|
||||
to_download.append(matched_books[i - 1])
|
||||
|
||||
if not to_download:
|
||||
print("[-] 未选择有效教材,退出。")
|
||||
return
|
||||
|
||||
out_dir = os.path.abspath("./downloads")
|
||||
print(f"\n🚀 即将开始下载 {len(to_download)} 本教材,保存目录: {out_dir}")
|
||||
downloader = PepDownloader(headless=True, output_dir=out_dir)
|
||||
|
||||
for idx, b in enumerate(to_download, 1):
|
||||
print(f"\n==================================================")
|
||||
print(f"[{idx}/{len(to_download)}] 开始下载: 《{b.get('title')}》")
|
||||
print(f"==================================================")
|
||||
downloader.download_book(book_id=b["id"], custom_title=b.get("title"))
|
||||
|
||||
print("\n🎉 全部选定任务执行完毕!")
|
||||
|
||||
|
||||
def cli_args_mode(args):
|
||||
"""命令行参数直接执行模式"""
|
||||
print_banner()
|
||||
matched = PepCatalog.filter_books(xd=args.xd, xk=args.xk, nj=args.nj, keyword=args.search)
|
||||
if not matched:
|
||||
print("[-] 未查找到符合条件的教材!")
|
||||
return
|
||||
|
||||
print(f"[+] 符合条件的教材共 {len(matched)} 本:")
|
||||
for idx, b in enumerate(matched, 1):
|
||||
print(f" [{idx}] 《{b.get('title')}》 (ID: {b['id']})")
|
||||
|
||||
if not args.yes:
|
||||
confirm = input(f"\n确认下载以上 {len(matched)} 本教材吗?(y/n, 默认 y): ").strip().lower()
|
||||
if confirm == "n":
|
||||
print("[*] 已取消。")
|
||||
return
|
||||
|
||||
out_dir = os.path.abspath(args.output)
|
||||
downloader = PepDownloader(headless=True, output_dir=out_dir)
|
||||
for idx, b in enumerate(matched, 1):
|
||||
print(f"\n[{idx}/{len(matched)}] 正在下载: 《{b.get('title')}》...")
|
||||
downloader.download_book(book_id=b["id"], custom_title=b.get("title"))
|
||||
|
||||
print(f"\n🎉 下载完成!文件已保存至: {out_dir}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="人民教育出版社电子教材 CLI 下载器")
|
||||
parser.add_argument("--xd", help="指定学段(如:小学(六三学制)、初中(六三学制)、高中等)")
|
||||
parser.add_argument("--xk", help="指定学科(如:语文、数学、英语、物理等)")
|
||||
parser.add_argument("--nj", help="指定年级(如:一年级、七年级、必修等)")
|
||||
parser.add_argument("--search", "-s", help="全局搜索关键词")
|
||||
parser.add_argument("--output", "-o", default="./downloads", help="PDF 文件保存目录 (默认: ./downloads)")
|
||||
parser.add_argument("--yes", "-y", action="store_true", help="免确认直接开始下载")
|
||||
parser.add_argument("--refresh", "-r", action="store_true", help="强制从官方服务器重新拉取并解密最新教材目录")
|
||||
parser.add_argument("--clear-cache", "-c", action="store_true", help="清理本地临时下载缓存 (temp_pages)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.clear_cache:
|
||||
print_banner()
|
||||
res = PepCatalog.clear_cache_files()
|
||||
print(f"[✔] {res['message']}")
|
||||
return
|
||||
|
||||
if args.refresh:
|
||||
print_banner()
|
||||
print("[*] 正在从人教社官方前端拉取并解密最新教材目录...")
|
||||
books = PepCatalog.fetch_and_decrypt_all(force_refresh=True)
|
||||
print(f"[+] 目录同步成功!共获取到 {len(books)} 本教材。")
|
||||
return
|
||||
|
||||
if args.xd or args.xk or args.nj or args.search:
|
||||
cli_args_mode(args)
|
||||
else:
|
||||
interactive_mode()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user