İnternetteki veriler dağınık bir kütüphane gibidir: ürün fiyatları bir rafta, haber başlıkları başka rafta, açık veri tabloları ise bodrumda durur. Go ile yazılan bir web scraper, bu kütüphanede patenle gezen disiplinli bir arşivciye benzer. Hızlı HTTP istemcisi, goroutine yapısı ve channel mantığı sayesinde binlerce sayfayı kısa sürede ziyaret edip veriyi indeksleyebiliriz. `` Web scraping temelde üç aşamadan oluşur: sayfayı indir, içeriği ayrıştır, anlamlı veriyi kaydet. Basit görünür ama işin teorik tarafında ağ gecikmesi, eşzamanlılık, hata toleransı ve etik sınırlar vardır. Bir isteğin toplam süresi yaklaşık olarak şöyle düşünülebilir: $T = T_{dns} + T_{tcp} + T_{tls} + T_{server} + T_{download}$. Tek tek çalışan bir tarayıcıda toplam süre $T_{seq} = n \times T$ olur. Eşzamanlı çalışan bir sistemde ise ideal durumda $T_{par} \approx \frac{n \times T}{w}$; burada $w$ worker sayısıdır. Elbette gerçek dünyada bant genişliği, hedef sunucu limitleri ve CPU bu denkleme biraz mizah katar.
Go bu iş için çok uygundur çünkü goroutine’ler hafiftir. Her URL için dev bir işletim sistemi thread’i açmak yerine, Go runtime binlerce işi verimli biçimde planlar. Channel’lar ise URL kuyruğu, sonuç akışı ve hata bildirimleri için temiz bir iletişim modeli sağlar.
| Yaklaşım | Avantaj | Dezavantaj | Uygun Senaryo |
|---|---|---|---|
| Sıralı istek | Basit, hata ayıklaması kolay | Çok yavaş | 10-20 sayfalık küçük işler |
| Sınırsız goroutine | İlk bakışta çok hızlı | Sunucuyu boğabilir, rate limit yer | Kontrollü olmayan deneyler, önerilmez |
| Worker pool | Dengeli, yönetilebilir | Biraz daha fazla kod | Gerçek projeler |
| Kuyruk + retry | Dayanıklı | Mimari karmaşıklık artar | Büyük ölçekli indeksleme |
Aşağıdaki örnekte kontrollü bir worker pool kuruyoruz. Amaç, URL listesini channel üzerinden worker’lara dağıtmak ve her sayfanın HTML uzunluğunu örnek olarak toplamaktır. Gerçek projede burada başlık, meta açıklama, ürün fiyatı veya linkler ayrıştırılabilir.
package main
import (
"fmt"
"io"
"net/http"
"sync"
"time"
)
type Result struct {
URL string
Length int
Err error
}
func fetch(client *http.Client, url string) Result {
resp, err := client.Get(url)
if err != nil {
return Result{URL: url, Err: err}
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return Result{URL: url, Err: err}
}
return Result{URL: url, Length: len(body)}
}
func worker(id int, client *http.Client, jobs <-chan string, results chan<- Result, wg *sync.WaitGroup) {
defer wg.Done()
for url := range jobs {
fmt.Println("Worker", id, "işliyor:", url)
results <- fetch(client, url)
}
}
func main() {
urls := []string{
"https://example.com",
"https://go.dev",
"https://pkg.go.dev",
}
client := &http.Client{Timeout: 8 * time.Second}
jobs := make(chan string)
results := make(chan Result)
var wg sync.WaitGroup
workerCount := 3
for i := 1; i <= workerCount; i++ {
wg.Add(1)
go worker(i, client, jobs, results, &wg)
}
go func() {
for _, url := range urls {
jobs <- url
}
close(jobs)
wg.Wait()
close(results)
}()
for r := range results {
if r.Err != nil {
fmt.Println("Hata:", r.URL, r.Err)
continue
}
fmt.Println(r.URL, "boyut:", r.Length)
}
}
Kodda http.Client için timeout vermek küçük ama hayati bir ayrıntıdır. Timeout yoksa bir istek sonsuza kadar asılı kalabilir ve scraper’ınız kahve molasından dönmeyen stajyer gibi ortadan kaybolur. workerCount değeri ise hız ile nezaket arasındaki dengedir. Çok düşük olursa yavaş kalırsınız, çok yüksek olursa hedef site sizi engelleyebilir.
HTML ayrıştırmak için standart kütüphane yeterli olmayabilir. Go ekosisteminde goquery, jQuery benzeri seçicilerle pratik bir çözüm sunar. Örneğin h1, .price, a[href] gibi seçicilerle veri çekilebilir. Fakat dinamik olarak JavaScript ile oluşan sayfalarda klasik HTTP isteği yetmez; bu durumda tarayıcı otomasyonu veya API keşfi gerekir.
| Konu | Dikkat Edilecek Nokta | İyi Uygulama |
|---|---|---|
| Robots.txt | Site taramaya izin vermeyebilir | Önce kontrol et |
| Rate limit | Çok sık istek engellenir | Gecikme ve worker limiti kullan |
| User-Agent | Varsayılan istemci şüpheli görünebilir | Açıklayıcı User-Agent gönder |
| Retry | Geçici ağ hataları olabilir | Üstel geri çekilme uygula |
| Veri temizliği | HTML gürültülüdür | Normalize et ve doğrula |
Sonuç olarak Go ile scraper yazmak sadece hızlı istek göndermek değildir; küçük bir dağıtık sistem tasarlamaktır. Kuyruklar, worker’lar, timeout’lar ve hata yönetimi bir araya geldiğinde saniyeler içinde binlerce sayfayı gezebilen sağlam bir indeksleyici ortaya çıkar. Hız güzel, ama kontrollü hız daha güzeldir.
Yorumlar