Proksi üzerinden büyük veri hacimlerini nasıl indirirsiniz ve kopma sonrası baştan başlamazsınız
Makale içeriği
- Giriş: uzun indirme neden neredeyse her zaman kopar ve bu normaldir
- Ön hazırlık: araçlar, erişimler ve ortam
- Temel kavramlar: dayanıklı indirmenin sözlüğü basit kelimelerle
- Adım 1: http üzerinden range başlığıyla devam indirme
- Adım 2: sayfalı indirmeler için kontrol noktaları
- Adım 3: tekrar kopya oluşturmasın diye idempotentlik
- Adım 4: belleği şişirmeden sonuçları tekilleştirme
- Adım 5: kayıpsız paralellik
- Adım 6: uzun bir aradan sonra devam ettirme
- Adım 7: python ile hazır dayanıklı indirici iskeleti
- Sonucu kontrol etme: dayanıklı indirme kontrol listesi
- Sık yapılan hatalar ve çözümleri
- Ek yetenekler ve optimizasyon
- Sss: dayanıklı indirmeyle ilgili sık sorulan sorular
- Sonuç: artık neler yapabiliyorsunuz ve nereye ilerlemeli
Giriş: uzun indirme neden neredeyse her zaman kopar ve bu normaldir
Büyük bir veri indirmesini bir kez olsun başlattıysanız bu hissi bilirsiniz. İşlem saatler sürdü, yüzde doksana geldi ve koptu. Bağlantı düştü, sunucu hata döndürdü, dizüstü uykuya geçti. Ve her şeye baştan başlamak gerekiyor. Bu rehber, bunun bir daha yaşanmaması için yazıldı.
Sonunda ne elde edeceksiniz. Kopmalara dayanan bir indirici kurmayı öğreneceksiniz. Dosyaları ortadan tamamlar, hangi sayfada kaldığını hatırlar, tekrarda kopya oluşturmaz ve uzun bir aradan sonra bile devam edebilir. Görevinize uyarlayabileceğiniz hazır bir Python iskeleti elde edeceksiniz.
Bu rehber kimin için. API'den veri indiren, büyük dosyalar indiren veya proksi üzerinden sayfa sayfa sonuç toplayan mühendisler, analistler ve geliştiriciler için. Seviye orta düzey. HTTP temellerini anlamalı ve Python kodunu okuyabilmelisiniz. Derin ağ programlama bilgisi gerekmez.
Önceden bilmeniz gerekenler. Temel Python, HTTP isteği ve yanıtı kavramı, başlıklar ve durum kodları nedir. requests kütüphanesiyle çalıştıysanız bu yeterlidir.
Ne kadar zaman gerekir. Kavramları okuyup anlamak yaklaşık kırk dakika sürer. İskeletimize göre çalışan bir indirici kurmak, veri kaynağınıza bağlı olarak bir ila üç saat arasında sürer.
Konuyla ilgili önemli bir not. 429 gibi durum kodlarını ve gecikmeli tekrar stratejilerini (backoff) ele almayacağız. Bununla ilgili ayrı bir materyal var. Burada odak yalnızca bir konuda: sürecin durumu ve devam ettirilmesi. İlerleme nasıl kaydedilir, veri nasıl kaybedilmez ve kopyalanmaz, kaldığınız yerden nasıl devam edilir.
İpucu: Kaynağınızın parametrelerini yazacağınız bir not defteri veya ayrı bir dosyayı elinizin altında tutun: devam indirmeyi destekliyor mu, sayfalama var mı, imleç formatı nasıl. Bu notlar her adımda işinize yarayacak.
Ön hazırlık: araçlar, erişimler ve ortam
Kod yazmadan önce çalışma ortamını kuralım. On dakika sürer ama saatlerce hata ayıklamadan kurtarır.
Kurulması gerekenler
- Python 3.10 veya daha yeni sürümü kurun. Terminalde
python --versionkomutuyla sürümü kontrol edin. - Proje bağımlılıklarının sistemle karışmaması için
python -m venv venvkomutuyla sanal ortam oluşturun. - Ortamı etkinleştirin. Windows'ta
venv\Scripts\activate, macOS ve Linux'tasource venv/bin/activate. - HTTP istekleri için kütüphaneyi
pip install requestskomutuyla kurun. - Durum veritabanıyla daha hızlı çalışmak için ek bir kurulum gerekmez:
sqlite3modülü Python standart kütüphanesinde zaten var.
Erişimlerden gerekenler
- Veri kaynağınıza erişim: URL, varsa token veya API anahtarı.
- Adres, port ve yetkilendirme bilgileriyle Proxeon proksisi. Kararlı bir proksi olmadan dayanıklı indirme anlamsızlaşır, çünkü yükü dağıtan ve bağlantıları öngörülebilir kılan tam da proksidir.
- Durum dosyası ve indirilen veriler için disk alanı.
Proxeon proksisini kontrol etme
http://kullanıcı:parola@adres:portbiçiminde bağlantı dizesini alın.- Basit bir istekle kontrol edin. Terminalde
curl -x http://kullanıcı:parola@adres:port https://api.ipify.orgkomutunu çalıştırın ve dönen adresin kendi adresiniz değil proksi IP adresi olduğundan emin olun.
İpucu: Proksi bağlantı dizesini koda değil, ortam değişkenine kaydedin. Böylece parolayı yanlışlıkla sürüm kontrol sistemine göndermezsiniz. Kodda os.environ üzerinden okuyun.
⚠️ Dikkat: Yalnızca yasal olarak erişim hakkınız olan veri kaynaklarıyla çalışın. Hizmetin kullanım koşullarına ve sahibi tarafından belirtilen limitlere uyun. Proxeon proksisi yasal mühendislik işleri için tasarlanmıştır: yük dağıtımı, bağlantı kararlılığı ve doğru veri indirme.
✅ Kontrol: python -c "import requests, sqlite3" komutu hatasız çalıştıysa ve proksi üzerinden yapılan istek proksi sunucusunun adresini döndürdüyse ortam hazırdır.
Temel kavramlar: dayanıklı indirmenin sözlüğü basit kelimelerle
Kod yazmadan önce anahtar terimleri ele alalım. Bunlar olmadan sonraki adımlar sihirli sözler gibi kulağa gelecektir.
Devam indirme
Devam indirme, bir dosyanın indirilmesinin kesildiği bayttan sürdürülmesidir. Dosyayı baştan indirmek yerine sunucudan yalnızca eksik parçayı göndermesini istersiniz. HTTP Range başlığı üzerinden çalışır.
Kontrol noktası
Kontrol noktası, kaydedilmiş bir ilerleme noktasıdır. Bilgisayar oyunundaki kaydetmeyi düşünün. Bir şeyler ters giderse oyunun başına değil, son kayda dönersiniz. İndirmede kontrol noktası, hangi sayfada veya kayıtta kaldığınızı saklar.
İmleç
İmleç, API'nin sonraki veri porsiyonunu istemeniz için size verdiği bir etikettir. Genellikle eyJvZmZzZXQiOjEwMH0 gibi bir dizedir. Onu geri gönderirsiniz ve sunucu nereden devam edeceğini anlar.
İdempotentlik
İdempotentlik, bir işlemin tekrarının sonucu değiştirmemesi özelliğidir. Aynı anahtarla aynı satırı iki kez yazdıysanız sonuçta iki değil, bir satır olur. Bu, tekrarlarda kopyalara karşı korumadır.
Tekilleştirme
Tekilleştirme, tekrar eden kayıtların ayıklanmasıdır. Dikkatli çalışmada bile aynı nesne iki kez gelebilir. Tekilleştirme, nihai kümenizde onun bir kez kalmasını garanti eder.
Temel ilke
Dayanıklı bir indirici tek bir fikre dayanır: ilerleme yalnızca sonda değil, sürekli kaydedilmelidir. Herhangi bir adım kopmadan önceki son adım olabilir. Demek ki her başarılı iş parçasından sonra durum diske yazılmalıdır. O zaman devam ettirmek, durumu okuyup sürdürmekten ibarettir.
İpucu: Her indirme için üç soru kuralını aklınızda tutun. Birincisi: nerede kaldım? İkincisi: alınanı nasıl kopyalamam? Üçüncüsü: ben yokken ne bayatlar? Bu soruların yanıtları dayanıklılığı oluşturur.
Adım 1: HTTP üzerinden Range başlığıyla devam indirme
Bu aşamanın amacı. Büyük bir dosyayı, kopma sonrası baştan değil, eksik kalan bayttan sürdürecek şekilde indirmeyi öğrenmek.
Nasıl çalışır
HTTP, dosyanın tamamını değil bir bölümünü istemenize izin verir. Bunun için isteğe Range başlığı eklenir. Örneğin Range: bytes=1048576- şu anlama gelir: bana 1048576 numaralı bayttan itibaren her şeyi ver. Ama önce sunucunun bunu yapabildiğinden emin olmak gerekir.
- Dosyaya HEAD yöntemiyle veya normal GET isteği gönderin ve yanıt başlıklarına bakın.
Accept-Rangesbaşlığını bulun. Değeribytesise sunucu devam indirmeyi destekliyor.- Başlık yoksa veya
noneise devam indirme mümkün değil. Bu durumda dosyayı tek seferde baştan indirmek veya alternatif kaynak aramak gerekir.
Devam indirme desteğini kontrol etme
Aşağıdaki kod, sunucunun dosyanın parçalarını verip veremeyeceğini kontrol eder.
import requests
def supports_resume(url, proxies):
resp = requests.head(url, proxies=proxies, timeout=30, allow_redirects=True)
accept = resp.headers.get("Accept-Ranges", "none")
total = resp.headers.get("Content-Length")
return accept.lower() == "bytes", totalDosyayı ortadan tamamlama
Şimdi asıl kod. Yerel olarak kaç bayt indirildiğine bakar ve sunucudan yalnızca kalanı ister.
import os
import requests
def download_resumable(url, dest, proxies):
already = 0
if os.path.exists(dest):
already = os.path.getsize(dest)
headers = {}
if already > 0:
headers["Range"] = f"bytes={already}-"
mode = "ab" if already > 0 else "wb"
with requests.get(url, headers=headers, proxies=proxies,
stream=True, timeout=60) as r:
if already > 0 and r.status_code == 200:
mode = "wb"
already = 0
with open(dest, mode) as f:
for chunk in r.iter_content(chunk_size=65536):
if chunk:
f.write(chunk)
return os.path.getsize(dest)Önemli noktaları inceleyelim. Sunucu 206 durumu döndürdüyse dosyanın bir kısmını dürüstçe vermiş demektir ve ekleme doğru çalışır. Sunucu Range başlığına rağmen 200 döndürdüyse devam indirmeyi yok sayıyor ve dosyayı baştan veriyor demektir. Bu durumda eski parçayı yenisiyle birleştirip dosyayı bozmamak için tam üzerine yazma moduna geçeriz.
⚠️ Dikkat: Sunucunun 206 koduyla yanıt verdiğinden emin değilseniz, ab modunda asla veri eklemeyin. Aksi halde başlangıcı önceki denemenin kalıntısı, devamı ise yeni tam dosya olan bozuk bir dosya elde edersiniz. Böyle bir dosya hatayla açılır ve nedenini ararken zaman kaybedersiniz.
İpucu: Doğrudan hedef dosyaya değil, .part uzantılı geçici bir dosyaya indirin. İndirme tamamen bitince onu nihai adına yeniden adlandırın. Böylece hazır dosyayı hiçbir zaman yarı indirilmiş dosyayla karıştırmazsınız.
Bütünlük kontrolü
Tam indirmeden sonra dosyanın bozulmadığını kontrol etmek iyi olur. Sunucu Content-Length başlığını verdiyse onu diskteki gerçek dosya boyutuyla karşılaştırın. Boyutlar eşleştiyse dosya bütünüyle inmiştir.
def verify_size(dest, expected):
if expected is None:
return True
return os.path.getsize(dest) == int(expected)✅ Kontrol: Programı kapatarak indirmeyi ortada kesin. Tekrar başlatın. Günlüklerde isteğin Range başlığıyla gittiğini ve dosyanın baştan değil, eklendiğini görmelisiniz. Nihai boyut beklenenle eşleşiyor.
Adım 2: Sayfalı indirmeler için kontrol noktaları
Bu aşamanın amacı. Verileri sayfa sayfa veren API'ler için ilerleme kaydını ayarlamak, böylece kopma sonrası doğru sayfadan devam etmek.
Tam olarak neyi saklamalı
Dosya bayt bayt tamamlanır, sayfalı indirme ise tamamen farklı bir mantıkla çalışır. Burada bayt yok, sayfa ve kayıt var. Demek ki kontrol noktasında başka şeyler saklamak gerekir.
- İmleç API imleçlerle çalışıyorsa. Bu en güvenilir seçenektir, çünkü imleç nereden devam edeceğini kendisi bilir.
- Sayfa numarası veya offset API offset ve limit ile çalışıyorsa. Başarıyla işlenen son sayfa numarasını saklayın.
- Son kaydın kimliği artan ID veya tarihe göre sıralanabiliyorsa. O zaman sonraki istek, saklanandan büyük ID'li kayıtları ister.
- İşlenen kayıt sayacı kontrol ve raporlama için.
Durum nerede saklanmalı
Basitten güvenilire doğru üç ana seçeneğiniz var.
- JSON dosyası. En basiti. Her sayfadan sonra imleç ve sayacı içeren sözlüğü dosyaya yazarsınız. Tek, paralel olmayan indirmeler için uygundur.
- SQLite veritabanı. Daha güvenilir. İşlemler (transaction) sunduğu için yazma anında kopma olsa bile durum bozulmaz. Veri çoksa ve tekilleştirme gerekiyorsa iyidir.
- Harici veritabanı. Birden fazla sürecin aynı işi paylaştığı büyük dağıtık indirmeler için.
Kontrol noktasını JSON'a kaydetme
import json
import os
def save_checkpoint(path, cursor, page, last_id, count):
tmp = path + ".tmp"
data = {
"cursor": cursor,
"page": page,
"last_id": last_id,
"count": count,
}
with open(tmp, "w") as f:
json.dump(data, f)
os.replace(tmp, path)
def load_checkpoint(path):
if not os.path.exists(path):
return {"cursor": None, "page": 0, "last_id": None, "count": 0}
with open(path) as f:
return json.load(f)Geçici dosya hilesine dikkat edin. Önce .tmp son ekli dosyaya yazıyoruz, sonra os.replace ile atomik olarak yeniden adlandırıyoruz. Bu, programın tam kontrol noktası yazılırken çökmesi durumuna karşı korur. Eski kontrol noktası bu sırada bozulmadan kalır, okunamayan yarım bir JSON'a dönüşmez.
⚠️ Dikkat: Kontrol noktasını asla geçici dosya olmadan doğrudan aynı dosyanın üzerine yazmayın. Yazmanın ortasında kopma size bozuk bir kontrol noktası bırakır ve devam ettirme imkânsız hale gelir. Atomik değiştirme bu sorunu tamamen çözer.
İpucu: Kontrol noktasını yalnızca sayfa verisi gerçekten depoya yazıldıktan sonra kaydedin. Sıra şöyle: sayfayı aldınız, veriyi yazdınız, sonra kontrol noktasını güncellediniz. Sırayı tersine çevirirseniz, kopmada bir sayfa atlar ve veri kaybedersiniz.
Kontrol noktalarıyla ana döngü
def paginate(fetch_page, save_data, cp_path, proxies):
cp = load_checkpoint(cp_path)
cursor = cp["cursor"]
count = cp["count"]
while True:
items, next_cursor = fetch_page(cursor, proxies)
if not items:
break
save_data(items)
count += len(items)
last_id = items[-1].get("id")
save_checkpoint(cp_path, next_cursor, cp["page"] + 1,
last_id, count)
cursor = next_cursor
if next_cursor is None:
break
return count✅ Kontrol: İndirmeyi başlatın, birkaç sayfa işlemesine izin verin, kesin. Kontrol noktası dosyasını açın ve içinde güncel imleç ile sayacın yazılı olduğundan emin olun. Tekrar başlatın: indirme ilk sayfadan değil, kaydedilen imleçten devam etmeli.
Adım 3: Tekrar kopya oluşturmasın diye idempotentlik
Bu aşamanın amacı. Tekrar başlatmanın veya belirli bir isteğin tekrarının deponuzda aynı kayıtların oluşmasına yol açmamasını sağlamak.
Kopyalar neden oluşur
Düşünün: bir veri sayfası aldınız, dosyaya yazdınız, ama kontrol noktası güncellenmeden program çöktü. Sonraki başlatmada aynı sayfayı tekrar isteyeceksiniz. Veri yeniden gelecek ve ikinci kez yazılacak. Kopyalar böyle doğar. Bu, kopmaların kaçınılmaz bir sonucudur ve onunla mimari düzeyinde mücadele etmek gerekir.
Tekilleştirme anahtarı
İdempotentliğin ana aracı tekilleştirme anahtarıdır. Bu, kaydı kesin olarak tanımlayan bir alan veya alan kombinasyonudur. Doğru anahtar seçimi sorunun yarısını çözer.
- Doğal ID. Kaydın kaynaktan gelen benzersiz bir tanımlayıcısı varsa onu kullanın. Bu ideal anahtardır.
- Alan kombinasyonu. Tek bir ID yoksa anahtarı birkaç kararlı alandan oluşturun. Örneğin e-posta artı kayıt tarihi.
- İçerik karması. Kararlı alan hiç yoksa tüm kaydın karmasını (hash) hesaplayın. Bu son çaredir, çünkü herhangi bir alan değişikliği yeni bir anahtar oluşturur.
UPSERT ile kopyasız yazma
Sonucu SQLite veya başka bir veritabanında saklıyorsanız, çakışmayı yok sayan ekleme kullanın. O zaman aynı anahtarla tekrar yazma hiçbir şey yapmaz.
import sqlite3
def init_db(path):
conn = sqlite3.connect(path)
conn.execute(
"CREATE TABLE IF NOT EXISTS records ("
"dedup_key TEXT PRIMARY KEY, payload TEXT)"
)
conn.commit()
return conn
def save_records(conn, items):
rows = [(item["id"], json.dumps(item)) for item in items]
conn.executemany(
"INSERT OR IGNORE INTO records (dedup_key, payload) "
"VALUES (?, ?)", rows
)
conn.commit()Buradaki kilit nokta, dedup_key alanındaki PRIMARY KEY'dir. Veritabanı aynı anahtarla tekrar eklemeyi kendisi reddeder, çünkü INSERT OR IGNORE çakışmayı sessizce yutar. Böyle bir kaydın zaten var olup olmadığını elle kontrol etmenize gerek yok. Bunu veritabanı sizin için ve hızlı bir şekilde yapar.
İpucu: Tekilleştirme anahtarını projenin başında bir kez seçin ve dokümantasyonda sabitleyin. İndirmenin ortasında anahtarı değiştirmek, eski ve yeni kayıtların eşleşmemesi ve kopyaların yine oluşması anlamına gelir. Anahtarın kararlılığı güzelliğinden daha önemlidir.
✅ Kontrol: İndirmeyi aynı veri aralığında üst üste iki kez çalıştırın. SELECT COUNT(*) FROM records komutuyla veritabanındaki satır sayısını sayın. Sayı birinci ve ikinci çalıştırmadan sonra aynı olmalı.
Adım 4: Belleği şişirmeden sonuçları tekilleştirme
Bu aşamanın amacı. Milyonlarca satırda, görülen anahtarların tümünü belleğe yükleyerek bilgisayarın belleğini doldurmadan tekrar eden kayıtları ayıklamak.
Naif yaklaşım ve sorunu
En basit tekilleştirme: bellekte görülen tüm anahtarların bir set kümesini tutmak. Her yeni kayıt için anahtarın kümede olup olmadığını kontrol etmek. Yüz binlerce satırda harika çalışır. Ama milyonlarda ve on milyonlarda küme büyür ve gigabaytlarca RAM yer. Program yavaşlar veya çöker.
Birinci çözüm: veritabanına güvenmek
Büyük hacimlerde en basit ve güvenilir yol, görüleni bellekte hiç tutmamak ve kontrolü bir önceki adımda yaptığımız gibi PRIMARY KEY üzerinden veritabanına bırakmaktır. Veritabanı indeksi sürecinizin belleğinde değil diskte tutar. On milyonlarca anahtarla, RAM'inize yük bindirmeden başa çıkar.
İkinci çözüm: kayıt karması
Doğal anahtar yoksa kaydın kompakt bir karmasını hesaplayın. Karma, kaydın boyutundan bağımsız olarak sabit ve küçük bir yer kaplar.
import hashlib
import json
def record_hash(item):
raw = json.dumps(item, sort_keys=True, ensure_ascii=False)
return hashlib.sha256(raw.encode("utf-8")).hexdigest()Burada sort_keys=True parametresi kritiktir. İçerik olarak aynı olan kayıtların, alanları farklı sırada gelseler bile aynı karmayı vermesini garanti eder. Bu sıralama olmadan iki özdeş nesne farklı karma alabilir ve farklı kayıtlar olarak geçebilir.
Üçüncü çözüm: bellek tasarrufu için Bloom filtresi
Devasa hacimlerde bellekte hızlı kontrol hâlâ gerekiyorsa Bloom filtresi kullanılır. Bu, az yer kaplayan ve bir anahtarı görüp görmediğimizi hızlıca yanıtlayan bir yapıdır. Bir özelliği vardır: bazen olmayan bir anahtar için onu gördüğümüzü yanlışlıkla söyleyebilir. Bu yüzden Bloom filtresi hızlı bir ön ayıklama olarak kullanılır, kesin kontrol veritabanına bırakılır.
- Anahtarı Bloom filtresiyle kontrol ederiz.
- Filtre kesinlikle görmedik diyorsa doğrudan veritabanına yazarız.
- Filtre muhtemelen gördük diyorsa veritabanında kesin kontrol yaparız.
⚠️ Dikkat: On milyonlarca satırı bellekte sıradan bir kümeyle tekilleştirmeye çalışmayın. Tipik bir dizüstünde bu, bellek tükenmesine ve indirmenin ortasında sürecin çökmesine yol açar. Yükü veritabanı üzerinden diske aktarın veya Bloom filtresi kullanın.
İpucu: Verileri porsiyonlar hâlinde indiriyorsanız ve tek bir porsiyon içinde kopyalar mümkünse, porsiyonu veritabanına yazmadan önce bellekte sıradan bir kümeyle tekilleştirin. Porsiyon küçüktür, bellek zarar görmez ve veritabanına daha az gereksiz ekleme gider.
✅ Kontrol: Tekilleştirmeyi kasıtlı tekrarlar içeren büyük bir test kümesinde çalıştırın. Nihai benzersiz kayıt sayısının doğru olduğunu ve sürecin bellek tüketiminin kararlı kaldığını, satır sayısıyla doğrusal olarak artmadığını kontrol edin.
Adım 5: Kayıpsız paralellik
Bu aşamanın amacı. Paralel isteklerle indirmeyi hızlandırmak, ama tek bir görevi bile kaybetmeden ve düşenleri doğru şekilde tekrarlayarak.
Görev kuyruğu
Güvenli paralelliğin temeli görev kuyruğudur. İşi önceden bağımsız parçalara bölersiniz. Örneğin sayfa listesi veya aralıklar. Bunları kuyruğa koyarsınız. Birkaç işçi kuyruktan görev alır, yürütür ve sonucu bir yere koyar. Bir işçi düşerse görevi kuyruğa geri konabilir ve başka birine verilebilir.
Eşzamanlılığı sınırlama
Sonsuz sayıda paralel istek başlatılamaz. Bu, kaynağı ve proksinizi aşırı yükler. Doğru yaklaşım, eşzamanlı işçi sayısını makul bir değerle sınırlamaktır. Küçük bir sayıyla başlayın ve kararlılığı gözlemleyerek artırın.
from concurrent.futures import ThreadPoolExecutor, as_completed
def run_parallel(tasks, worker, proxies, max_workers=5):
results = []
failed = []
with ThreadPoolExecutor(max_workers=max_workers) as pool:
future_map = {
pool.submit(worker, t, proxies): t for t in tasks
}
for future in as_completed(future_map):
task = future_map[future]
try:
results.append(future.result())
except Exception:
failed.append(task)
return results, failedDüşen görevleri tekrarlama
Toplanan failed listesi kayıp veri değil, tekrarlanması gerekenlerin listesidir. İlk turdan sonra düşen görevleri bir kez daha çalıştırırsınız. Genellikle bu, kalanı bitirmeye yeter.
def run_with_retry(tasks, worker, proxies, rounds=3):
remaining = tasks
for _ in range(rounds):
done, remaining = run_parallel(remaining, worker, proxies)
if not remaining:
break
return remainingParalellikte Proxeon proksisinin rolü. Paralel çalışmada proksi bağlantıları dağıtır, bu da indirmeyi daha kararlı ve öngörülebilir kılar. Her işçi kendi bağlantısı üzerinden çalışır ve yük tek bir noktada yoğunlaşmaz.
⚠️ Dikkat: Tek bir dosyaya veya tek bir kontrol noktasına paralel yazmada veri yarışı (race condition) oluşur. İki işçi birbirinin durumunu üzerine yazabilir. Sonuçları yalnızca işlemli veritabanına yazın veya her işçi için ayrı bir dosya kullanın, özet kontrol noktasını ayrı bir akışta toplayın.
İpucu: Görevleri küçük ve bağımsız yapın. Bir görev çok büyük bir aralığı kapsıyorsa kopması çok işi çöpe atar. Küçük görevler ucuz ve neredeyse fark edilmeden tekrarlanır.
✅ Kontrol: Paralel indirmeyi başlatın, işçilerin bir kısmını kasıtlı olarak düşürün. Tekrar turlarından sonra remaining listesi boşalmalı ve nihai veri kümesi tam olmalı. Alınan kayıt sayısını beklenenle karşılaştırın.
Adım 6: Uzun bir aradan sonra devam ettirme
Bu aşamanın amacı. Denemeler arasında çok zaman geçtiyse indirmeyi doğru şekilde sürdürmek ve bu sürede nelerin bayatlamış olabileceğini anlamak.
Zamanla neler bayatlar
Bir dakikalık kopma ile bir günlük ara farklı durumlardır. Uzun arada durumunuzun bir kısmı geçersiz hâle gelebilir.
- Oturum. Birçok hizmet oturumu sınırlı süre tutar. Uzun aradan sonra sunucu onu unutur ve istekler yetkilendirme hatası döndürmeye başlar.
- Erişim jetonu. API jetonlarının genellikle dakikalar veya saatler süren ömrü vardır. Süresi geçmiş jetonu devam etmeden önce yenilemek gerekir.
- İmleç. Bazı imleçler kısa ömürlüdür. İmleç bayatladıysa en yakın kararlı noktadan, örneğin son kaydın kimliğinden başlamak gerekir.
- Verinin kendisi. Arada kaynakta yeni kayıtlar eklenmiş veya eskiler değişmiş olabilir. Bu, offset ile sayfalı gezinmede kaymaları etkiler.
Güvenli devam ettirme stratejisi
- Başlangıçta kontrol noktasının yaşını kontrol edin. Eskiyse durumun bir kısmının geçersiz olduğuna hazır olun.
- İlk istekten önce erişim jetonunu yenileyin ve yeni bir oturum oluşturun. Eskilere güvenmeyin.
- Devam ettirmede son kaydın kimliğini sayfa numarasına tercih edin. Kimlik kararlıdır, sayfa numarası ise veri değiştiyse kayar.
- Kaydedilen imleçle bir deneme isteği yapın. Geçersiz imleç hatası döndüyse last_id ile devam ettirmeye geçin.
def resume(cp, fetch_by_id, fetch_by_cursor, proxies):
if cp["cursor"]:
try:
return fetch_by_cursor(cp["cursor"], proxies)
except CursorExpired:
pass
return fetch_by_id(cp["last_id"], proxies)ID ile devam ettirme neden daha güvenilirdir. Tarihe göre sıralıyken 50. sayfada durduğunuzu düşünün. Siz yokken başa yeni kayıtlar eklendi. Şimdi 50. sayfa tamamen farklı veri içeriyor ve bazı kayıtları atlarsınız. Son kaydın kimliğiyle devam ettirme bundan etkilenmez: yalnızca saklanan ID'den büyük olanları istersiniz.
İpucu: Kontrol noktasına her zaman hem imleci hem de son kaydın kimliğini birlikte kaydedin. İmleç daha hızlıdır ama ID, uzun arada imleç bayatlarsa güvenlik halatınızdır.
✅ Kontrol: İndirmeyi durdurun, jeton veya imlecin eskimesi için yeterince bekleyin ve tekrar başlatın. İndirici jetonu yenilemeli, bayat imleci tespit etmeli ve kayıt kaybetmeden ve kopyalamadan kimlik üzerinden devam etmeli.
Adım 7: Python ile hazır dayanıklı indirici iskeleti
Bu aşamanın amacı. Öğrenilen her şeyi, veri kaynağınıza uyarlayacağınız tek bir çalışan iskelette toplamak.
Aşağıda kontrol noktalarını, veritabanı üzerinden tekilleştirmeyi, jeton yenilemeyi ve devam ettirmeyi birleştiren bir iskelet var. Sayfa getirme fonksiyonlarını belirli API'nize göre kendiniz koyarsınız.
import os
import json
import sqlite3
import requests
class ResilientLoader:
def __init__(self, cp_path, db_path, proxies):
self.cp_path = cp_path
self.proxies = proxies
self.conn = sqlite3.connect(db_path)
self.conn.execute(
"CREATE TABLE IF NOT EXISTS records ("
"dedup_key TEXT PRIMARY KEY, payload TEXT)"
)
self.conn.commit()
def load_cp(self):
if not os.path.exists(self.cp_path):
return {"cursor": None, "last_id": None, "count": 0}
with open(self.cp_path) as f:
return json.load(f)
def save_cp(self, cp):
tmp = self.cp_path + ".tmp"
with open(tmp, "w") as f:
json.dump(cp, f)
os.replace(tmp, self.cp_path)
def save_records(self, items):
rows = [(str(i["id"]), json.dumps(i)) for i in items]
self.conn.executemany(
"INSERT OR IGNORE INTO records "
"(dedup_key, payload) VALUES (?, ?)", rows
)
self.conn.commit()
def run(self, fetch_page):
cp = self.load_cp()
while True:
items, next_cursor = fetch_page(
cp["cursor"], cp["last_id"], self.proxies
)
if not items:
break
self.save_records(items)
cp["count"] += len(items)
cp["last_id"] = items[-1]["id"]
cp["cursor"] = next_cursor
self.save_cp(cp)
if next_cursor is None:
break
return cp["count"]Kaynağınıza göre sayfa getirme fonksiyonu örneği. İstek mantığını ve yanıt ayrıştırmayı burada uygularsınız.
def fetch_page(cursor, last_id, proxies):
params = {"limit": 100}
if cursor:
params["cursor"] = cursor
elif last_id:
params["after_id"] = last_id
r = requests.get(
"https://example-source/api/records",
params=params, proxies=proxies, timeout=60
)
r.raise_for_status()
data = r.json()
return data["items"], data.get("next_cursor")Tüm mekanizmayı başlatmak basit görünür.
proxies = {
"http": os.environ["PROXEON_URL"],
"https": os.environ["PROXEON_URL"],
}
loader = ResilientLoader("state.json", "out.db", proxies)
total = loader.run(fetch_page)
print("Toplam kayıt:", total)İpucu: Döngüye her yüz kayıtta bir günlükleme ekleyin: zaman, sayaç, geçerli imleç. Böylece ilerlemeyi görürsünüz ve indirme bir noktada takılırsa kolayca anlarsınız.
✅ Kontrol: İskeleti gerçek bir kaynakta çalıştırın, ortada kesin, tekrar başlatın. Tamamlamadan sonraki nihai kayıt sayısı kaynağın toplam kayıt sayısıyla eşleşmeli ve tekrar başlatma benzersiz satır sayacını artırmamalı.
Sonucu kontrol etme: dayanıklı indirme kontrol listesi
Bu listeyi gözden geçirin. Tüm maddeler karşılanıyorsa indiriciniz gerçekten dayanıklıdır.
- Dosya devam indirmesi baştan değil, eksik kalan bayttan sürüyor.
- İndirici, sunucunun Range başlığını yok saydığı durumu doğru işliyor.
- Kontrol noktası yalnızca sonda değil, işlenen her sayfadan sonra kaydediliyor.
- Kontrol noktası geçici dosya ve yeniden adlandırma yoluyla atomik yazılıyor.
- Tekrar başlatma nihai kümede kopya oluşturmuyor.
- Tekilleştirme bellekte satır sayısıyla doğrusal büyümüyor.
- Paralel işçiler düşen görevleri kaybetmiyor ve onları tekrarlıyor.
- Uzun aradan sonra jeton yenileniyor ve bayat imleç, ID ile devam ettirmeyle değiştiriliyor.
Nasıl test edilir
- Küçük bir kümenin tam indirmesini çalıştırın ve kayıt sayısını not edin.
- Aynı kümede bir kez daha çalıştırın ve sayının değişmediğinden emin olun.
- İndirmeyi farklı yerlerde kesin: başta, ortada, sona yakın.
- Her kesintiden sonra yeniden başlatın ve sonucun aynı olduğunu kontrol edin.
Başarı göstergeleri. Benzersiz kayıt sayısı çalıştırmalar arasında kararlı. Bellek tüketimi kontrolsüz artmıyor. Devam ettirme her zaman kaydedilen noktadan sürüyor. Tamamlamadan sonra bozuk dosya yok.
Sık yapılan hatalar ve çözümleri
Sorun: tamamlamadan sonra dosya açılmıyor. Neden: sunucu 200 koduyla dosyayı baştan verdiyse de veri ab modunda eklenmiş. Çözüm: yanıt durumunu kontrol edin, 200'de dosyayı baştan tam üzerine yazma moduna geçin.
Sorun: kopmadan sonra indirme ilk sayfadan başlıyor. Neden: kontrol noktası yalnızca işin sonunda kaydedilmiş veya hiç kaydedilmemiş. Çözüm: kontrol noktasını işlenen her sayfadan sonra, veri yazımının hemen ardından kaydedin.
Sorun: kontrol noktası okunmuyor, JSON bozuk. Neden: program doğrudan hedef dosyaya yazarken çökmüş. Çözüm: geçici dosyaya yazın ve os.replace ile atomik olarak değiştirin.
Sorun: nihai kümede kopyalar var. Neden: tekilleştirme anahtarı yok veya kararsız. Çözüm: güvenilir bir anahtarda PRIMARY KEY tanımlayın ve INSERT OR IGNORE kullanın.
Sorun: büyük hacimlerde süreç bellek yetersizliğinden çöküyor. Neden: görülen tüm anahtarlar bellekteki bir kümede tutuluyor. Çözüm: benzersizlik kontrolünü veritabanına taşıyın veya Bloom filtresi uygulayın.
Sorun: uzun aradan sonra istekler yetkilendirme hatası döndürüyor. Neden: jeton veya oturum arada bayatlamış. Çözüm: her başlangıçta jetonu yenileyin ve yeni bir oturum oluşturun.
Sorun: aradan sonra bazı kayıtlar atlanmış veya ikiye katlanmış. Neden: devam ettirme sayfa numarasıyla yapılmış, ama kaynaktaki veri değişmiş. Çözüm: offset yerine son kaydın kimliğiyle devam edin.
Sorun: paralel işçiler verinin bir kısmını kaybediyor. Neden: birkaç işçi tek bir kontrol noktasına yazmış ve birbirinin üzerine yazmış. Çözüm: sonuçları ortak durum dosyasına değil, işlemli veritabanına yazın.
Ek yetenekler ve optimizasyon
Toplu yazma
Veritabanına satır satır yazmayın. Birkaç yüz kayıttan oluşan bir paket toplayın ve executemany ile bir kerede ekleyin. Bu, büyük hacimlerde yazmayı kat kat hızlandırır.
Periyodik işleme (commit)
commit'i her kayıtta değil, birkaç yüz satırda bir çağırın. Çok sık commit veritabanını yavaşlatır. Çok seyrek commit ise kopmada daha fazla veri kaybetme riski taşır. Yükünüze göre dengeyi bulun.
İlerleme raporu
Kalan süre tahmini ekleyin. Sayfa işleme hızını ve toplam kayıt sayısını bilerek ne kadar bekleneceğini kestirebilirsiniz. Bu, uzun indirmelerde kullanışlıdır.
Ham ve işlenmiş veriler için ayrı depolar
Ham yanıtları ayrıştırılmış kayıtlardan ayrı saklayın. İleride ayrıştırma mantığını değiştirirseniz verileri yeniden indirmeniz gerekmez. Ham yanıtları yeni ayrıştırıcıdan geçirmek yeter.
İpucu: Proxeon proksisini bağlantıların indirme boyunca kararlı olacağı şekilde ayarlayın. Kararlı bir bağlantı kopma sayısını azaltır, yani indiriciniz daha seyrek devam ettirmeye geçer ve daha hızlı çalışır.
SSS: dayanıklı indirmeyle ilgili sık sorulan sorular
Sunucunun dosya devam indirmeyi destekleyip desteklemediğini nasıl anlarım? Bir HEAD isteği gönderin ve Accept-Ranges başlığına bakın. bytes değeri destek anlamına gelir. Başlığın olmaması veya none olması devam indirmenin mümkün olmadığı anlamına gelir.
API imleç değil, yalnızca sayfa veriyorsa ne yapmalıyım? Sayfa numarasını ve mümkünse son kaydın kimliğini saklayın. Devam ettirmede ID'yi tercih edin, çünkü veri değişince sayfa numaraları kayar.
Kontrol noktası ne sıklıkla kaydedilmeli? Başarıyla işlenen ve yazılan her sayfadan sonra. Böylece kopmada en fazla bir sayfalık iş kaybedersiniz, tüm indirmeyi değil.
Veritabanı olmadan tekilleştirme yapılabilir mi? Küçük hacimlerde evet, bellekte sıradan bir kümeyle. Milyonlarca satırda bu, bellek nedeniyle tehlikelidir. En iyisi PRIMARY KEY'li bir veritabanı veya Bloom filtresi kullanmaktır.
Tekilleştirme anahtarı olarak ne seçilmeli? Varsa kaynağın doğal benzersiz ID'si. Yoksa kararlı alanlardan oluşan bir kombinasyon. Son çarede, anahtarları sıralanmış tüm kaydın karması.
ID ile devam ettirme neden sayfa numarasından daha güvenilirdir? Çünkü kaynaktaki veri değişebilir. Yeni kayıtlar sayfaları kaydırır ve sayfa numarasıyla veri atlar veya ikiye katlarsınız. ID bundan etkilenmez.
Kaç paralel işçi konulmalı? Küçük bir sayıyla başlayın ve kararlılığı ve kaynak limitlerini gözlemleyerek artırın. Aşırı paralellik yardımcı olmaktan çok zarar verir.
Proksi bağlantı dizesi güvenli nasıl saklanır? Kodda değil, bir ortam değişkeninde. os.environ üzerinden okuyun. Böylece parola sürüm kontrol sistemine girmez.
Uzun arada imleç bayatlarsa ne yapmalıyım? Geçersiz imleç hatasını yakalayın ve kaydedilmiş son kayıt kimliğiyle devam ettirmeye geçin.
İndirilen dosyanın bütünlüğünü kontrol etmek gerekli mi? Evet. Dosyanın gerçek boyutunu Content-Length başlığıyla karşılaştırın. Sunucu bir sağlama toplamı (checksum) veriyorsa onu da kontrol edin.
Sonuç: artık neler yapabiliyorsunuz ve nereye ilerlemeli
İlk kopmada çöken kırılgan bir indirmeden dayanıklı bir indiriciye giden yolu kat ettiniz. Artık kopmanın felaket olmaktan çıkıp sıradan bir çalışma durumu olması için tüm araçlara sahipsiniz.
Neleri öğrendiniz. Accept-Ranges kontrolüyle Range başlığı üzerinden dosya devam indirmeyi. Atomik kontrol noktalarıyla ilerlemeyi kaydetmeyi. Tekilleştirme anahtarı düzeyinde idempotentliği. Milyonlarca satırda belleği şişirmeden tekilleştirmeyi. Düşen görevlerin tekrarlandığı paralelliği. Jetonları yenileyerek ve bayat imleçleri değiştirerek uzun aradan sonra devam ettirmeyi. Ve en önemlisi, tüm bunları birleştiren hazır bir Python iskeletini.
Bundan sonra ne yapmalı. Gerçek veri kaynağınızı alın ve sayfa getirme fonksiyonunu ona uyarlayın. Küçük bir hacimle başlayın, kesintilerde devam ettirmeyi hata ayıklayın, sonra ölçeklendirin. Bağlantıların indirme boyunca öngörülebilir olması için kararlı bir Proxeon proksisi ayarlayın.
Nereye gelişmeli. Toplu yazmayı ve Bloom filtrelerini daha derinlemesine inceleyin. İlerleme izleme ve süre tahmini ekleyin. Ham ve işlenmiş veri depolamayı ayırın. Kademeli