Web scraping (web kazıma), e-ticaret sitelerindeki dinamik fiyatları takip etmekten borsa verilerini arşivlemeye, rakip analizinden kamu duyurularını toplamaya kadar sayısız veri odaklı projenin omurgasını oluşturur. Bir kazıma betiğini yerel terminalinizde tek seferlik çalıştırmak kolaydır; ancak bunu bir Linux sunucusunda kesintisiz, düzenli ve otomatik bir veri toplama hattına dönüştürmek farklı teknik zorlukları beraberinde getirir.
Linux ortamında zamanlanmış kazıma denildiğinde ilk akla gelen araç cron olsa da, tarayıcı otomasyonu ve web scraping süreçleri standart sistem komutlarından çok farklı davranır. Cron alt kabuğunda grafik ekran değişkeninin ($DISPLAY) bulunmaması, sanal ortam (virtualenv / nvm) yollarının eksikliği, hedef sitelerin uyguladığı hız sınırlamaları (rate limiting) ve üst üste binen (overlapping) kazıma süreçlerinin sunucu belleğini tüketmesi en sık yaşanan problemlerdir. Kazıma görevlerini planlamadan önce temel crontab sözdizimi kurallarını bilmek doğru zamanlama aralıkları seçmenizi sağlar.
Bu kapsamlı rehberde; Python (BeautifulSoup) ve Node.js (Playwright) ile sıfırdan zamanlanmış web kazıma ortamı kuracak, Linux flock aracıyla süreç çakışmalarını engelleyecek ve headless Chromium hatalarını gidereceksiniz. Ayrıca ağır kazıma işlemlerini sunucu içinde crontab ile koşturmak yerine ücretsiz cron job servisi üzerinden HTTP webhook tetikleme modeliyle nasıl mimarileştireceğinizi inceleyeceğiz.
Web Scraping Otomasyonunda Cron Mimarisi ve Karşılaşılan Riskler
Web scraping betikleri, statik HTML sayfalarını çeken basit HTTP istemcilerinden (Requests, Axios; HTML ayrıştırmak için BeautifulSoup) JavaScript ağırlıklı tek sayfa uygulamalarını (SPA) render eden headless tarayıcılara (Puppeteer, Playwright, Selenium) kadar geniş bir yelpazeyi kapsar. Bu betikleri cron ile otomatize ederken üç temel risk faktörü mutlaka yönetilmelidir:
- Kabuk Ortamı Farklılıkları: Cron, kullanıcı oturumu açılmadan arka planda
/bin/shile çalışır. Kullanıcının.bashrcveya.zshrcdosyalarında tanımlı olanPATHdeğişkenleri ve sanal ortamlar cron tarafından yüklenmez. - Süreç Çakışması (Task Overlapping): Hedef sitede yaşanan yavaşlama veya sayfa sayısının artması nedeniyle 5 dakikada bir çalışması planlanan bir kazıma işlemi 8 dakika sürebilir. Yeni bir cron tetiklemesi geldiğinde önceki betik henüz kapanmadığından sistemde paralel süreçler birikir; bu durum CPU ve RAM darboğazına yol açar.
- IP Engellemesi ve Etik Kurallar: Hedef sunucuya çok sık ve agresif istek atmak
HTTP 429 Too Many Requestsveya doğrudan IP ban ile sonuçlanır. Görevler arasında bekleme süresi koymak,robots.txtdirektiflerine uymak ve anlamlı User-Agent başlığı iletmek şarttır.
Adım Adım Kurulum: Python (BeautifulSoup) ve Node.js (Playwright) Ortamları
Üretim sunucusunda scraping otomasyonu kurarken doğrudan sistem paket yöneticisini kirletmek yerine izole ortamlar oluşturulmalıdır. Aşağıdaki adımlar Debian/Ubuntu tabanlı bir Linux sunucuda sıfırdan kurulumu gösterir:
- Gerekli sistem kütüphanelerini ve derleme bağımlılıklarını kurun.
- Python için özel bir sanal ortam (venv) oluşturun ve
beautifulsoup4ilerequestspaketlerini yükleyin. - Node.js için Playwright ve Chromium tarayıcı çekirdeğini sisteme indirin.
- Cron servisi için log dizinlerini ve kilit dosyası yetkilerini tanımlayın.
Sanal Ortamların (venv ve nvm) Crontab'a Doğru Tanımlanması
Cron job yapılandırırken en sık yapılan hata, crontab satırına doğrudan python scraper.py yazmaktır. Sistem cron'u sistem genelindeki Python sürümünü çağırır ve kurulu kütüphaneleri bulamaz. Mutlak yol (absolute path) kullanılmalıdır. Ayrıntılı adımlar için Python cron job rehberi ve Node.js cron job rehberi sayfalarından yararlanabilirsiniz:
# Doğru crontab tanımlaması (mutlak sanal ortam yoluyla)
0 */4 * * * /home/deployer/scraper-env/bin/python /home/deployer/scripts/fiyat_takip.py >> /var/log/scraper.log 2>&1
Headless Tarayıcılar İçin Sanal Ekran (Xvfb) ve Sandbox Bayrakları
Playwright veya Puppeteer gibi araçlar bir Linux sunucusunda X11 grafik arayüzü olmadan (headless) çalıştırıldığında Chromium çekirdeği bazı güvenlik ve bellek kısıtlamalarına takılır. Özellikle paylaşılan bellek alanının (/dev/shm) küçük olduğu konteyner ve VPS sunucularda Chromium sekme çökmeleri yaşanır. Paylaşımlı bellek yetersizliğinde --disable-dev-shm-usage kullanılabilir; --no-sandbox zorunlu değildir ve tarayıcı güvenlik yalıtımını devre dışı bırakır.
Görev Çakışmalarını Önleme: flock ile Tekil Yürütme Güvencesi
Web kazıma işlemlerinin süresi ağ gecikmesine, sayfa yapısına ve hedef sitenin yanıt hızına göre büyük değişkenlik gösterir. 10 dakikalık bir periyotla çalışan scraper bazen 15 dakika sürebilir. Yeni bir periyot başladığında eski scraper hâlâ çalışıyorsa iki süreç aynı veritabanına yazmaya çalışır ve veriler bozulabilir.
Linux çekirdeğinin sunduğu flock (file lock) komutu, dosya tabanlı kilit mekanizmasıyla bu sorunu tamamen çözer. -n (non-blocking) bayrağı kullanıldığında; eğer kilit dosyası başka bir süreç tarafından tutuluyorsa yeni süreç beklemeden sessizce sonlanır:
# flock ile güvenli crontab kaydı
*/10 * * * * /usr/bin/flock -n /tmp/scraper_fiyat.lock /home/deployer/scraper-env/bin/python /home/deployer/scripts/fiyat_takip.py >> /var/log/scraper.log 2>&1
Gerçek Kod Örnekleri ve Veri Kayıt Süreci
Aşağıda hem Python hem de Node.js ortamları için üretime hazır, hata yakalama mekanizmasına sahip iki gerçek kazıma betiği yer almaktadır.
Python ve BeautifulSoup ile Fiyat Takip Betiği
Bu betik hedef e-ticaret sayfasına bağlanır, ürünün adını ve güncel fiyatını ayrıştırır, zaman damgasıyla yerel bir SQLite veritabanına kaydeder. Sunucuyu yormamak için her istek arasında rastgele gecikme bırakır:
#!/usr/bin/env python3
# /home/deployer/scripts/fiyat_takip.py
import os
import sys
import time
import random
import sqlite3
import requests
from bs4 import BeautifulSoup
from datetime import datetime
DB_PATH = "/home/deployer/data/fiyatlar.db"
TARGET_URL = "https://ornek-magaza.com/urun/kablosuz-kulaklik"
def init_db():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
with sqlite3.connect(DB_PATH) as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS fiyat_gecmisi (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tarih TEXT NOT NULL,
urun_adi TEXT NOT NULL,
fiyat REAL NOT NULL
)
""")
def scrape_fiyat():
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8"
}
# Hedef sunucuya ani yük bindirmemek için rastgele 2-5 saniye bekle
time.sleep(random.uniform(2.0, 5.0))
response = requests.get(TARGET_URL, headers=headers, timeout=20)
if response.status_code != 200:
print(f"Hata: HTTP {response.status_code}", file=sys.stderr)
sys.exit(1)
soup = BeautifulSoup(response.text, "html.parser")
fiyat_elementi = soup.select_one(".product-price-current")
baslik_elementi = soup.select_one("h1.product-title")
if not fiyat_elementi or not baslik_elementi:
print("Hata: Hedef CSS seçicileri bulunamadı.", file=sys.stderr)
sys.exit(1)
fiyat_metni = fiyat_elementi.get_text(strip=True).replace("TL", "").replace(".", "").replace(",", ".").strip()
fiyat = float(fiyat_metni)
urun_adi = baslik_elementi.get_text(strip=True)
with sqlite3.connect(DB_PATH) as conn:
conn.execute(
"INSERT INTO fiyat_gecmisi (tarih, urun_adi, fiyat) VALUES (?, ?, ?)",
(datetime.now().isoformat(), urun_adi, fiyat)
)
print(f"Başarılı: {urun_adi} - {fiyat} TL kaydedildi.")
if __name__ == "__main__":
init_db()
scrape_fiyat()
Node.js ve Playwright ile Dinamik Sayfa Kazıma
JavaScript ile istemci tarafında render edilen dinamik sayfalar için Playwright headless Chromium kullanır. Aşağıdaki script dinamik listeyi render edip JSONL (satır bazlı JSON) dosyasına ekler:
// /home/deployer/scripts/playwright_scraper.js
const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const OUTPUT_FILE = '/home/deployer/data/urunler.jsonl';
(async () => {
const browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu'
]
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) Chrome/122.0.0.0 Safari/537.36'
});
const page = await context.newPage();
try {
await page.goto('https://ornek-magaza.com/kategori/bilgisayar', {
waitUntil: 'networkidle',
timeout: 30000
});
const urunler = await page.$$eval('.product-card', cards => {
return cards.map(c => ({
ad: c.querySelector('.title')?.innerText?.trim() || '',
fiyat: c.querySelector('.price')?.innerText?.trim() || ''
}));
});
const kayit = {
timestamp: new Date().toISOString(),
adet: urunler.length,
veriler: urunler
};
fs.mkdirSync(path.dirname(OUTPUT_FILE), { recursive: true });
fs.appendFileSync(OUTPUT_FILE, JSON.stringify(kayit) + '\n');
console.log(`Tamamlandı: ${urunler.length} ürün kaydedildi.`);
} catch (err) {
console.error('Playwright kazıma hatası:', err.message);
process.exitCode = 1;
} finally {
await browser.close();
}
})();
Gerçek Senaryo: ETag ve Last-Modified ile Değişiklik Algılama
Bir ürün sayfası günde yalnızca birkaç kez değişir; ancak kazıma betiği her çalıştığında sayfayı baştan indirip baştan ayrıştırır. Değişmeyen sayfaları koşullu isteklerle atlamak hem bant genişliğinizi hem de hedef sitenin hız limitine takılma riskinizi düşürür. Çözüm kısaca şudur: sunucunun verdiği ETag veya Last-Modified değerini saklayın, sonraki isteklerde If-None-Match / If-Modified-Since başlıklarıyla geri gönderin; HTTP 304 Not Modified yanıtı aldığınızda ayrıştırma ve veritabanı yazma adımlarını tamamen atlayın. Bu teknik özellikle dakikalık ya da 5 dakikalık aralıklarla çalışan izleyicilerde belirgin kazanç sağlar.
- İlk istekte yanıt başlıklarındaki
ETagveLast-Modifieddeğerlerini URL ile birlikte kalıcı olarak saklayın (aşağıdaki örnekte SQLitekaynak_durumtablosu). - Sonraki her istekte bu değerleri
If-None-MatchveIf-Modified-Sincebaşlıklarıyla geri gönderin. 304yanıtı gelirse betiği başarı çıkış koduyla (0) bitirin; hiçbir ayrıştırma yapmayın.200yanıtı gelince fiyatı ayrıştırın ve yeni başlık değerlerini eskileriyle değiştirin.
# fiyat_takip.py içine eklenecek koşullu istek bölümü
# Bu blok scrape_fiyat() içindeki istek adımının yerine, with sqlite3.connect(DB_PATH) as conn: bloğunun içine yerleştirilir
with sqlite3.connect(DB_PATH) as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS kaynak_durum (
url TEXT PRIMARY KEY,
etag TEXT,
last_modified TEXT
)
""")
headers = {
"User-Agent": "FiyatIzleyici/1.0",
"Accept-Language": "tr-TR,tr;q=0.9"
}
row = conn.execute(
"SELECT etag, last_modified FROM kaynak_durum WHERE url = ?", (TARGET_URL,)
).fetchone()
if row:
if row[0]:
headers["If-None-Match"] = row[0]
if row[1]:
headers["If-Modified-Since"] = row[1]
response = requests.get(TARGET_URL, headers=headers, timeout=20)
if response.status_code == 304:
print("Değişiklik yok; ayrıştırma atlandı.")
sys.exit(0)
# 200 alındı: fiyatı ayrıştır, sonra yeni başlık değerlerini sakla
conn.execute(
"""
INSERT INTO kaynak_durum (url, etag, last_modified) VALUES (?, ?, ?)
ON CONFLICT(url) DO UPDATE SET
etag = excluded.etag,
last_modified = excluded.last_modified
""",
(TARGET_URL, response.headers.get("ETag", ""), response.headers.get("Last-Modified", ""))
)
İki uyarı: ON CONFLICT yan tümcesi SQLite 3.24 ve üzeri sürümlerde çalışır; ayrıca bazı sunucular her yanıtta farklı bir ETag üretir; böyle durumlarda 304 hiç gelmez. Değişikliği yine de yakalamak istiyorsanız sayfa gövdesinin SHA-256 sağlamasını (hash) alıp önceki değerle karşılaştırın — hash aynıysa yazımı atlayın. Fiyat belirlediğiniz eşiği aştığında e-posta ile uyarı göndermek isterseniz cron ile e-posta gönderme rehberindeki kuyruk ve hız limiti yaklaşımını betiğinize uygulayabilirsiniz; SMTP sağlayıcılarının saatlik gönderim limitleri, dakikalık kazıma döngüsüne doğrudan e-posta basmanızı engeller.
Sık Karşılaşılan Hatalar ve Çözümleri
Zamanlanmış kazıma betiklerinde karşılaşılan yaygın teknik problemler ve operasyonel çözümleri şunlardır:
Error: Failed to launch the browser process! Missing X server or $DISPLAY
Bu mesaj, grafik modda başlatılan tarayıcının X sunucusu veya DISPLAY değişkeni bulamadığını gösterir. headless: true kullanın veya grafik mod gerekiyorsa Xvfb ile başlatın. Eksik sistem kütüphaneleri ayrıca npx playwright install-deps komutuyla kurulabilir.
HTTP Error 429: Too Many Requests
Hedef web sitesinin güvenlik duvarı (WAF veya Cloudflare) IP adresinizden gelen istek sıklığının izin verilen eşiği aştığını tespit etmiştir. Çözüm olarak cron çalışma sıklığını seyreltin (örneğin dakikalık yerine saatlik çalıştırın), istekler arasına rastgele uyku süreleri (jitter) koyun, gerçek tarayıcı User-Agent başlıkları rotasyonu uygulayın veya kurumsal proxy sağlayıcıları üzerinden istek atın.
flock: failed to get lock: Resource temporarily unavailable
Bu bir hata değil, koruma mekanizmasının çalıştığını gösteren durumdur. Önceki kazıma süreci henüz bitmediği için flock -n yeni sürecin açılmasını engelleyerek sunucunun kilitlenmesini önlemiştir. Ancak bu uyarıyı sürekli alıyorsanız kazıma süreniz cron aralığınızdan uzun sürüyor demektir; aralığı açmalı veya kazıma işlemini optimize etmelisiniz. Diğer cron sorunları için cron job çalışmıyor rehberi adımlarını inceleyebilirsiniz.
ModuleNotFoundError: No module named 'bs4'
Terminalde betiği çalıştırdığınızda çalışan kod, cron altında bu hatayı verir. Sebep, cron'un sistem genelindeki Python çalıştırıcısını kullanması ve BeautifulSoup'un kurulu olduğu sanal ortamı (virtualenv) görmemesidir. Çözüm için crontab satırındaki python komutunu sanal ortamın tam mutlak yolu ile (örneğin /home/user/venv/bin/python) değiştirin.
Doğrulama ve Sorun Giderme: Kazıma Görevini Kanıtlamak
Kazıma görevi "çalışmıyor" gibi göründüğünde üç ayrı zincir halkasını tek tek doğrulayın: crontab kaydı daemon tarafından okundu mu, daemon komutu gerçekten başlattı mı, betik kendi çıkış koduyla başarıyı yazdı mı. Bu üçünü ayrı tutmak, sorunun zamanlamada mı betikte mi hedef sitede mi olduğunun tek tek elenmesini sağlar. Aşağıdaki tabloda en sık görülen belirtiler, gerçek kontrol komutları ve çözümleri yer alır:
| Belirti | Olası neden | Kontrol komutu | Çözüm |
|---|---|---|---|
| Betik terminalde çalışıyor, crontab'tan başlamıyor | Cron'un PATH'i sanal ortamı içermiyor | env -i /bin/sh -c '/home/deployer/scraper-env/bin/python /home/deployer/scripts/fiyat_takip.py' | Python ve betik yolunu mutlak yazın; hata env -i altında da tekrarlanıyorsa yol sorunudur |
| Syslog'ta CMD satırı hiç yok | Cron servisi durdurulmuş veya crontab kaydı girilmemiş | systemctl status cron ve crontab -l | Servisi başlatın, satırı yeniden kaydedin |
| CMD satırı var, scraper.log boş | Çıktı yönlendirilmemiş ya da süreç anında çökmüş | grep CRON /var/log/syslog | Komuta >> /var/log/scraper.log 2>&1 ekleyin; hatayı terminalden yeniden üretin |
No module named bs4 yalnızca cron'da | Sistem Python'u çağrılıyor, sanal ortam değil | which python3 ve ls /home/deployer/scraper-env/bin/ | Crontab satırında sanal ortamın Python yolunu kullanın |
| Aynı dakikada birden çok süreç görünüyor | Kilit yok veya aralık kazıma süresinden kısa | pgrep -af fiyat_takip.py | flock -n ile tekil yürütme kilidi kurun; aralığı açın |
sqlite3.OperationalError: database is locked | İki kazıyıcı aynı anda aynı dosyaya yazıyor | journalctl -u cron --since "1 hour ago" ile tetikleme sıklığını görün | flock uygulayın; bağlantıda PRAGMA busy_timeout = 5000 tanımlayın |
Playwright yalnızca cron'da Missing X server veriyor | Cron kabuğunda $DISPLAY tanımlı değil | Geçici crontab satırıyla * * * * * /usr/bin/echo "DISPLAY=[$DISPLAY]" >> /tmp/display-test.txt | headless: true kullanın; sistem kütüphaneleri için npx playwright install-deps çalıştırın |
Aşağıdaki çıktı, aynı görevin başarılı ve başarısız iki çalışmasının gerçekte nasıl göründüğünü gösterir. Daemon kaydı syslog'ta (Ubuntu/Debian), betiğin kendi çıktısı ise yönlendirdiğiniz log dosyasında birikir; bu ikisini yan yana okumak tetikleme ile iş mantığı hatasını birbirinden ayırır:
# /var/log/syslog — daemon tetikleme kaydı
Oct 29 04:10:01 app01 CRON[24011]: (deployer) CMD (/usr/bin/flock -n /tmp/scraper_fiyat.lock /home/deployer/scraper-env/bin/python /home/deployer/scripts/fiyat_takip.py >> /var/log/scraper.log 2>&1)
# /var/log/scraper.log — başarılı çalışma (betik exit 0)
Başarılı: Kablosuz Kulaklık - 2499.0 TL kaydedildi.
# /var/log/scraper.log — başarısız çalışma (betik exit 1;
# syslog'ta ek satır görünmez, hata yalnızca bu dosyadadır)
Hata: HTTP 503
requests ve beautifulsoup4, Node.js 22 LTS üzerinde Playwright Chromium; tekil yürütme için util-linux flock. Ekim 2026.cronjob.com.tr ile HTTP Tabanlı Alternatif Kurulum (Scraper Webhook Endpoint)
Sunucu üzerinde doğrudan Chromium çalıştırmak veya yerel crontab ile kazıma betikleri yönetmek küçük sunucularda ciddi bellek yetersizliğine ve operasyonel yüke yol açar. Daha modern ve mikroservis uyumlu bir alternatif; kazıma mantığını FastAPI (Python) veya Express (Node.js) arkasında bir HTTP uç noktasına (örneğin POST /api/scrape/fiyatlar) bağlamak ve bu uç noktayı harici bir bulut zamanlayıcı ile tetiklemektir. Kazıma sonuçlarını Django tabanlı bir uygulamada işliyorsanız zamanlamayı uygulamanın içinde de tutabilirsiniz; Django Celery Beat ile periyodik görev planlama rehberi, kazımayı kuyruk tüketicisi olan bir uygulama görevine dönüştürmenin eşdeğer kurulumunu anlatır.
Bu mimaride cronjob.com.tr servisi periyodik olarak uç noktanızı tetikler. Kazıma servisi isteği aldığında işlemi arka plandaki bir kuyruğa veya worker sürecine aktararak anında HTTP 200 OK dönebilir. Bu modelin cronjob.com.tr üzerindeki kuralları ve avantajları şunlardır:
cronjob.com.tr tamamen ücretsizdir ve kayıt olurken kredi kartı bilgisi istenmez. En kısa çalışma periyodu 60 saniyedir (1 dakika). Zamanlamanızı paneldeki hazır aralık seçenekleriyle kurabileceğiniz gibi standart 5 alanlı cron ifadesiyle de oluşturabilirsiniz. Saat dilimi Europe/Istanbul veya ihtiyacınıza göre iş bazında seçilebilir.
Platform; GET, POST, PUT, PATCH, DELETE, HEAD ve OPTIONS metodlarını destekler. Kazıma uç noktanızı korumak için X-Scraper-Token: GizliAnahtar gibi özel başlıklar veya HTTP Basic Auth ekleyebilirsiniz. İstek zaman aşımı süresi 1 dakikalık aralıkta 55 saniye, 5 dakika ve üzeri aralıklarda ya da cron ifadeli görevlerde 290 saniyedir (neredeyse 5 dakika). HTTP 200–399 arası yanıtlar başarı sayılır, en fazla 5 yönlendirme izlenir ve HTTPS sertifikası doğrulanır.
Güvenlik nedeniyle localhost veya özel yerel ağ IP'lerine (192.168.x, 10.x vb.) istek gönderilmez; uç noktanız genel internete açık bir alan adı veya IP üzerinde bulunmalıdır. İstekler CronJobComTr/1.0 (+https://cronjob.com.tr) User-Agent bilgisiyle yapılır. Kazıma uç noktanız art arda 3 kez hata verirse (1–20 arası yapılandırılabilir) sistem yöneticisine erişim sorunu e-postası iletilir, sorun çözüldüğünde kesinti süresiyle düzeldi e-postası gönderilir (aynı iş için en az 60 dakika arayla). Tüm çalıştırma logları (durum kodu, yanıt süresi, hata ayrıntısı, yanıt gövdesinin ilk ~64 KB'ı) 7 gün boyunca saklanır.
Sıkça Sorulan Sorular (SSS)
Cron ile scraping yaparken IP engellenmesini nasıl önlerim?
İstekler arasına rastgele gecikmeler koyarak, proxy havuzu ve User-Agent rotasyonu kullanarak ve sitenin robots.txt kurallarına saygı göstererek IP engeli riskini azaltabilirsiniz; bu yöntemler engellenmeyeceğinizi garanti etmez.
Selenium cron job içinde neden hata verir?
Cron kabuğu grafik ekran ortam değişkeni ($DISPLAY) içermediği için Selenium varsayılan modda pencere açamaz; headless mod kullanılmalı veya grafik mod gerekiyorsa Xvfb sanal ekranı tanımlanmalıdır.
Dakikalık headless scraping sunucuyu kilitler mi?
Evet, Chromium gibi headless tarayıcılar her açılışta yüksek RAM ve CPU tükettiğinden dakikalık sık tetiklemeler bellek taşmasına ve sunucunun kilitlenmesine yol açabilir.
Kazıma işlemi 10 dakikadan uzun sürerse ne olur?
İşlem bir sonraki cron periyoduna kadar bitmezse çalışmalar üst üste binerek kaynakları tüketebilir; bu durumu önlemek için Linux flock komutu ile tekil yürütme kilidi kullanılmalıdır.
robots.txt kurallarını betiğimde nasıl kontrol ederim?
Python'un urllib.robotparser modülüyle robots.txt dosyasını okuyup can_fetch() yöntemiyle belirli bir URL'nin kazınıp kazınamayacağını sorabilirsiniz; izin verilmeyen yolları atlayın ve istekler arasına gecikme koyun. Uyum isteğe bağlıdır ama ihlal, IP engellenme riskini ve hedef sitenin yükünü artırır.
Sayfa değişmediğinde kazımayı nasıl atlarım?
Sunucu ETag veya Last-Modified başlığı gönderiyorsa önceki değeri If-None-Match veya If-Modified-Since başlığıyla geri gönderin; 304 yanıtı aldığınızda ayrıştırma ve veritabanına yazma adımlarını atlayın. Bu başlıklar yoksa sayfanın içeriğinden aldığınız bir sağlamayla (hash) değişikliği kendiniz algılayabilirsiniz.
Hemen Ücretsiz Cron Job Oluşturun
Fiyat izleme uç noktanızı X-Scraper-Token başlığıyla koruyup cronjob.com.tr'den dakikalık aralıkla çağırabilirsiniz; her çalışmanın durum kodu ve yanıt süresi 7 gün loglanır, ardışık hatalarda e-posta alırsınız.