v1.1 加入了命令行下的批量下载功能
现在可以一次把人教社的身体掏空了,实测最大10线程没问题。
This commit is contained in:
@@ -54,12 +54,16 @@ def interactive_mode():
|
||||
print("\n请选择检索模式:")
|
||||
print(" [1] 分类层级筛选(学段 ➔ 学科 ➔ 年级)")
|
||||
print(" [2] 关键词全局搜索(如输入:'必修一'、'高一数学'、'生物')")
|
||||
print(" [3] 一键按「学段 ➔ 年级」两层目录全量下载全部教材")
|
||||
|
||||
mode_choice = input("请输入模式编号 (1/2, 默认 1): ").strip()
|
||||
mode_choice = input("请输入模式编号 (1/2/3, 默认 1): ").strip()
|
||||
|
||||
matched_books = []
|
||||
|
||||
if mode_choice == "2":
|
||||
if mode_choice == "3":
|
||||
matched_books = PepCatalog.fetch_and_decrypt_all()
|
||||
print(f"\n[+] 已加载全网全部教材,共 {len(matched_books)} 本。")
|
||||
elif mode_choice == "2":
|
||||
kw = input("\n🔍 请输入搜索关键词: ").strip()
|
||||
if not kw:
|
||||
print("[-] 关键词不能为空!")
|
||||
@@ -93,13 +97,15 @@ def interactive_mode():
|
||||
|
||||
print(f"\n✅ 共检索到 {len(matched_books)} 本教材:")
|
||||
print("-" * 65)
|
||||
for idx, b in enumerate(matched_books, 1):
|
||||
for idx, b in enumerate(matched_books[:15], 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']})")
|
||||
if len(matched_books) > 15:
|
||||
print(f" ... 以及其余 {len(matched_books) - 15} 本教材(已折叠显示)")
|
||||
print("-" * 65)
|
||||
|
||||
print("\n请选择下载范围:")
|
||||
@@ -139,14 +145,28 @@ def interactive_mode():
|
||||
return
|
||||
|
||||
out_dir = os.path.abspath("./downloads")
|
||||
print(f"\n🚀 即将开始下载 {len(to_download)} 本教材,保存目录: {out_dir}")
|
||||
print(f"\n🚀 即将开始下载 {len(to_download)} 本教材,基础保存目录: {out_dir}")
|
||||
print(f"🌲 采用「学段 ➔ 年级」两层子目录分类保存 (已存在 PDF 自动跳过)")
|
||||
downloader = PepDownloader(headless=True, output_dir=out_dir)
|
||||
|
||||
for idx, b in enumerate(to_download, 1):
|
||||
xd = b.get("xd", "其他学段")
|
||||
nj = b.get("nj", "通用").strip() or "通用"
|
||||
import re
|
||||
safe_xd = re.sub(r'[\/:*?"<>|]', '_', xd).strip()
|
||||
safe_nj = re.sub(r'[\/:*?"<>|]', '_', nj).strip()
|
||||
sub_dir = os.path.join(safe_xd, safe_nj)
|
||||
|
||||
print(f"\n==================================================")
|
||||
print(f"[{idx}/{len(to_download)}] 开始下载: 《{b.get('title')}》")
|
||||
print(f"[{idx}/{len(to_download)}] [{safe_xd}/{safe_nj}] 《{b.get('title')}》")
|
||||
print(f"==================================================")
|
||||
downloader.download_book(book_id=b["id"], custom_title=b.get("title"))
|
||||
downloader.download_book(
|
||||
book_id=b["id"],
|
||||
custom_title=b.get("title"),
|
||||
sub_dir=sub_dir,
|
||||
skip_if_exists=True,
|
||||
clean_temp=True
|
||||
)
|
||||
|
||||
print("\n🎉 全部选定任务执行完毕!")
|
||||
|
||||
@@ -154,14 +174,20 @@ def interactive_mode():
|
||||
def cli_args_mode(args):
|
||||
"""命令行参数直接执行模式"""
|
||||
print_banner()
|
||||
matched = PepCatalog.filter_books(xd=args.xd, xk=args.xk, nj=args.nj, keyword=args.search)
|
||||
if args.all:
|
||||
matched = PepCatalog.fetch_and_decrypt_all()
|
||||
else:
|
||||
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']})")
|
||||
for idx, b in enumerate(matched[:15], 1):
|
||||
print(f" [{idx}] [{b.get('xd')}|{b.get('nj')}] 《{b.get('title')}》 (ID: {b['id']})")
|
||||
if len(matched) > 15:
|
||||
print(f" ... 以及其余 {len(matched) - 15} 本教材(已折叠)")
|
||||
|
||||
if not args.yes:
|
||||
confirm = input(f"\n确认下载以上 {len(matched)} 本教材吗?(y/n, 默认 y): ").strip().lower()
|
||||
@@ -171,20 +197,40 @@ def cli_args_mode(args):
|
||||
|
||||
out_dir = os.path.abspath(args.output)
|
||||
downloader = PepDownloader(headless=True, output_dir=out_dir)
|
||||
use_tree = not args.flat
|
||||
|
||||
print(f"\n📂 保存根目录: {out_dir}")
|
||||
print(f"🌲 目录结构: {'按「学段/年级」两层子目录' if use_tree else '全部平铺在根目录'}")
|
||||
|
||||
for idx, b in enumerate(matched, 1):
|
||||
sub_dir = None
|
||||
if use_tree:
|
||||
import re
|
||||
safe_xd = re.sub(r'[\/:*?"<>|]', '_', b.get("xd", "其他学段")).strip()
|
||||
safe_nj = re.sub(r'[\/:*?"<>|]', '_', b.get("nj", "通用") or "通用").strip()
|
||||
sub_dir = os.path.join(safe_xd, safe_nj)
|
||||
|
||||
print(f"\n[{idx}/{len(matched)}] 正在下载: 《{b.get('title')}》...")
|
||||
downloader.download_book(book_id=b["id"], custom_title=b.get("title"))
|
||||
downloader.download_book(
|
||||
book_id=b["id"],
|
||||
custom_title=b.get("title"),
|
||||
sub_dir=sub_dir,
|
||||
skip_if_exists=True,
|
||||
clean_temp=True
|
||||
)
|
||||
|
||||
print(f"\n🎉 下载完成!文件已保存至: {out_dir}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="人民教育出版社电子教材 CLI 下载器")
|
||||
parser.add_argument("--all", "-a", action="store_true", help="下载全网全部 780+ 本教材")
|
||||
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("--flat", action="store_true", help="平铺存放在根目录下(默认自动按「学段/年级」两层子目录分类)")
|
||||
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)")
|
||||
@@ -204,7 +250,7 @@ def main():
|
||||
print(f"[+] 目录同步成功!共获取到 {len(books)} 本教材。")
|
||||
return
|
||||
|
||||
if args.xd or args.xk or args.nj or args.search:
|
||||
if args.all or args.xd or args.xk or args.nj or args.search:
|
||||
cli_args_mode(args)
|
||||
else:
|
||||
interactive_mode()
|
||||
|
||||
Reference in New Issue
Block a user