Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| 一键抓取所有 HuggingFace 数据集的 PDF 直链,按分类+数量分片输出多个小 JSON 文件。 | |
| """ | |
| import json, urllib.request, urllib.error, urllib.parse, time, os, sys, io, math | |
| sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') | |
| # 数据集配置 | |
| DATASETS = [ | |
| {"id": "long_meishi", "name": "长篇·美式", "genre": "normal", "length": "long", "layout": "meishi", "repo": "quejing/20000-meishi-pdf", "subPath": None}, | |
| {"id": "long_yingshi", "name": "长篇·英式", "genre": "normal", "length": "long", "layout": "yingshi", "repo": "quejing/20000-yingshi-pdf", "subPath": None}, | |
| {"id": "short_meishi", "name": "短篇·美式", "genre": "normal", "length": "short", "layout": "meishi", "repo": "quejing/small-meishi-pdf", "subPath": None}, | |
| {"id": "short_yingshi", "name": "短篇·英式", "genre": "normal", "length": "short", "layout": "yingshi", "repo": "quejing/small-yingshi-pdf", "subPath": None}, | |
| {"id": "zashu_meishi", "name": "杂书名著·美式", "genre": "zashu", "length": "long", "layout": "meishi", "repo": "quejing/zashuPDF", "subPath": "旧书美式PDF"}, | |
| {"id": "zashu_yingshi", "name": "杂书名著·英式", "genre": "zashu", "length": "long", "layout": "yingshi", "repo": "quejing/zashuPDF", "subPath": "旧书英式PDF"}, | |
| {"id": "zhongwen_meishi","name": "古典中文·美式", "genre": "zhongwen", "length": "long", "layout": "meishi", "repo": "quejing/zhongwenxiaoshuo", "subPath": "古典美式PDF"}, | |
| {"id": "zhongwen_yingshi","name": "古典中文·英式","genre": "zhongwen", "length": "long", "layout": "yingshi", "repo": "quejing/zhongwenxiaoshuo", "subPath": "古典英式PDF"}, | |
| ] | |
| BASE_URL = "https://huggingface.co" | |
| API_BASE = "https://huggingface.co/api" | |
| CHUNK_SIZE = 5000 # 每个分片最多5000条 | |
| def fetch_all_files(repo): | |
| url = f"{API_BASE}/datasets/{repo}?full=true" | |
| req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) | |
| resp = urllib.request.urlopen(req, timeout=120) | |
| data = json.loads(resp.read().decode("utf-8")) | |
| return data.get("siblings", []) | |
| def build_pdf_list(siblings, sub_path, ds_info): | |
| results = [] | |
| for sib in siblings: | |
| fname = sib.get("rfilename", "") | |
| if not fname.lower().endswith(".pdf"): | |
| continue | |
| if sub_path and not fname.startswith(sub_path + "/"): | |
| continue | |
| base_name = fname.rsplit("/", 1)[-1] | |
| # 拼接直链 — 对路径中特殊字符做 URL 编码,防止 &、空格等引起问题 | |
| encoded_fname = urllib.parse.quote(fname, safe='/%') | |
| direct_url = f"{BASE_URL}/datasets/{ds_info['repo']}/resolve/main/{encoded_fname}" | |
| results.append([base_name, direct_url]) # 紧凑格式: [文件名, 下载链接] | |
| return results | |
| def main(): | |
| out_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") | |
| os.makedirs(out_dir, exist_ok=True) | |
| # 索引文件:列出所有数据源及其分片信息 | |
| index = {"sources": []} | |
| for idx, ds in enumerate(DATASETS): | |
| print(f"[{idx+1}/{len(DATASETS)}] 正在获取: {ds['name']} ({ds['repo']})", flush=True) | |
| try: | |
| siblings = fetch_all_files(ds["repo"]) | |
| pdfs = build_pdf_list(siblings, ds["subPath"], ds) | |
| total = len(pdfs) | |
| chunks = math.ceil(total / CHUNK_SIZE) if total > 0 else 1 | |
| print(f" -> 找到 {total} 个 PDF,分 {chunks} 个文件", flush=True) | |
| # 写分片文件 | |
| chunk_files = [] | |
| for ci in range(chunks): | |
| chunk_pdfs = pdfs[ci * CHUNK_SIZE : (ci + 1) * CHUNK_SIZE] | |
| chunk_name = f"{ds['id']}_{ci}.json" | |
| chunk_path = os.path.join(out_dir, chunk_name) | |
| # 紧凑JSON,不缩进,不用ensure_ascii | |
| with open(chunk_path, "w", encoding="utf-8") as f: | |
| json.dump(chunk_pdfs, f, ensure_ascii=False, separators=(',', ':')) | |
| fsize = os.path.getsize(chunk_path) | |
| print(f" -> {chunk_name}: {len(chunk_pdfs)} 条, {fsize:,} bytes", flush=True) | |
| chunk_files.append({"f": chunk_name, "n": len(chunk_pdfs)}) | |
| index["sources"].append({ | |
| "id": ds["id"], | |
| "name": ds["name"], | |
| "genre": ds["genre"], | |
| "length": ds["length"], | |
| "layout": ds["layout"], | |
| "repo": ds["repo"], | |
| "total": total, | |
| "chunks": chunk_files, | |
| }) | |
| except Exception as e: | |
| print(f" !! 错误: {e}", flush=True) | |
| index["sources"].append({ | |
| "id": ds["id"], | |
| "name": ds["name"], | |
| "genre": ds["genre"], | |
| "length": ds["length"], | |
| "layout": ds["layout"], | |
| "repo": ds["repo"], | |
| "total": 0, | |
| "chunks": [], | |
| "error": str(e), | |
| }) | |
| time.sleep(0.3) | |
| # 写索引文件 | |
| idx_path = os.path.join(out_dir, "index.json") | |
| with open(idx_path, "w", encoding="utf-8") as f: | |
| json.dump(index, f, ensure_ascii=False, separators=(',', ':')) | |
| # 汇总 | |
| total = sum(s["total"] for s in index["sources"]) | |
| total_chunks = sum(len(s["chunks"]) for s in index["sources"]) | |
| print(f"\n{'='*60}", flush=True) | |
| print(f"完成! {len(index['sources'])} 数据源, {total} PDF, {total_chunks} 个分片文件", flush=True) | |
| for s in index["sources"]: | |
| print(f" {s['name']}: {s['total']} 个 ({len(s['chunks'])} 分片)", flush=True) | |
| # 清理旧的单文件 | |
| old = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pdf_data.json") | |
| if os.path.exists(old): | |
| os.remove(old) | |
| print(f"\n已删除旧文件: pdf_data.json", flush=True) | |
| print(f"\n数据目录: {out_dir}", flush=True) | |
| if __name__ == "__main__": | |
| main() | |