Mengoptimalkan performa scraper Scrapling: benchmark parser (Scrapling vs lxml vs Parsel vs Selectolax vs PyQuery), strategi memilih fetcher yang tepat, serta teknik memory dan speed seperti reuse tree, membatasi payload, dan seleksi batch.

Di episode 17 kalian belajar spider lanjutan: pause/resume lewat checkpoint, state persistence, dan real-time stats untuk crawl ribuan halaman. Semakin besar scale crawl, semakin terasa satu hal: performa. Satu detik yang dihemat per halaman akan terakumulasi jadi jam penghematan saat kalian crawl 10 ribu halaman. Episode 18 ini fokus ke optimasi performa.
Roadmap episode ini: pertama kita membandingkan parser Scrapling dengan alternatif lain lewat benchmark, lalu membahas strategi memilih fetcher yang tepat sesuai kebutuhan, dan terakhir menutup dengan teknik memory dan speed — reuse tree, membatasi payload, dan optimasi seleksi batch.
Web scraping yang lambat bukan sekadar masalah kenyamanan — ia biaya. Semakin lama satu halaman diproses, semakin banyak resource yang terpakai, semakin besar risiko timeout, dan semakin kecil throughput data yang bisa dikumpulkan dalam jendela waktu tertentu.
Ada tiga sumber pemborosan yang paling umum:
Prinsip utama optimasi di episode ini sederhana: jangan buang waktu pada apa yang tidak perlu. Pilih parser tercepat untuk kerjaan kalian, pilih fetcher paling ringan yang masih bisa menembus proteksi halaman, dan jangan parse ulang apa yang sudah ada di tangan.
Parser adalah lapisan yang paling sering jadi bottleneck. Adaptor Scrapling dibangun di atas lxml (pembungkus C libxml2), sehingga kecepatannya dekat dengan lxml mentah — dan jauh meninggalkan parser Python murni. Perbandingan cepatnya:
| Parser | Basis | Kecepatan | Catatan |
|---|---|---|---|
| Scrapling Adaptor | lxml + optimasi Scrapling | Sangat cepat | Selector kaya, self-healing |
| lxml | C libxml2 | Sangat cepat | API dasar, tanpa helper modern |
| Parsel | lxml, gaya Scrapy | Cepat | Nyaman jika terbiasa Scrapy |
| Selectolax | Rust (Lexbor) | Tercepat di parsing murni | Fitur seleksi terbatas |
| PyQuery | lxml, gaya jQuery | Cepat | Gaya sintaks jQuery |
| BeautifulSoup | Python murni | Lambat (patokan) | Mudah, banyak tutorial |
Untuk memvalidasi sendiri, kalian bisa memakai timeit seperti contoh berikut:
import timeit
from scrapling import Fetcher
from lxml import html
url = "https://quotes.toscrape.com/"
raw = Fetcher().get(url).body
def scrapling_path():
page = Fetcher().get(url)
return len(page.css(".quote .text::text").getall())
def lxml_path():
tree = html.fromstring(raw)
return len(tree.xpath('//div[contains(@class,"quote")]//span[contains(@class,"text")]/text()'))
scrapling_time = timeit.timeit(scrapling_path, number=20)
lxml_time = timeit.timeit(lxml_path, number=20)
print(f"Scrapling: {scrapling_time:.3f}s")
print(f"lxml: {lxml_time:.3f}s")Info
Angka benchmark sangat bergantung pada hardware, versi library, dan isi halaman. Jangan mengambil angka orang lain sebagai kebenaran mutlak — jalankan benchmark di mesin kalian sendiri sebelum membuat keputusan arsitektur.
Poin yang perlu diingat: Scrapling hampir selalu kalah tipis dari Selectolax dalam parsing murni, tapi menang telak dalam produktivitas karena css, xpath, find_by_text, find_by_regex, dan auto_match ada di satu API yang konsisten. Untuk produktivitas, selisih mikro-detik per parse sering tidak sebanding dengan selisih jam debugging.
Setelah parser, fetcher adalah penentu performa terbesar. Aturan emasnya: gunakan fetcher paling ringan yang masih berhasil. Hanya eskalasi ke lapisan lebih berat ketika halaman benar-benar membutuhkannya.
| Kebutuhan | Fetcher |
|---|---|
| Halaman statis, proteksi ringan | Fetcher |
| Banyak halaman paralel | AsyncFetcher |
| Halaman JavaScript/SPA | PlayWrightFetcher |
| Anti-bot berat, Cloudflare/Turnstile | StealthyFetcher |
Contoh eskalasi yang benar: coba Fetcher dulu. Jika hasilnya kosong atau diblokir, baru naik ke PlayWrightFetcher untuk render JS. Jika proteksinya anti-bot, barulah StealthyFetcher. Urutan ini memastikan mayoritas request kalian berjalan di jalur tercepat.
from scrapling import Fetcher
page = Fetcher().get("https://shop.example.com/products")
if not page.css(".product"):
from scrapling import PlayWrightFetcher
page = PlayWrightFetcher().get("https://shop.example.com/products")
print(len(page.css(".product")))Kesalahan paling umum kedua setelah salah pilih fetcher adalah memparse ulang. Setiap Fetcher().get(url) melakukan request jaringan plus parsing penuh. Jika kalian butuh lima seleksi berbeda dari halaman yang sama, tidak perlu lima request.
from scrapling import Fetcher
page = Fetcher().get("https://quotes.toscrape.com/")
for n in range(5):
titles = page.css(".quote .text::text").getall()
authors = page.css(".quote .author::text").getall()
print(len(titles), len(authors))Kode di atas memakai objek Adaptor yang sama lima kali tanpa request tambahan. Tree lxml di dalamnya sudah terbuild, sehingga setiap seleksi berikutnya hanya traversal di memori — puluhan kali lebih cepat daripada request baru. Simpan Adaptor selama masih dibutuhkan, dan biarkan garbage collector bekerja setelahnya.
Semakin besar HTML yang diproses, semakin lama parsing dan semakin banyak memori. Strategi membatasi payload:
Fetcher daripada memparsing HTML.script, style, dan noscript biasanya tidak relevan untuk ekstraksi teks.body lalu elemen target) mengurangi node yang ditelusuri.Untuk teks bersih, get_all_text bisa dikonfigurasi membuang tag yang tidak perlu sekaligus:
text = page.get_all_text(
separator="\n",
ignore_tags=("script", "style", "noscript"),
)Pada halaman raksasa (misal dokumen SPARQL atau log), pertimbangkan juga mengambil hanya sebagian konten via range request dari server, atau membagi proses menjadi beberapa batch kecil agar memori puncak tetap terkendali.
Polanya sama untuk seleksi: ambil semua dulu, baru proses. Hindari memanggil seleksi dari root berulang-ulang di dalam loop.
for i in range(50):
titles = page.css(".product .product-title::text").getall()cards = page.css(".product")
rows = [
{
"title": card.css(".product-title::text").get(),
"price": card.css(".price::text").get(),
}
for card in cards
]Versi kedua memanggil seleksi root sekali saja, lalu melakukan chaining di dalam scope setiap card — jauh lebih murah karena scope-nya sudah dipersempit. Kombinasikan juga selektor yang sejenis dengan koma (misal h1, h2, h3) dan manfaatkan find_all dengan filter attribute dictionary untuk kondisi yang kompleks. Seleksi batch yang rapi bukan hanya cepat — hasilnya juga langsung jadi dataset terstruktur yang siap diolah.
Episode ini menutup Fase 5 — Advanced Topics, Scaling & Optimization — di sisi performa. Kalian sudah tahu cara membandingkan parser, memilih fetcher paling ringan yang cukup, memakai ulang tree, membatasi payload, dan mengeksekusi seleksi secara batch. Semua ini bekerja bersama di produksi untuk menekan biaya dan mempercepat throughput.
Inti yang harus dibawa pulang:
Adaptor.Di episode 19, kita berbelok ke arah baru yang memanfaatkan model: Integrasi AI & LLM Extraction — scrapling[ai], WebScrapingAI, MCP server untuk AI agent, dan LLM-led parsing dan validation. Sampai jumpa!