--- created: 2026-08-06T15:15:00 tags: - NotebookLM - colab - vector updated: 2026-08-06T15:15:00 --- # REFACTORED COLAB NOTEBOOK - Free Tier Compatible ## Kentucky Kernel Archive → Vector DB Pipeline --- ## CELL 1: SETUP & MOUNT (DO THIS FIRST) ```python ## @title SETUP (RUN ONCE) ## Install everything upfront !pip install internetarchive chromadb sentence-transformers ## Mount Drive once, reuse everywhere from google.colab import drive drive.mount('/content/drive') print("✅ Setup complete. Drive mounted.") ``` --- ## CELL 2: CONFIGURE PATHS (USER INPUT) ```python ## @title CONFIG - Enter Your Settings ## User inputs creator_name = input("Enter creator name (e.g., 'The Mt. Sterling Advocate'): ").strip() test_mode = input("Test mode? (y/n, tests with 50 issues): ").strip().lower() == 'y' ## Paths BASE_DOWNLOAD = f'/content/{creator_name.replace(" ", "_")}_Archive' CLEANED_JSON = f'/content/drive/MyDrive/{creator_name.replace(" ", "_")}_Cleaned/cleaned_pages.json' VECTORDB_PATH = f'/content/drive/MyDrive/{creator_name.replace(" ", "_")}_VectorDB' ## Create directories import os os.makedirs(os.path.dirname(CLEANED_JSON), exist_ok=True) os.makedirs(BASE_DOWNLOAD, exist_ok=True) print(f"✓ Creator: {creator_name}") print(f"✓ Download to: {BASE_DOWNLOAD}") print(f"✓ Cleaned JSON: {CLEANED_JSON}") print(f"✓ Vector DB: {VECTORDB_PATH}") print(f"✓ Test Mode: {test_mode}") ``` --- ## CELL 3: DOWNLOAD FROM ARCHIVE.ORG ```python ## @title DOWNLOAD (This takes 30-45 min) from internetarchive import search_items, get_item, get_session from concurrent.futures import ThreadPoolExecutor, as_completed ia_session = get_session() ia_session.mount_http_adapter() ## Search query = f'creator:"{creator_name}"' print(f"Searching: {query}") search = search_items(query, archive_session=ia_session) identifiers = [result['identifier'] for result in search] if test_mode: identifiers = identifiers[:50] # Test with 50 only total_items = len(identifiers) print(f"Found {total_items} issues. Starting download with 2 workers (safe)...\n") def fast_download(identifier): target_folder = os.path.join(BASE_DOWNLOAD, identifier) if os.path.exists(target_folder) and len(os.listdir(target_folder)) >= 3: return f"⏭️ Skipped: {identifier}" try: item = get_item(identifier, archive_session=ia_session) item.download( destdir=BASE_DOWNLOAD, glob_pattern=['*meta.xml', '*djvu.xml', '*djvu.txt'], ignore_existing=True, retries=3 ) return f"✅ Downloaded: {identifier}" except Exception as e: return f"❌ Failed: {identifier} - {str(e)[:50]}" ## Use 2 workers (safer than 4 on Free Tier) completed = 0 failed = [] with ThreadPoolExecutor(max_workers=2) as executor: futures = {executor.submit(fast_download, identifier): identifier for identifier in identifiers} for future in as_completed(futures): completed += 1 result = future.result() if "❌" in result: failed.append(result) if completed % 25 == 0 or completed == total_items: print(f"[{completed}/{total_items}] {result[:60]}") print(f"\n✅ Download complete. {len(failed)} failures.") if failed: print("Failed items:") for f in failed[:5]: print(f" {f}") ``` --- ## CELL 4: PARSE XML & CLEAN TEXT (CPU-BASED) ```python ## @title PARSE & CLEAN import xml.etree.ElementTree as ET import json import pandas as pd # ← Use pandas, not cudf import glob print("Scanning for DJVU XML files...") djvu_files = glob.glob(os.path.join(BASE_DOWNLOAD, '**/*djvu.xml'), recursive=True) print(f"Found {len(djvu_files)} XML files.") page_data_list = [] failed_issues = [] for djvu_path in djvu_files: folder_name = os.path.basename(os.path.dirname(djvu_path)) try: tree = ET.parse(djvu_path) root = tree.getroot() pages = root.findall('.//OBJECT') for page_index, page in enumerate(pages): page_text = [] for word in page.findall('.//WORD'): if word.text: page_text.append(word.text) raw_page = " ".join(page_text) if raw_page.strip(): page_data_list.append({ "issue_id": folder_name, "page_number": page_index + 1, "text": raw_page }) except Exception as e: failed_issues.append((folder_name, str(e))) print(f"Extracted {len(page_data_list)} pages, {len(failed_issues)} failures.") ## CLEANING: Use pandas (CPU) - faster than GPU for regex print("Cleaning text with regex...") texts_df = pd.DataFrame({'text': [p['text'] for p in page_data_list]}) ## Fix hyphenation texts_df['text'] = texts_df['text'].str.replace(r'-\s*\n\s*', '', regex=True) ## Remove garbage OCR chars texts_df['text'] = texts_df['text'].str.replace(r'[^a-zA-Z0-9\s.,;:\'"!?()-]', '', regex=True) ## Normalize spaces texts_df['text'] = texts_df['text'].str.replace(r'\s+', ' ', regex=True) ## Reattach cleaned text for i, clean_text in enumerate(texts_df['text'].tolist()): page_data_list[i]['text'] = clean_text ## Save print(f"Saving {len(page_data_list)} pages to JSON...") with open(CLEANED_JSON, 'w', encoding='utf-8') as f: json.dump(page_data_list, f, indent=2) print(f"✅ Complete! {len(page_data_list)} clean pages saved.") ``` --- ## CELL 5: BUILD VECTOR DB (GPU-Accelerated Embeddings) ```python ## @title BUILD VECTOR DB (30-60 min - may timeout, that's ok) import json import chromadb from chromadb.utils import embedding_functions print("Loading cleaned pages...") with open(CLEANED_JSON, 'r', encoding='utf-8') as f: pages_data = json.load(f) print(f"Loaded {len(pages_data)} pages.") ## Initialize ChromaDB with GPU embeddings print("Initializing ChromaDB with GPU embeddings...") os.makedirs(VECTORDB_PATH, exist_ok=True) gpu_ef = embedding_functions.SentenceTransformerEmbeddingFunction( model_name="all-MiniLM-L6-v2", device="cuda" # GPU accelerated ) chroma_client = chromadb.PersistentClient(path=VECTORDB_PATH) collection = chroma_client.get_or_create_collection( name="historical_news", embedding_function=gpu_ef ) ## Chunking function def chunk_text(text, chunk_size=800, overlap=150): chunks = [] start = 0 while start < len(text): end = min(start + chunk_size, len(text)) chunks.append(text[start:end]) start += chunk_size - overlap return chunks ## Extract & chunk documents = [] metadatas = [] ids = [] print("Chunking and preparing documents...") for page in pages_data: issue_id = page.get('issue_id', 'unknown') page_num = page.get('page_number', 0) text = page.get('text', '') if not text.strip(): continue chunks = chunk_text(text) for chunk_idx, chunk_text_data in enumerate(chunks): documents.append(chunk_text_data) metadatas.append({ "issue_id": issue_id, "page_number": page_num, "chunk_id": chunk_idx }) ids.append(f"{issue_id}_p{page_num}_c{chunk_idx}") total_chunks = len(documents) print(f"Prepared {total_chunks} chunks.") ## Save checkpoint before starting (in case it times out) checkpoint = { 'total_chunks': total_chunks, 'vectorized': 0, 'started_at': str(__import__('datetime').datetime.now()) } checkpoint_file = os.path.join(VECTORDB_PATH, 'progress.json') print("Adding to vector DB...") BATCH_SIZE = 250 for b in range(0, total_chunks, BATCH_SIZE): try: collection.add( documents=documents[b:b+BATCH_SIZE], metadatas=metadatas[b:b+BATCH_SIZE], ids=ids[b:b+BATCH_SIZE] ) checkpoint['vectorized'] = b + BATCH_SIZE if (b + BATCH_SIZE) % 1000 == 0 or (b + BATCH_SIZE) >= total_chunks: with open(checkpoint_file, 'w') as f: json.dump(checkpoint, f) print(f"✓ {b + BATCH_SIZE}/{total_chunks} chunks vectorized") except Exception as e: print(f"❌ Error at batch {b}: {e}") print(f"Saved progress checkpoint. DB is recoverable.") break print(f"✅ Vector DB complete! Stored at: {VECTORDB_PATH}") print(f"Query it with: chroma_client.get_collection('historical_news')") ``` --- ## CELL 6: TEST THE VECTOR DB (Optional) ```python ## @title TEST QUERY import chromadb db_path = VECTORDB_PATH client = chromadb.PersistentClient(path=db_path) collection = client.get_collection("historical_news") ## Test query test_query = input("Enter a search term (e.g., 'railroad accident'): ").strip() results = collection.query( query_texts=[test_query], n_results=5 ) print(f"\n🔍 Top 5 results for: '{test_query}'\n") for i, (doc, metadata, distance) in enumerate(zip( results['documents'][0], results['metadatas'][0], results['distances'][0] )): print(f"{i+1}. Issue: {metadata['issue_id']}, Page {metadata['page_number']}") print(f" Relevance score: {1 - distance:.2f}") print(f" Text: {doc[:150]}...\n") ``` --- ## KEY CHANGES FROM ORIGINAL | Issue | Original | Fixed | |-------|----------|-------| | **cudf** | ❌ Will crash | ✅ Use pandas (faster anyway) | | **Drive mounts** | 3x (wasteful) | 1x (efficient) | | **GPU usage** | Regex on GPU (slow) | Embeddings on GPU (fast) | | **Hardcoded paths** | ❌ Not flexible | ✅ User input + parameterized | | **Error handling** | Silent fails | ✅ Logged failures | | **Checkpointing** | None | ✅ Progress saved | | **Workers** | 4 (risky) | 2 (safe) | | **Test mode** | N/A | ✅ Optional test with 50 issues | | **Memory** | All in RAM | ✅ Batch processing | --- ## EXPECTED RESULTS - **Download:** 30-45 min (1756 issues) - **Parse & Clean:** 10-15 min - **Vectorize:** 45-90 min (may timeout on Free Tier around 70 min mark) - **Total:** ~2.5 hours If vectorization times out, your progress is saved and resumable in next session.