Bottleneck Headless Browser dan LLM pada Pipeline Ingest
Menggunakan headless browser seperti Playwright/Puppeteer atau LLM untuk mengekstrak konten bersih dari raw HTML adalah pemborosan resource. Headless browser membutuhkan 300 MB hingga 1 GB RAM per worker dan latensi render 500–3.000 ms per halaman. Sementara itu, LLM memicu latensi jaringan ratusan milidetik, biaya token per request, dan output non-deterministik.
Untuk pipeline ingest dataset (seperti retrieval corpora, index pencarian, atau data training LLM), solusi Pareto-optimal bertumpu pada parser berbasis tree-walk heuristik deterministik. Pendekatan ini berjalan langsung di CPU memory runtime tanpa render engine, memangkas konsumsi RAM di bawah 30 MB per worker dengan throughput di atas 500 dokumen per detik.
Arsitektur Parser Deterministik: Selectolax (Lexbor)
Alternatif paling efisien untuk memproses HTML kotor secara deterministik adalah parser berbasis C seperti Lexbor (via binding selectolax di Python) atau native DOM engine (seperti kuchiki/scraper di Rust). Engine ini mem-parsing HTML5 secara spesifikasi-akurat tanpa runtime overhead eksekusi JavaScript.
# cleaner.py
import re
from selectolax.parser import HTMLParser
# Daftar elemen non-konten yang harus dibuang dari DOM tree
DROP_TAGS = {
"script", "style", "noscript", "nav", "footer",
"header", "aside", "form", "svg", "iframe"
}
def extract_clean_text(raw_html: str) -> str:
# ponytail: parse standar HTML5 string. Ganti ke lxml jika butuh XPath kompleks.
tree = HTMLParser(raw_html)
# 1. Hapus node noise secara in-place
for node in tree.css(",".join(DROP_TAGS)):
node.decompose()
# 2. Prioritaskan konten utama jika semantic container tersedia
body = tree.css_first("article, main, [role='main']") or tree.body
if not body:
return ""
# 3. Ekstraksi text nodes dan normalisasi whitespace
raw_text = body.text(separator=" ", strip=True)
return re.sub(r"\s+", " ", raw_text).strip()
Dilewati: Ekstraksi metadata berbasis visual tree. Tambahkan jika: Konten hanya di-generate lewat client-side hydration (SSR wajib sebelum parser ini berjalan).
Mitigasi Edge Case: Malformed DOM dan Encoding Rusak
HTML publik sering kali mengalami tag tidak tertutup, nest looping (misal tag <div> bersarang tanpa akhir), atau mismatch encoding (header menyatakan UTF-8, isi dokumen ISO-8859-1). Cara menangani edge case tersebut secara konsisten:
- Parser Toleran HTML5: Gunakan parser yang mengimplementasikan spesifikasi HTML5 tree construction (seperti Lexbor atau html5lib). Parser jenis ini otomatis memperbaiki tag yang tidak ditutup dan menempatkan tag yatim ke hierarchy yang valid tanpa melempar exception
ParseError. - Encoding Fallback: Selalu lakukan decode raw bytes menggunakan
ftfyatau standard library dengan parametererrors="replace"jika deteksi charset gagal. Jangan biarkan decoding error menghentikan worker batch. - Batas Kedalaman Rekursi: Malformed DOM ekstrem dapat memicu deep nesting. Parser C seperti Lexbor membatasi nesting depth secara internal untuk mencegah stack overflow.
Integrasi Step CI: Validasi Regresi dan Latency Budget
CI pipeline harus menguji dua hal: kebersihan teks ekstraksi (assertion correctness) dan latency budget (regresi performa). Jika perubahan kode memperlambat proses atau meloloskan tag noise, pipeline otomatis gagal.
1. Assertion Test Suite
# test_cleaner.py
import time
import pytest
from cleaner import extract_clean_text
DIRTY_HTML_FIXTURE = """
<!DOCTYPE html>
<html>
<head><title>Benchmark</title><script>var ads = 1;</script></head>
<body>
<header><nav><a href="/">Home</a></nav></header>
<main>
<article>
<h1>Judul Konten</h1>
<p>Paragraf utama berisi informasi teknis valid.</p>
</article>
</main>
<footer>Copyright 2026</footer>
</body>
</html>
"""
def test_extraction_sanitization():
result = extract_clean_text(DIRTY_HTML_FIXTURE)
assert "Judul Konten" in result
assert "Paragraf utama" in result
assert "var ads" not in result
assert "Copyright 2026" not in result
assert "Home" not in result
def test_latency_budget():
# Budget: parsing 500 dokumen fixture tidak boleh melebihi 100 ms (< 0.2ms / doc)
start = time.perf_counter()
for _ in range(500):
_ = extract_clean_text(DIRTY_HTML_FIXTURE)
duration_ms = (time.perf_counter() - start) * 1000
assert duration_ms < 100.0, f"Latency budget terlampaui: {duration_ms:.2f}ms"
2. Workflow GitHub Actions
# .github/workflows/ci-sanitizer.yml
name: Data Pipeline Ingest CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
validate-sanitizer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install selectolax pytest
- name: Run Extraction & Latency Budget Tests
run: |
pytest test_cleaner.py -v -s
Perbandingan Performa: Headless vs. LLM vs. Parser Deterministik
Data berikut membandingkan metrik throughput dan penggunaan resource pada server 4-core vCPU standar untuk pemrosesan 10.000 dokumen HTML (ukuran rata-rata 120 KB per file):
| Metrik | Playwright (Chromium) | LLM Prompting (API) | Selectolax (Lexbor C) |
|---|---|---|---|
| Throughput | ~2–5 docs/detik | ~10–25 docs/detik (rate limited) | ~800–1.200 docs/detik |
| Alokasi Memori Worker | ~450 MB per proses | ~25 MB (client overhead) | ~18 MB per proses |
| Biaya Eksekusi | Tinggi (komputasi CPU VM) | Sangat Tinggi (biaya token input) | Minimal (CPU cycle standar) |
| Deterministik | Ya (DOM-based) | Tidak (kemungkinan halusinasi) | Ya (100% konsisten) |
| Penanganan Malformed | Tinggi (browser recovery) | Tinggi (toleran konteks) | Tinggi (HTML5 tree construct) |
Troubleshooting dan Debugging
- Teks terpotong pada tag kustom: Framework modern sering menggunakan Web Components (misal
<app-content>). Jika ekstraktor mengembalikan teks kosong, periksa apakah tag kontainer terdaftar di luar selektor fallback (main, article). Solusi: perlebar query selektor CSS untuk membaca semantic container kustom. - Entity HTML lolos ke output: Gunakan fungsi unescape native seperti
html.unescape()jika raw string masih menyisakan kode entitas seperti&atau". - Worker out-of-memory pada batch raksasa: Jangan kumpulkan seluruh tree node di memory. Proses file secara generator/streaming baris per baris atau stream tar archive agar garbage collector dapat segera membebaskan memori objek C Lexbor.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!