This document shows the actual text cleaning process implemented in historical_data_collector.py, not just theoretical cleaning steps.
📊 Visual Flow: See Cleaning Flow Diagram for a Mermaid diagram of the complete process.
📁 Raw Files
↓
🔍 File Type Detection
├── .txt, .txt.utf-8, _txt.utf-8 → Text Processing
├── .pdf → PDF Processing
├── .html, .htm → HTML Processing
├── .xml → XML Processing (Old Bailey, London Lives)
└── No Extension → Content Detection
├── HTML-like content → HTML Processing
├── Text-like content → Text Processing
└── Binary/Unknown → REJECTED
↓
🚫 Filename Language Check
├── Non-English characters → REJECTED (logged)
└── English/Latin → Continue
📄 Text File
↓
📖 Read with UTF-8 encoding (errors='ignore')
↓
🧹 clean_gutenberg_text()
├── Remove Project Gutenberg headers/footers
├── Remove metadata patterns
└── Clean whitespace
📄 PDF File
↓
🔧 extract_text_from_pdf()
├── Try system pdftotext (preferred)
└── Fallback to PyPDF2
↓
🧹 clean_pdf_text()
├── Remove page numbers
├── Remove library stamps
├── Remove headers/footers
└── Fix OCR artifacts (0→O, 1→I, 5→S, 8→B, rn→m, cl→d, etc.)
📄 HTML File
↓
🔧 clean_html_text()
├── BeautifulSoup parsing (if available)
├── Remove script, style, nav, header, footer
├── Extract text content
└── Remove wiki metadata
📄 XML File
↓
🔍 Detect XML Type
├── Old Bailey XML → extract_old_bailey_text()
│ ├── Extract trial accounts
│ ├── Extract front matter
│ └── Preserve historical language
└── London Lives XML → extract_london_lives_text()
├── Extract paragraphs with semantic markup
├── Extract lists
└── Preserve person names, places, occupations
↓
🧹 Type-specific cleaning
├── Old Bailey → clean_old_bailey_text()
└── London Lives → clean_london_lives_text()
📝 Extracted Text
↓
🔧 normalize_text()
├── Fix encoding issues (’→', “→", â€"→—, etc.)
├── Normalize Unicode (NFC)
├── Handle single long lines → break_long_line()
├── Normalize line endings (\r\n→\n)
└── Clean excessive whitespace
📝 Normalized Text
↓
🔍 Duplicate Detection
├── Content hash check → REJECTED if duplicate
└── Continue if unique
↓
🌍 Language Detection
├── Non-English detected → REJECTED (logged)
└── English → Continue
↓
📊 Quality Analysis (analyze_text_quality())
├── Length checks (min 200 chars)
├── Project Gutenberg validation (relaxed criteria)
├── Historical text validation (very relaxed)
├── OCR artifact detection
├── Advertisement density check
└── Meaningful word ratio (≥50%)
↓
❌ Poor Quality → REJECTED (logged with details)
✅ Good Quality → Continue
✅ Validated Text
↓
💾 Save to Processed Directory
├── Filename: cleaned_{original_stem}.txt
├── UTF-8 encoding
└── Update statistics
↓
📊 Statistics Tracking
├── Characters before/after
├── Files processed/cleaned/skipped/failed
├── OCR artifacts fixed
├── Gutenberg headers removed
└── Rejection reasons logged
def clean_gutenberg_text(text: str) -> str:
# Remove START/END markers
# Remove metadata patterns (Title, Author, Release Date, etc.)
# Clean excessive whitespace
# Preserve historical language patternsdef clean_pdf_text(text: str) -> str:
# Remove page numbers: [Page 123], Page 123, standalone numbers
# Remove library stamps: Internet Archive, Google, etc.
# Remove headers/footers: all-caps lines, chapter numbers
# Fix OCR errors: 0→O, 1→I, 5→S, 8→B, rn→m, cl→d, ii→n, vv→w, ſ→sdef clean_html_text(html_content: str) -> str:
# BeautifulSoup parsing (if available)
# Remove unwanted elements: script, style, nav, header, footer, aside
# Extract text content with proper spacing
# Remove wiki metadata: "This page was last edited", "Jump to navigation", etc.def extract_old_bailey_text(soup) -> str:
# Extract trial accounts (main narrative)
# Extract front matter (session info)
# Preserve historical language and structure
# Clean up XML artifacts while maintaining readabilitydef extract_london_lives_text(soup) -> str:
# Extract paragraphs with semantic markup
# Preserve person names, places, occupations, dates
# Extract lists with proper formatting
# Clean up markup artifacts- Long capitals:
[A-Z]{5,}\s+[A-Z]{5,} - Spaced letters:
\b[A-Za-z]\s+[A-Za-z]\s+[A-Za-z]\s+[A-Za-z]\b - Special characters:
[!@#$%^&*()]{3,} - Mixed numbers/letters:
\b\d+[A-Za-z]+\d+\b - Long non-word sequences:
[^\w\s]{10,}
- Common patterns: "this day is published", "just ready", "elegantly bound"
- Price notations: "price \d+s"
- Publisher references: "paternoster row", "corner of", "publishers"
- Book advertisements: "now ready", "new novels", "advertisements"
- Ratio calculation: meaningful_words / total_words
- Threshold: ≥50% for general content
- Relaxed criteria: ≥40% for Project Gutenberg
- Very relaxed: ≥1000 chars + 100 words for historical texts
- Non-English filename (Arabic, Chinese, Cyrillic, etc.)
- Duplicate content (hash-based detection)
- Non-English content (language detection)
- Poor quality (OCR artifacts, ads, low meaningful word ratio)
- Too short (<200 characters, <5 lines, <50 words)
- Unsupported file type (binary, unknown format)
{
"timestamp": "2024-01-15 10:30:45",
"file_path": "/path/to/file.txt",
"filename": "file.txt",
"file_size": 1234,
"rejection_reason": "Poor content quality",
"details": {
"text_length": 150,
"rejection_reasons": ["Text too short (< 200 chars)"],
"ocr_issues": [],
"advertisement_indicators": [],
"meaningful_word_ratio": 0.3
},
"preview": "First 500 characters of file content"
}- Files: downloaded, processed, cleaned, skipped, failed, sanitized
- Characters: before cleaning, after cleaning, removed
- Quality: Gutenberg headers removed, OCR artifacts fixed, HTML markup removed
- Content: duplicates found, non-English skipped, poor quality skipped
- Sources: Gutenberg processed/accepted, encoding issues fixed
- Total rejected files with detailed reasons
- Rejection summary by reason type
- File previews for manual review
- Quality analysis details for each rejection
📁 Cleaned Files
↓
🔧 create_comprehensive_corpus()
├── Read all cleaned_*.txt files
├── Split into training segments (split_into_training_segments)
│ ├── Split on double newlines (paragraphs)
│ ├── Max length: 2000 characters
│ ├── Min length: 100 characters
│ └── Further split long segments at sentence boundaries
├── Filter segments (min 50 characters)
└── Write to london_historical_corpus_comprehensive.txt
- ✅ File type detection and routing
- ✅ Format-specific cleaning (PDF, HTML, XML)
- ✅ Historical text preservation (Old Bailey, London Lives)
- ✅ Quality analysis with detailed rejection logging
- ✅ Duplicate detection using content hashing
- ✅ Language detection and filtering
- ✅ OCR artifact detection and correction
- ✅ Advertisement detection and filtering
- ✅ Corpus segmentation for training
- ❌ Separate text_cleaner.py script (functionality integrated)
- ❌ Quality scoring system (0-100 points)
- ❌ Batch processing with separate scripts
- ❌ Advanced OCR correction patterns
- ❌ Separate analysis tools
historical_data_collector.py→process_file()methoddownload_and_process_sources()→ Main collection pipelinecreate_comprehensive_corpus()→ Final corpus creation
- Data source flags in
config.py(enable_old_bailey, enable_london_lives, etc.) - Quality thresholds hardcoded in analysis functions
- File type detection based on extensions and content
This is the actual cleaning process implemented in the codebase, not the theoretical process described in TEXT_CLEANING_GUIDE.md.