Posisi kami: kami adalah Geonode dan kami menjual proxy, yang berfungsi sebagai input untuk proses crawling, bukan untuk pengelolaan daftar. Kenyataannya, daftar crawling yang dikelola dengan buruk jauh lebih mahal daripada paket proxy yang dipilih dengan buruk. Sebuah crawler tanpa normalisasi URL akan mengunjungi halaman yang sama puluhan kali dengan urutan parameter kueri yang berbeda-beda, dan dengan sistem penagihan per gigabyte, Anda harus membayar untuk setiap kunjungan tersebut. Crawler tanpa batasan anggaran akan terus berjalan tanpa henti sesuai kalender dan menagih Anda untuk itu. Memperbaiki daftar itu gratis; memperbaikinya setelah tagihan dikeluarkan tidaklah gratis. Bagian tersebut ada di bawah ini, dan itulah yang sebaiknya dibaca terlebih dahulu.
Apa Sebenarnya yang Dimaksud dengan Daftar Crawl
Tiga hal yang sering disamakan.
Daftar awal — titik awal Anda. Beberapa URL, atau peta situs lengkap.
Frontier — URL yang telah ditemukan dan dimasukkan ke antrian, tetapi belum diambil. Ini adalah struktur data yang aktif dan yang memuat semua keputusan desain di dalamnya.
Kumpulan URL yang telah dikunjungi — URL yang sudah diambil, disimpan agar Anda tidak mengambilnya lagi.
Siklus hidupnya berupa loop: ambil URL dari perbatasan, ambil datanya, ekstrak tautan, normalisasikan, buang yang sudah ada di kumpulan yang sudah dikunjungi atau sudah diantrekan, tambahkan sisanya ke perbatasan, tandai URL tersebut sebagai sudah dikunjungi. Ulangi hingga perbatasan kosong atau anggaran habis.
Secara garis besar, prosesnya sederhana. Namun, setiap langkah memiliki detail yang bisa menghabiskan waktu satu hari jika Anda salah melakukannya.
Seeding: Dari Mana URL Pertama Berasal
Diurutkan berdasarkan seberapa banyak upaya yang dapat dihemat masing-masing.
Sitemap. Sumber awal terbaik yang tersedia, karena merupakan inventaris situs itu sendiri dan mencakup cap waktu lastmod yang menunjukkan perubahan apa saja yang terjadi. Temukan melalui direktif Sitemap: di robots.txt, lalu telusuri berkas indeks sitemap secara rekursif.
Umpan mitra atau API. Jika ada, Anda mungkin tidak perlu melakukan perayapan sama sekali.
Arsip web. API CDX dari Internet Archive mengembalikan URL historis untuk suatu domain, termasuk halaman yang tidak lagi ditautkan dari mana pun. Layanan ini tidak membebankan biaya apa pun kepada target dan menemukan halaman terlantar yang tidak dapat ditemukan melalui perayapan.
Operator pencarian. Kueri site: menampilkan halaman-halaman yang diindeks dan, yang lebih berguna, subdomain yang tidak Anda ketahui sebelumnya.
Halaman kategori dan indeks. Untuk proses perayapan yang ditargetkan, memulai dari halaman daftar spesifik yang Anda minati jauh lebih efisien daripada memulai dari beranda dan berharap-harap cemas.
Beranda. Pilihan terakhir, dan yang biasanya menjadi pilihan pertama orang-orang. Memulai dari satu URL dan menemukan semuanya melalui penelusuran tautan adalah cara paling lambat yang menghasilkan cakupan terburuk.
Kami telah membahas hierarki sumber lengkap dalam cara menemukan semua halaman di sebuah situs web. Versi singkatnya: daftar awal yang baik mengubah proses penelusuran menjadi proses pengambilan data.
Normalisasi URL: Langkah yang Sering Dilewatkan
Elemen rekayasa paling penting dalam sebuah crawler, namun paling sering diabaikan.
Tanpa langkah ini, akan ada lima URL berbeda yang mengarah ke kumpulan halaman yang telah dikunjungi dan satu halaman bagi server:
http://Example.com/Products
http://example.com/products
http://example.com/products/
http://example.com:80/products
http://example.com/products?utm_source=email
RFC 3986 mendefinisikan norma-norma normalisasi yang selalu aman.
Normalisasi huruf besar-kecil. "Skema dan host tidak membedakan huruf besar-kecil, sehingga harus dinormalisasi menjadi huruf kecil. Misalnya, URI <HTTP://www.EXAMPLE.com/>
setara dengan <http://www.example.com/>."
Perhatikan batasan ini: "Komponen sintaksis generik lainnya dianggap membedakan huruf besar-kecil kecuali ditentukan lain secara spesifik oleh skema." Jalur (path) peka huruf besar-kecil — jangan ubah menjadi huruf kecil.
Selain itu: "digit heksadesimal dalam triplet pengkodean persen (misalnya, %3a
versus %3A
) tidak peka huruf besar-kecil dan oleh karena itu harus dinormalisasi untuk menggunakan huruf besar".
Normalisasi pengkodean persen. RFC menyebut hal ini sebagai "sumber variasi yang sering terjadi di antara URI yang sebetulnya identik", karena "beberapa pembuat URI mengenkode dengan persen oktet yang tidak memerlukan pengkodean persen". Oktet-oktet ini "harus dinormalisasi dengan mendekode setiap oktet yang dikodekan dengan persen yang sesuai dengan karakter yang tidak dicadangkan".
Normalisasi segmen jalur. Hapus segmen .
dan ..
dengan menerapkan algoritma remove_dot_segments
, karena "beberapa implementasi yang telah diterapkan secara keliru mengasumsikan bahwa resolusi referensi tidak diperlukan ketika referensi tersebut sudah berupa URI".
Normalisasi berbasis skema. RFC memberikan contoh kanonik — keempat ini setara:
http://example.com
http://example.com/
http://example.com:/
http://example.com:80/
Jadi, jalur kosong “harus dinormalisasi menjadi jalur /
”, dan port default atau kosong “harus dihapus melalui normalisasi berbasis skema”.
Di luar spesifikasi, tiga normalisasi berikut bersifat pragmatis daripada sepenuhnya aman, dan perlu diterapkan dengan hati-hati:
Hapus parameter pelacakan. utm_*
, fbclid
, gclid
, pengidentifikasi sesi. Parameter-parameter ini hampir tidak pernah mengubah konten dan sangat memperbanyak jumlah URL Anda. Buatlah daftar daripada menebak-nebak.
Urutkan parameter kueri yang tersisa. ?a=1&b=2
dan ?b=2&a=1
biasanya mengarah ke halaman yang sama. Biasanya — beberapa aplikasi sensitif terhadap urutan, jadi lakukan pengujian pada sampel.
Hapus fragmen. #section
adalah bagian klien dan tidak pernah sampai ke server. Selalu aman untuk diabaikan dalam proses perayapan.
**Dan patuhi rel="canonical"
.** Jika sebuah halaman menyatakan URL kanonik, itu berarti situs tersebut memberi tahu Anda mana di antara beberapa alamat yang merupakan alamat yang sebenarnya. Mematuhinya berarti deduplikasi gratis dengan otoritas situs itu sendiri di belakangnya.
The Frontier: Desain Antrian
Tiga sifat menentukan apakah crawler Anda dapat diskalakan.
Proses deduplikasi harus efisien. Sebelum menambahkan sebuah URL, Anda memeriksa apakah URL tersebut sudah terdaftar. Pada satu juta URL, pemindaian linier tidak praktis. Hash set bisa berfungsi sampai batas tertentu; di luar itu, filter Bloom memberi Anda keanggotaan waktu konstan dengan memori yang lebih sedikit, serta tingkat false-positive yang rendah — artinya Anda kadang-kadang melewatkan URL yang belum pernah Anda lihat. Untuk sebagian besar proses crawling, pertukaran ini tidak menjadi masalah; jika kelengkapan menjadi hal yang penting, dukung filter tersebut dengan penyimpanan yang tepat.
Urutan harus dapat dikendalikan. Antrian FIFO biasa memberikan penelusuran breadth-first, yang biasanya sesuai dengan yang Anda inginkan — penelusuran ini mencapai cakupan luas sejak awal dan tetap dangkal. Tumpukan LIFO memberikan penelusuran depth-first, yang menjangkau jauh ke dalam satu cabang dan jarang berguna untuk penelusuran situs. Antrian prioritas memungkinkan Anda mengurutkan berdasarkan apa pun yang Anda inginkan, yang akan dibahas selanjutnya.
Status per-host harus dilacak. Dalam praktiknya, batas depan bukanlah satu antrian; melainkan antrian per host, sehingga batasan kesopanan berlaku secara independen untuk masing-masing. Satu antrian global dengan batasan laju global berarti satu situs besar akan menghalangi situs lainnya.
Struktur yang dapat diskalakan adalah sekumpulan antrian per-host ditambah penjadwal yang memilih host berikutnya yang memenuhi syarat untuk diambil datanya — memenuhi syarat berarti telah berlalu cukup waktu sejak permintaan terakhir kepadanya.
Penentuan Prioritas
URL mana yang harus diambil berikutnya, ketika Anda tidak dapat mengambil semuanya.
Berdasarkan kedalaman. Halaman yang lebih dangkal biasanya lebih penting. Ini adalah pengaturan default yang sederhana dan efektif.
Berdasarkan pola jalur (path pattern). Jika Anda ingin mengambil halaman produk, prioritaskan URL yang sesuai dengan /product/. Ini adalah heuristik dengan hasil terbaik untuk perayapan yang ditargetkan dan biayanya sangat murah.
Berdasarkan riwayat perubahan (lastmod). Dari peta situs (sitemap). Ambil yang telah berubah.
Berdasarkan riwayat perubahan. Untuk perayapan berulang, halaman yang sering berubah sebelumnya cenderung akan sering berubah lagi.
Berdasarkan jumlah tautan masuk. Halaman yang ditautkan dari banyak tempat biasanya lebih signifikan. Perhitungannya memakan sumber daya selama perayapan, namun sepadan untuk pekerjaan berskala besar.
Berdasarkan nilai perkiraan. Apa pun tujuan Anda sebenarnya. Jika Anda menginginkan harga, prioritaskan halaman yang kemungkinan besar memuatnya.
Pengaturan praktisnya adalah skor bilangan bulat kecil yang dihitung pada saat masuk antrian berdasarkan beberapa sinyal ini, yang digunakan sebagai kunci dalam antrian prioritas. Skema yang rumit jarang sebanding dengan kompleksitasnya; kedalaman ditambah bonus pola jalur sudah cukup untuk memenuhi sebagian besar kebutuhan.
Batasan: Anggaran dan Perangkap
Tanpa batasan, beberapa proses crawling tidak akan berhenti. Ini bukanlah kasus khusus.
Kedalaman maksimum. Tautan dari tautan dari tautan. Batas kedalaman lima atau enam mencakup hampir semua struktur situs yang sebenarnya.
Jumlah halaman maksimum per host. Angka pasti. Saat batas ini tercapai, hentikan dan laporkan, jangan dilanjutkan.
Jumlah halaman total maksimum. Untuk seluruh pekerjaan.
Bandwidth maksimum. Terutama pada lalu lintas proxy berbayar, di mana penjelajahan tanpa batas berarti tagihan yang tak terbatas.
Pengecualian pola. Kalender adalah contoh klasik ruang tak terbatas — tautan next month
akan terus menghasilkan URL tanpa henti. Navigasi berfacet pada katalog besar menghasilkan ledakan kombinatorial. Kecualikan hal-hal ini berdasarkan pola:
/calendar/
/?filter=
/*?sort=
Deteksi konten duplikat. Lakukan hash pada isi halaman. Jika seratus URL mengembalikan konten yang identik, Anda telah menemukan ruang yang dihasilkan secara otomatis, bukan seratus halaman.
Dan pantau rasio penemuan. Peringatan tunggal yang paling berguna: lacak URL baru yang ditemukan per halaman yang diambil. Pada situs dengan ukuran terbatas, angka ini akan terus menurun hingga mendekati nol. Jika tetap stabil atau meningkat, berarti ada sesuatu yang menghasilkan URL lebih cepat daripada yang dapat Anda proses — jebakan, kalender, atau ledakan navigasi berfacet. Kami telah membahas versi yang disengaja dalam jebakan honeypot.
Penjadwalan Pengindeksan Ulang
Untuk apa pun yang dijalankan lebih dari sekali, daftar tersebut menjadi jadwal.
Kelompokkan berdasarkan tingkat volatilitas. Halaman yang berubah setiap jam memerlukan pemeriksaan setiap jam; sedangkan halaman yang hanya berubah sekali dalam setahun tidak memerlukannya. Mengindeks ulang semuanya dengan frekuensi yang dibutuhkan oleh item paling volatil adalah cara paling umum yang menyebabkan pemborosan anggaran.
Gunakan permintaan bersyarat. If-Modified-Since dan If-None-Match mengubah proses pengindeksan ulang menjadi serangkaian respons 304 Not Modified yang masing-masing hanya memakan beberapa ratus byte. Pada pengindeksan ulang di mana sebagian besar halaman tidak berubah, hal ini mengurangi tagihan Anda dan beban server target hingga satu orde besar.
Sesuaikan berdasarkan pengamatan. Jika sebuah halaman tidak berubah dalam sepuluh kali pemeriksaan, periksalah lebih jarang. Jika berubah dalam tiga pemeriksaan terakhir, periksalah lebih sering. Strategi back-off perkalian sederhana sudah cukup.
Deteksi penghapusan secara eksplisit. Halaman bisa menghilang, dan situs jarang mengumumkannya. Pantau kode status 404 atau terapkan kebijakan — URL yang tidak terdeteksi dalam tiga proses crawling berturut-turut ditandai sebagai tidak aktif. Tanpa ini, dataset Anda akan dipenuhi entri yang sudah tidak ada lagi, yang merusak kepercayaan lebih cepat daripada entri yang hilang.
Penyimpanan dan Skalabilitas
Catatan mengenai kapan pendekatan in-memory tidak lagi berfungsi.
Hingga sekitar seratus ribu URL, set dan daftar Python masih memadai. Jangan membuat sistem yang terlalu rumit.
Hingga beberapa juta, gunakan basis data lokal — SQLite berfungsi dengan baik — dengan indeks pada hash URL dan status pengambilan. Persistensi juga berarti proses perayapan yang terhenti akibat crash dapat dilanjutkan alih-alih dimulai ulang, yang lebih penting daripada kinerja.
Di atas itu, gunakan antrian yang tepat dan penyimpanan kunci-nilai, dengan batas yang dipartisi berdasarkan host sehingga pekerja dapat ditugaskan ke seluruh host dan etika tetap terjaga tanpa perlu koordinasi.
Dua keputusan desain yang bermanfaat di setiap skala:
Simpan hash URL, bukan hanya URL-nya. Perbandingan dan indeks pada hash berpanjang tetap lebih efisien dan penyimpanannya lebih kecil.
Simpan respons mentah, bukan hanya hasil yang telah diparsing. Ketika parser mengalami kegagalan — dan hal itu pasti terjadi — mem-parsing ulang apa yang Anda miliki tidak memerlukan biaya, sedangkan mengambil ulang data akan menghabiskan bandwidth dan mengorbankan kepercayaan pengguna.
Implementasi Minimal
Seluruh kode tersebut terdiri dari sekitar empat puluh baris, untuk memperjelas bagian-bagian yang bergerak.
import time
from collections import deque, defaultdict
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
TRACKING = {"utm_source", "utm_medium", "utm_campaign", "fbclid", "gclid"}
def normalise(url):
p = urlsplit(url)
host = p.hostname or ""
port = "" if p.port in (None, 80, 443) else f":{p.port}"
query = urlencode(sorted(
(k, v) for k, v in parse_qsl(p.query, keep_blank_values=True)
if k.lower() not in TRACKING
))
return urlunsplit((p.scheme.lower(), host + port, p.path or "/", query, ""))
class Frontier:
def __init__(self, delay=1.5, max_per_host=5000):
self.queues = defaultdict(deque)
self.seen = set()
self.next_ok = defaultdict(float)
self.counts = defaultdict(int)
self.delay, self.max_per_host = delay, max_per_host
def add(self, url, depth=0):
url = normalise(url)
if url in self.seen or depth > 5:
return False
host = urlsplit(url).hostname
if self.counts[host] >= self.max_per_host:
return False
self.seen.add(url)
self.counts[host] += 1
self.queues[host].append((url, depth))
return True
def next(self):
now = time.monotonic()
for host, q in self.queues.items():
if q and self.next_ok[host] <= now:
self.next_ok[host] = now + self.delay
return q.popleft()
return None
Terdapat lima keputusan desain yang terlihat di sana, dan masing-masing sesuai dengan bagian di atas.
Normalisasi dilakukan saat add(), bukan saat proses pengambilan data. Penghapusan duplikat hanya akan akurat jika bentuk kanoniklah yang dimasukkan ke dalam himpunan yang telah dikunjungi; oleh karena itu, melakukan normalisasi setelahnya berarti Anda telah menyimpan duplikat.
Himpunan yang telah dikunjungi diisi saat dimasukkan ke antrian (enqueue), bukan saat proses selesai. Jika tidak, sebuah URL yang ditemukan di dua puluh halaman akan dimasukkan ke antrian sebanyak dua puluh kali sebelum pengambilan pertama selesai.
Antrian bersifat per host dan kebijakan "politeness" juga berlaku per host. next_ok mencatat kapan setiap host dapat dihubungi berikutnya, sehingga satu situs besar tidak dapat menghabiskan sumber daya yang seharusnya untuk situs lain, dan penundaan diterapkan di tempat yang seharusnya.
Batasan anggaran diterapkan pada titik masuk. Batasan kedalaman dan per-host akan menolak URL sebelum menghabiskan memori, yang merupakan perbedaan antara perayapan yang terkendali dan perayapan yang menemukan batasnya dengan menghabiskan RAM.
next() mengembalikan None alih-alih memblokir. Hal ini memberi kebebasan kepada pemanggil untuk memutuskan apakah akan menunggu, melakukan pekerjaan lain, atau menyelesaikan proses — penjadwal yang "tidur" di dalam struktur data adalah sesuatu yang tidak dapat Anda pantau.
Yang sengaja tidak disertakan di sini adalah persistensi, dan itulah hal pertama yang harus ditambahkan untuk penerapan yang nyata. Proses perayapan yang crash dan harus dimulai ulang dari daftar seed tidak hanya kehilangan waktu; proses tersebut juga harus mengambil ulang semua data, yang menghabiskan bandwidth dan merusak reputasi.
Memantau Daftar
Apa yang perlu diukur, karena proses perayapan yang hanya melaporkan "halaman yang diambil" hampir tidak memberikan informasi apa pun.
Ukuran batas (frontier) dari waktu ke waktu. Seharusnya menurun mendekati nol. Kenaikan berarti penemuan yang tak terbatas.
Rasio penemuan. Jumlah URL baru per halaman yang diambil, seperti dijelaskan di atas.
Hasil pengambilan berdasarkan status, per host. Angka agregat menyembunyikan kegagalan total pada satu host.
Tingkat duplikat. Berapa banyak URL yang ditemukan yang sebenarnya sudah diketahui. Tingkat yang tinggi setelah normalisasi berarti normalisasi Anda melewatkan sesuatu.
Byte per halaman berguna. Angka yang menghubungkan proses crawling dengan tagihan, dan yang mengungkapkan bahwa browser headless mengambil megabyte gambar yang tidak Anda butuhkan.
Pemeriksaan konten. Apakah halaman-halaman tersebut mengandung penanda yang Anda harapkan. Crawler yang melaporkan keberhasilan 100% namun mengembalikan halaman yang diblokir secara lunak merupakan kegagalan yang mahal, dan hanya pemeriksaan konten yang dapat mendeteksinya.
Pertanyaan Terkait
Apa itu daftar perayapan?
Kumpulan URL yang akan dikunjungi oleh crawler, biasanya terdiri dari daftar awal (seed list), batas (frontier) URL yang telah ditemukan tetapi belum diambil, dan kumpulan URL yang telah dikunjungi. Pengelolaan batas — urutan, penghapusan duplikat, dan alokasi sumber daya — merupakan faktor utama yang menentukan apakah proses crawling akan selesai.
Bagaimana cara menormalkan URL untuk crawling?
Ubah skema dan host menjadi huruf kecil tetapi jangan ubah jalurnya, ubah digit heksadesimal yang dikodekan dengan tanda persen menjadi huruf besar, dekodekan pengkodean persen yang tidak diperlukan, hapus segmen titik, hapus port default, normalisasikan jalur kosong menjadi /, hapus fragmen, dan hapus parameter pelacakan yang diketahui. RFC 3986 mendefinisikan semuanya kecuali dua yang terakhir.
Apa itu batas perayapan (crawl frontier)?
Antrean URL yang telah ditemukan namun belum diambil. Dalam praktiknya, ini adalah kumpulan antrean per-host ditambah penjadwal yang memilih host berikutnya yang memenuhi syarat sesuai batas kesopanan, karena antrean global tunggal dapat membuat satu situs besar menghabiskan semua sumber daya sehingga situs lain tidak mendapat giliran.
Bagaimana cara menghentikan crawler agar tidak berjalan selamanya?
Tetapkan batasan ketat: kedalaman maksimum, jumlah halaman maksimum per host dan secara keseluruhan, serta batas bandwidth. Tambahkan pengecualian pola untuk kalender dan navigasi berfacet, serta berikan peringatan ketika rasio URL yang baru ditemukan terhadap halaman yang telah diambil berhenti menurun.
Bagaimana cara menghindari merayapi halaman yang sama dua kali?
Normalisasikan URL sebelum deduplikasi, karena halaman yang sama memiliki banyak alamat yang valid. Kemudian periksa keanggotaan dalam himpunan yang telah dikunjungi — himpunan hash pada skala kecil, filter Bloom, atau basis data pada skala besar. Hormati atribut rel="canonical" jika halaman menyatakan hal tersebut.
Seberapa sering saya harus melakukan crawling ulang?
Sesedikit mungkin sesuai dengan kebutuhan Anda, disesuaikan berdasarkan seberapa sering setiap halaman benar-benar berubah. Gunakan "lastmod" dari peta situs dan permintaan bersyarat sehingga halaman yang tidak berubah hanya memerlukan "304" alih-alih pengambilan penuh. Melakukan crawling terhadap semua halaman dengan frekuensi yang sama seperti halaman yang sering berubah adalah sumber pemborosan yang paling umum.
Apa itu Bloom filter dan apakah saya membutuhkannya?
Sebuah himpunan probabilistik yang menentukan keanggotaan dalam waktu konstan dengan menggunakan memori yang sangat sedikit, dengan kemungkinan kecil terjadinya false positive — artinya Anda sesekali melewatkan URL yang sebenarnya belum pernah Anda lihat. Layak digunakan jika jumlah URL melebihi beberapa juta; tidak diperlukan di bawah jumlah tersebut, di mana himpunan biasa lebih sederhana dan akurat.
Bagaimana cara mendeteksi bahwa halaman telah dihapus?
Perhatikan kode status 404, dan terapkan kebijakan untuk halaman yang tiba-tiba tidak muncul lagi — misalnya, tandai URL sebagai tidak aktif jika URL tersebut tidak terdeteksi dalam tiga proses crawling berturut-turut. Tanpa ini, dataset akan menumpuk entri yang sudah tidak ada lagi, yang merusak kepercayaan lebih cepat daripada adanya celah data.
Kesimpulan
Proses crawling sebagian besar bersifat administratif. Pengambilan data sudah menjadi masalah yang terpecahkan berkat pustaka-pustaka; menentukan apa yang harus diambil, mengenali bahwa data tersebut sudah pernah diambil, dan mengetahui kapan harus berhenti—itulah inti dari aspek tekniknya.
Tiga hal yang layak mendapat perhatian lebih besar daripada tingkat kesulitannya. Normalisasi, karena tanpa itu Anda akan mengunjungi halaman yang sama melalui belasan alamat berbeda dan membayar untuk masing-masingnya — dan RFC 3986 menjelaskan dengan tepat transformasi mana yang aman. Anggaran, karena beberapa ruang URL benar-benar tak terbatas dan crawler tanpa batasan yang tegas akan menemui batas tersebut. Dan rasio penemuan, karena angka tunggal ini mengungkap jebakan, masalah penjadwalan, atau ledakan data berfacet jauh sebelum tagihan datang.
Kemudian, lakukan penyemaian dengan baik. Peta situs mengubah proses perayapan menjadi pengambilan data yang berubah, sementara kueri arsip menampilkan halaman-halaman yang tidak pernah dapat dijangkau oleh penelusuran tautan, dan keduanya tidak membebani target apa pun. Memulai dari beranda dan hanya berharap adalah rute paling lambat menuju hasil yang paling tidak lengkap, namun hal ini masih menjadi pengaturan default.
