Scraping'e yeni başlayan hemen herkes aynı anı yaşar: tarayıcıda ürünler, fiyatlar ve yorumlar gözünüzün önündedir; aynı adresi Python'da requests ile çektiğinizde ise seçiciler hiçbir şey bulamaz. Sayfanın HTML'ine baktığınızda ürün listesi yerine bir "Yükleniyor..." yazısı ve birkaç <script> etiketi görürsünüz. Sorun kodunuzda değildir; sayfa dinamiktir ve içeriği tarayıcıda çalışan JavaScript sonradan getirir.
Bu yazıda statik ve dinamik sayfaların farkını, bir sayfanın dinamik olup olmadığını birkaç dakikada nasıl anlayacağınızı ve headless tarayıcının ne olduğunu anlatıyoruz. Asıl üzerinde durduğumuz konu ise çoğu durumda headless tarayıcıya hiç gerek kalmadan veriye ulaşmanın yolları: sayfanın arka planda çağırdığı API isteğini bulmak ve HTML'e gömülü JSON'u okumak. Tarayıcı gerçekten gerektiğinde Playwright ile doğru bekleme stratejilerini ve maliyeti düşürme yollarını da gösteriyoruz. Örnekleri, içeriği JavaScript ile yükleyen yerel bir test sayfasında çalıştırdık.
Statik sayfa nedir?
Statik sayfada, tarayıcının gösterdiği içeriğin tamamı sunucunun ilk yanıtındaki HTML'de bulunur. Sunucu bu HTML'i önceden hazırlanmış bir dosyadan okuyabilir ya da her istekte bir veritabanından üretebilir; scraping açısından önemli olan, içeriğin tarayıcıya ulaştığı anda HTML'in içinde olmasıdır.
Statik bir sayfadan veri almak için tarayıcıya gerek yoktur:
import requests
from bs4 import BeautifulSoup
html = requests.get("https://ornek.com/kampanyalar", timeout=20).text
kartlar = BeautifulSoup(html, "html.parser").select("div.urun")
print("Bulunan ürün kartı:", len(kartlar))Haber siteleri, bloglar, birçok kurumsal site ve sunucu tarafında çizilen (server-side rendering) e-ticaret sayfaları bu gruptadır.
Dinamik (JavaScript ile yüklenen) sayfa nedir?
Dinamik sayfada sunucunun ilk yanıtı bir iskelettir: sayfa düzeni, boş bir liste kapsayıcısı ve JavaScript dosyaları. İçerik şu adımlarla sonradan gelir:
- Tarayıcı HTML iskeletini alır ve ekrana "Yükleniyor..." gibi bir yer tutucu çizer.
- JavaScript dosyaları indirilir ve çalıştırılır. Çoğu zaman React, Vue veya benzeri bir çerçeve.
- JavaScript bir API isteği gönderir. Örneğin
/api/urunler?kategori=kulaklik&sayfa=1adresinefetchile. - Sunucu veriyi JSON olarak döner. Ürün adları, fiyatlar, stok bilgisi.
- JavaScript bu JSON'dan HTML üretip sayfaya yerleştirir. Ürün kartları ancak bu adımda görünür.
- Kaydırma veya tıklamayla yeni istekler gelir. Sonsuz kaydırma, "daha fazla göster" butonu, filtreler.
Python'daki requests gibi bir HTTP istemcisi yalnızca birinci adımı yapar; JavaScript çalıştırmaz. Bu yüzden aldığı HTML'de ürün kartları yoktur. Yerel test sayfamızda yukarıdaki requests kodu sıfır kart buldu; aynı sayfa tarayıcıda iki ürünü gösteriyordu.
Arada bir de karma yapı vardır: Next.js ve Nuxt gibi çerçeveler sayfanın ilk hâlini sunucuda üretir ve aynı veriyi JavaScript'in kullanması için HTML'in içine bir JSON bloğu olarak da gömer. Bu tür sayfalarda veri, JavaScript çalışmadan HTML'de bulunur ama bazen görünen kartlarda değil, bir <script> etiketinin içindedir.
Bir sayfanın dinamik olduğu nasıl anlaşılır?
Headless tarayıcı kurmadan önce birkaç dakikalık bir teşhis, doğru yolu seçmenizi sağlar:
- Kaynağı görüntüleyin. Sayfada
Ctrl + U(macOS'taCmd + Option + U) sunucunun gönderdiği ham HTML'i gösterir. Sayfada gördüğünüz bir ürün adını veya fiyatı burada arayın. Bulursanız sayfa büyük olasılıkla statiktir. - Elements paneliyle karşılaştırın. Geliştirici araçlarındaki Elements paneli JavaScript çalıştıktan sonraki sayfayı gösterir. Metin Elements'te var, kaynakta yoksa içerik JavaScript ile gelmiştir.
- JavaScript'i kapatıp yeniden yükleyin. Chrome geliştirici araçlarında
Ctrl + Shift + Pile komut menüsünü açıp "Disable JavaScript" yazın ve sayfayı yenileyin. İçerik kayboluyorsa sayfa dinamiktir. - Kodla kontrol edin.
requestsile aldığınız HTML'de beklediğiniz bir metni arayın. Yoksa ve HTML çok kısaysa iskelet almışsınızdır. - Network panelinde Fetch/XHR filtresine bakın. Sayfayı yenileyip bu filtrede JSON yanıtları görüyorsanız, veri büyük olasılıkla bu isteklerden birindedir.
Dördüncü adımın bir tuzağı vardır: HTML'de içerik yoksa bunun nedeni her zaman JavaScript değildir. Site size bir doğrulama sayfası veya farklı bir içerik döndürmüş olabilir. Yanıtın durum kodunu ve içeriğini kontrol edin; bu teşhisi Web Scraping'de Engellenmeden Veri Toplama Yöntemleri yazımızda ayrıntılı anlattık.
Headless tarayıcı nedir?
Headless tarayıcı, ekranda pencere açmadan çalışan gerçek bir tarayıcıdır. Chromium, Firefox veya WebKit motoru sayfayı normal bir tarayıcı gibi yükler, JavaScript'i çalıştırır, API isteklerini gönderir ve sayfayı oluşturur; siz de bu sayfayı kodla okursunuz. Yaygın araçlar Playwright, Puppeteer ve Selenium'dur.
Headless tarayıcının yetenekleri bir HTTP istemcisinden fazladır, ama maliyeti de öyledir:
- İşlemci ve bellek. Her tarayıcı sekmesi, bir HTTP isteğinin kat kat fazlası kaynak kullanır. Aynı makinede çalıştırabileceğiniz eşzamanlı sayfa sayısı ağla değil, çekirdek ve bellekle sınırlanır.
- Süre. Sayfanın bütün JavaScript'inin indirilip çalışması saniyeler alabilir.
- İstek sayısı ve bant genişliği. Tek bir sayfa görseller, yazı tipleri, stil dosyaları, analiz betikleri ve reklamlarla onlarca ek istek üretir. Bu hem sizin trafiğinizi hem hedef sitenin yükünü artırır.
- Bakım. Tarayıcı sürümleri, sürücüler ve bekleme mantığı ek karmaşıklık getirir.
Bu yüzden headless tarayıcı ilk tercih değil, son çare olmalıdır. Farklı araçların karşılaştırması için Web Kazıma: JavaScript mi Python mu? yazımıza, Selenium kurulumu için Selenium ile Proxy Kullanımı yazımıza bakabilirsiniz.
Önce API/XHR isteğini bulmak
Dinamik bir sayfadaki veri, tarayıcıya bir yerden JSON olarak gelir. O isteği bulursanız sayfayı hiç çizmeden veriyi doğrudan alabilirsiniz. Chrome'un Network paneli başvuru belgesi filtreleri ve kopyalama seçeneklerini ayrıntılı anlatır; kısa yol şöyledir:
- Geliştirici araçlarını açın (F12) ve Network sekmesine geçin.
- Filtre çubuğunda Fetch/XHR seçeneğini işaretleyin.
- Sayfayı yenileyin veya veriyi yükleyen eylemi yapın (sayfayı kaydırın, filtre seçin, sonraki sayfaya geçin).
- Listede adı
api,graphql,search,productsgibi ifadeler içeren isteklere tıklayın ve Response sekmesinde JSON'a bakın. - Aradığınız veriyi içeren isteği bulduğunuzda Headers sekmesinden adresi, parametreleri ve gerekli başlıkları not edin. İsteğe sağ tıklayıp Copy > Copy as cURL seçeneğiyle komut satırında deneyebilirsiniz.
Bulduğunuz isteği Python'da tekrarlamak genellikle birkaç satırdır:
import requests
session = requests.Session()
session.headers.update({
"User-Agent": "OrnekFiyatBotu/1.0 (+https://ornek.com/bot-hakkinda)",
"Accept": "application/json",
})
yanit = session.get(
"https://ornek.com/api/urunler",
params={"kategori": "kulaklik", "sayfa": 1},
timeout=20,
)
yanit.raise_for_status()
for urun in yanit.json()["urunler"]:
print(urun["ad"], urun["fiyat"])Bu yolun avantajları büyüktür: veri yapılandırılmış gelir, HTML seçicilerine gerek kalmaz, sayfa tasarımı değiştiğinde kodunuz bozulmaz ve tek bir istek bütün bir sayfanın yükünün küçük bir kısmıdır. Sayfalama çoğu zaman sayfa, offset veya cursor parametresiyle yapılır.
Dikkat edilmesi gerekenler:
- Kimlik doğrulama ve belirteçler. Bazı API istekleri çerez, oturum belirteci veya kısa ömürlü imzalar ister. Bunları elle kopyalamak kısa süre çalışır, sonra bozulur.
- Site şartları. Sitenin kendi ön yüzü için kullandığı iç API, herkese açık bir API değildir. Sitenin kullanım şartlarını ve robots.txt kurallarını bu isteklere de uygulayın; resmi bir API sunuluyorsa onu tercih edin. Hukuki çerçeve için Web Scraping Yasal mı? yazımıza bakabilirsiniz.
- Hız. API istekleri hafif olduğu için çok hızlı gönderilebilir; bu, sitenin sınırlarını daha kolay aşmanız anlamına da gelir. Aynı hız kurallarını uygulayın.
HTML'e gömülü JSON'u okumak
Next.js, Nuxt ve benzeri çerçevelerle yapılmış sitelerde veri çoğu zaman HTML'in içinde bir <script> etiketinde hazır bekler. Kaynağı görüntüleyip __NEXT_DATA__, __NUXT__, __INITIAL_STATE__ veya application/ld+json ifadelerini arayın.
import json
import requests
from bs4 import BeautifulSoup
html = requests.get("https://ornek.com/kampanyalar", timeout=20).text
soup = BeautifulSoup(html, "html.parser")
veri = json.loads(soup.select_one("script#__NEXT_DATA__").string)
for urun in veri["props"]["pageProps"]["urunler"]:
print(urun["ad"], urun["fiyat"])application/ld+json blokları ise schema.org biçiminde ürün adı, fiyat, stok ve puan gibi yapılandırılmış veriyi taşır ve arama motorları için hazırlandıkları için genellikle kararlıdır. Seçicilerin tasarım değişikliğine dayanıklılığı konusunu CSS Selector ve XPath yazımızda ele aldık.
Playwright ile bekleme stratejileri
Veri ne API'de ne gömülü JSON'da bulunamıyorsa, ya da içeriğe ulaşmak için tıklama, kaydırma ve form doldurma gibi etkileşimler gerekiyorsa headless tarayıcı kullanılır. Bu durumda en sık hata ne zaman okunacağıdır: sayfa açıldığı anda okursanız içerik henüz gelmemiştir; sabit bir süre beklerseniz ya gereksiz zaman kaybedersiniz ya da yavaş bir yanıtta yine boş sonuç alırsınız.
Playwright'ın doğru bekleme araçları şunlardır:
- Locator'ların otomatik beklemesi.
page.locator("div.urun h2").inner_text()gibi işlemler, öğe sayfada belirene kadar varsayılan zaman aşımına kadar bekler. Playwright'ın eylem öncesi kontroller belgesi hangi durumların beklendiğini listeler. - Belirli bir öğeyi beklemek.
page.locator("div.urun").first.wait_for()ilk ürün kartı görünene kadar bekler. - Belirli bir yanıtı beklemek.
page.expect_response(...)sayfanın veri isteğinin dönmesini bekler ve JSON'a doğrudan erişmenizi sağlar. - Sabit bekleme kullanmamak.
page.wait_for_timeout(5000)testlerde işe yarar, üretimde güvenilmezdir.
Playwright belgeleri networkidle yükleme durumunun, yani ağ trafiğinin bir süre durmasını beklemenin, önerilmediğini belirtir; analiz ve reklam betikleri sürekli istek gönderen sayfalarda bu durum hiç gelmeyebilir.
Aşağıdaki örnek, bir proxy üzerinden sayfayı açar, görsel ve yazı tiplerini engelleyerek yükü azaltır, veri isteğinin yanıtını yakalar ve ardından çizilmiş kartları okur:
from playwright.sync_api import sync_playwright
PROXY = {"server": "http://pr.proxynet.io:8000", "username": "kullanici", "password": "parola"}
with sync_playwright() as p:
tarayici = p.chromium.launch(proxy=PROXY)
sayfa = tarayici.new_page()
# Görsel ve yazı tiplerini indirmeyerek süreyi ve trafiği azalt
sayfa.route("**/*.{png,jpg,jpeg,webp,gif,woff2}", lambda route: route.abort())
with sayfa.expect_response(lambda r: "/api/urunler" in r.url and r.ok) as yanit:
sayfa.goto("https://ornek.com/kampanyalar")
api_verisi = yanit.value.json() # JSON'u doğrudan al
sayfa.locator("div.urun").first.wait_for() # ya da çizilmiş kartları bekle
adlar = sayfa.locator("div.urun h2").all_inner_texts()
print(len(api_verisi["urunler"]), adlar)
tarayici.close()Bu örnekte expect_response ile yakalanan JSON çoğu zaman tek başına yeterlidir; kartları okumaya gerek kalmaz. Tarayıcıyı yalnızca sayfanın o isteği doğru parametre ve çerezlerle göndermesini sağlamak için kullanmış olursunuz.
Proxy bilgisi Playwright'ta tarayıcı başlatılırken verilir; kullanıcı adı ve şifre ayrı alanlardadır. Oturum boyunca aynı IP'nin korunması gereken akışlarda sabit bir çıkış adresi, çok sayıda bağımsız sayfada ise her bağlantıda farklı IP veren Rotating Proxy kullanılabilir. Tarayıcı otomasyonunda doğrulama ekranlarının neden çıktığını Puppeteer ve CAPTCHA yazımızda anlattık.
Maliyet: headless tarayıcı ne zaman gereksiz?
| Yaklaşım | Ne zaman işe yarar? | Hız ve kaynak | Kırılganlık | Dikkat |
|---|---|---|---|---|
| HTTP istemcisi + HTML | Sayfa statik, içerik kaynakta var | Çok hızlı, çok hafif | Tasarım değişikliğine duyarlı | Seçicileri kararlı özniteliklere bağlayın |
| HTML'e gömülü JSON | Next.js, Nuxt gibi karma sayfalar | Çok hızlı, hafif | Veri yapısı değişebilir | JSON yolunu kontrol eden hata yönetimi |
| Arka plandaki API isteği | İçerik JSON ile geliyor | Çok hızlı, en hafif | Tasarımdan bağımsız, API değişebilir | Site şartları, belirteçler, hız sınırı |
| Headless tarayıcı | Etkileşim gerekiyor, veri başka yoldan alınamıyor | Yavaş, ağır | Bekleme mantığına duyarlı | Görselleri engelleyin, eşzamanlılığı düşük tutun |
Headless tarayıcının gereksiz olduğu tipik durumlar:
- İçerik sayfa kaynağında zaten var.
- Veri bir JSON isteğinden geliyor ve bu istek basit parametrelerle tekrarlanabiliyor.
- Veri
__NEXT_DATA__veya JSON-LD bloğunda duruyor. - Yalnızca sayfalar arası gezinme için JavaScript kullanılıyor, içerik her sayfada sunucudan geliyor.
Headless tarayıcının gerçekten gerektiği durumlar:
- İçerik ancak kaydırma, tıklama veya form etkileşimiyle yükleniyor ve ilgili API isteği tekrarlanamıyor.
- İstekler, sayfadaki JavaScript'in ürettiği kısa ömürlü imzalar veya belirteçler gerektiriyor.
- Sayfanın görsel çıktısı (ekran görüntüsü, düzen doğrulaması) işin kendisi.
Headless tarayıcı kullanıyorsanız, eşzamanlı sayfa sayısını makine kaynaklarına göre ayarlayın; bu konuyu Concurrency ve Parallelism yazımızda ele aldık.
Kullanım senaryoları
- E-ticaret fiyat takibi: Kategori sayfası dinamik, ama ürün listesi tek bir
/api/urunleristeğinden geliyor. Headless tarayıcı yerine API isteği; farklı ülkelerdeki fiyatları görmek için gerçek ev bağlantılarından çıkan Residential Proxy. Kurgusu e-ticaret proxy çözümü sayfamızda. - Next.js ile yapılmış ilan sitesi: Veri
__NEXT_DATA__içinde. HTTP istemcisi ve JSON ayrıştırma yeterli. - Sonsuz kaydırmalı yorum listesi: Kaydırma sırasında gönderilen sayfalama isteği bulunur ve
cursorparametresiyle döngüye alınır. - Etkileşim gerektiren hesap paneli (kendi hesabınız): Giriş, filtre seçimi ve rapor indirme adımları headless tarayıcıyla; oturum boyunca aynı IP. Genel kurgu veri kazıma çözümü sayfamızda.
Sık yapılan hatalar
- Önce headless tarayıcıya geçmek. Network panelindeki JSON isteği çoğu zaman aynı veriyi çok daha ucuza verir.
- Sabit süre beklemek.
time.sleep(5)hızlı yanıtta zaman kaybettirir, yavaş yanıtta yine boş sonuç verir. networkidlebeklemek. Sürekli istek gönderen sayfalarda hiç gelmeyebilir.- Bütün kaynakları indirmek. Görseller, yazı tipleri ve videolar veri için gerekmez; engellemek süreyi ve trafiği azaltır.
- Boş sonucu dinamik sayfa sanmak. Doğrulama sayfası veya engelleme de boş sonuç verir; durum kodunu ve içeriği kontrol edin.
- Gizli API'yi herkese açık API gibi kullanmak. Site şartları ve hız sınırları bu isteklere de uygulanır.
- Tespit atlatma araçlarına yönelmek. Tarayıcıyı "insan gibi göstermeye" çalışan eklentiler sorunun kaynağını gizler; hız, kapsam ve izin tarafını gözden geçirmek daha sağlam bir yoldur. Bu tür araçların neden sorunlu olduğunu Undetected ChromeDriver ile Scraping yazımızda tartıştık.
Karar rehberi
| Teşhis sonucu | Öneri |
|---|---|
| Metin sayfa kaynağında var | HTTP istemcisi + HTML ayrıştırma |
Kaynakta __NEXT_DATA__ veya JSON-LD var | HTTP istemcisi + JSON ayrıştırma |
| Network panelinde veriyi taşıyan JSON isteği var | İsteği doğrudan tekrarlayın |
| JSON isteği kısa ömürlü imza istiyor | Headless tarayıcı + expect_response |
| İçerik yalnızca etkileşimle geliyor | Headless tarayıcı + locator beklemesi |
| Headless tarayıcı yavaş ve ağır | Görselleri engelleyin, eşzamanlılığı düşürün |
| HTML'de içerik yok ve durum kodu 403/429 | Dinamik değil, engelleme; teşhis edin |
Sık sorulan sorular
requests neden tarayıcıda gördüğüm içeriği döndürmüyor?
Çünkü requests yalnızca sunucunun ilk gönderdiği HTML'i alır ve JavaScript çalıştırmaz. Sayfa içeriği JavaScript ile sonradan yükleniyorsa bu HTML'de yalnızca iskelet vardır. Network panelinde verinin geldiği isteği bulun ya da HTML'e gömülü JSON'u arayın.
Headless tarayıcı ile normal tarayıcı arasındaki fark nedir?
Motor aynıdır; fark, headless modda ekrana pencere çizilmemesidir. Sayfa yükleme, JavaScript çalıştırma ve ağ istekleri normal tarayıcıdaki gibi yapılır. Bazı siteler headless tarayıcıyı küçük farklılıklardan ayırt edebilir.
Playwright mı Selenium mu?
İkisi de dinamik sayfaları çalıştırır. Playwright otomatik bekleme, ağ yanıtı yakalama ve istek engelleme gibi scraping'de işe yarayan özellikleri tek bir API'de sunar; Selenium ise daha eski, çok dilli ve geniş bir ekosisteme sahiptir. Mevcut projenizde hangisi varsa onunla devam etmek genellikle en pratik yoldur.
Gizli API isteğini kullanmak yasal mı?
İsteğin teknik olarak herkese açık olması, kullanımının serbest olduğu anlamına gelmez. Sitenin kullanım şartlarını, robots.txt kurallarını ve kişisel veri mevzuatını değerlendirin; resmi bir API varsa onu tercih edin. Emin değilseniz hukuki destek alın.
Headless tarayıcıyı proxy ile nasıl kullanırım?
Playwright'ta proxy, tarayıcı başlatılırken proxy parametresiyle verilir; sunucu adresi, kullanıcı adı ve şifre ayrı alanlardadır. Puppeteer ve Selenium'da ise tarayıcıya başlatma parametresi olarak verilir ve kimlik doğrulama ayrıca ele alınır.
Sonsuz kaydırmalı sayfalardan veri nasıl alınır?
Önce Network panelinde, kaydırdıkça gönderilen sayfalama isteğini bulun. Bu istek genellikle bir sayfa numarası veya cursor değeri taşır ve döngüyle tekrarlanabilir. İstek tekrarlanamıyorsa headless tarayıcıda sayfayı kademeli kaydırıp her adımda yeni kartların gelmesini bekleyin.
Özetle
Statik sayfada içerik HTML'de hazırdır ve basit bir HTTP isteği yeterlidir; dinamik sayfada içerik JavaScript ile sonradan gelir ve HTTP isteği iskelet döndürür. Headless tarayıcıya geçmeden önce kaynağı görüntüleyin, Network panelinde veriyi taşıyan JSON isteğini arayın ve HTML'e gömülü veriyi kontrol edin. Tarayıcı gerçekten gerektiğinde sabit beklemeler yerine locator beklemesi ve expect_response kullanın, görselleri engelleyin ve eşzamanlılığı düşük tutun. Veri toplama işleriniz için uygun proxy türlerini proxy hizmetlerimizde bulabilirsiniz.




