Download docs/semantic/fetch10.py from Lawless1987/covenant: direct link, hf CLI and curl.
- Browser
- Download file 3.59 kB
-
https://huggingface.co/Lawless1987/covenant/resolve/main/docs/semantic/fetch10.py
- Command line
-
hf download hf://Lawless1987/covenant/docs/semantic/fetch10.py
-
curl -L -o fetch10.py https://huggingface.co/Lawless1987/covenant/resolve/main/docs/semantic/fetch10.py
3.59 kB
| """SEM2 corpus fetch: same deterministic sampling as fetch_corpus.py (seed | |
| 20260824, shuffle of the whole catalogue), extended from 3M to 10M words per | |
| language. The 10M corpus is therefore (up to availability drift and thread | |
| timing at the stop boundary) a superset of the 08-24 corpus. Ids recorded to | |
| MANIFEST_10M.json per language as each finishes; resumable per language.""" | |
| import csv, json, os, re, sys, threading, urllib.request | |
| from concurrent.futures import ThreadPoolExecutor | |
| CAT = "/root/sem/pg_catalog.csv" | |
| OUT = "/root/sem/corpus10" | |
| TARGET_WORDS = 11_300_000 | |
| UA = {"User-Agent": "corpus-builder/1.0"} | |
| SEED = 20260824 | |
| csv.field_size_limit(10 ** 7) | |
| START = re.compile(r"\*\*\*\s*START OF (?:THE|THIS) PROJECT GUTENBERG EBOOK.*?\*\*\*", re.I | re.S) | |
| END = re.compile(r"\*\*\*\s*END OF (?:THE|THIS) PROJECT GUTENBERG EBOOK.*?\*\*\*", re.I | re.S) | |
| def catalogue(lang): | |
| ids = [] | |
| with open(CAT, encoding="utf-8") as f: | |
| for row in csv.DictReader(f): | |
| if row["Type"] != "Text" or row["Language"] != lang: | |
| continue | |
| try: | |
| ids.append(int(row["Text#"])) | |
| except ValueError: | |
| pass | |
| return ids | |
| def fetch(tid): | |
| url = f"https://www.gutenberg.org/cache/epub/{tid}/pg{tid}.txt" | |
| try: | |
| raw = urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=40).read() | |
| except Exception: | |
| return None | |
| txt = None | |
| for enc in ("utf-8", "latin-1"): | |
| try: | |
| txt = raw.decode(enc) | |
| break | |
| except UnicodeDecodeError: | |
| continue | |
| if not txt or len(txt) < 20_000: | |
| return None | |
| m = START.search(txt) | |
| if m: | |
| txt = txt[m.end():] | |
| m = END.search(txt) | |
| if m: | |
| txt = txt[:m.start()] | |
| return txt if txt.count(" ") >= 5_000 else None | |
| def build(lang): | |
| import random | |
| ids = catalogue(lang) | |
| rng = random.Random(SEED) | |
| rng.shuffle(ids) | |
| lock = threading.Lock() | |
| state = {"words": 0, "used": [], "stop": False} | |
| out = open(os.path.join(OUT, f"{lang}.txt"), "w", encoding="utf-8") | |
| def work(tid): | |
| if state["stop"]: | |
| return | |
| body = fetch(tid) | |
| if not body: | |
| return | |
| n = body.count(" ") | |
| with lock: | |
| if state["stop"]: | |
| return | |
| out.write(body) | |
| out.write("\n") | |
| state["words"] += n | |
| state["used"].append(tid) | |
| if state["words"] >= TARGET_WORDS: | |
| state["stop"] = True | |
| with ThreadPoolExecutor(max_workers=12) as ex: | |
| list(ex.map(work, ids[:4000])) | |
| out.close() | |
| entry = {"ids": sorted(state["used"]), "approx_words": state["words"], | |
| "seed": SEED, "target_words": TARGET_WORDS, | |
| "catalogue_size": len(ids), | |
| "sampling": "deterministic shuffle of the whole catalogue"} | |
| mp = os.path.join(OUT, "MANIFEST_10M.json") | |
| manifest = json.load(open(mp)) if os.path.exists(mp) else {} | |
| manifest[lang] = entry | |
| json.dump(manifest, open(mp, "w"), indent=1) | |
| print(f"{lang}: {len(state['used'])} books ~{state['words']:,} words " | |
| f"(catalogue {len(ids):,})", flush=True) | |
| if __name__ == "__main__": | |
| for lang in sys.argv[1:]: | |
| p = os.path.join(OUT, f"{lang}.txt") | |
| mp = os.path.join(OUT, "MANIFEST_10M.json") | |
| done = json.load(open(mp)) if os.path.exists(mp) else {} | |
| if lang in done: | |
| print(f"{lang}: already fetched ({done[lang]['approx_words']:,} words)") | |
| continue | |
| build(lang) | |