Scraper'ınız on sayfayı dolaşıyor ve yüz temiz kayıt yazdırıyor. Ertesi sabah betiği yeniden çalıştırıyorsunuz; CSV dosyasında artık iki yüz satır var ve yarısı tekrar. Bir ekip arkadaşınız dosyayı Excel'de açıyor, her kıvrık tırnak “ olarak görünüyor. Bir hafta sonra iş yazma sırasında çöküyor ve geride hiçbir ayrıştırıcının açamadığı bir JSON dosyası kalıyor. Bunların hiçbiri kazıma sorunu değil; hepsini betiğin sonundaki birkaç kayıt satırı belirliyor.
Bu rehber, Python scraper'larının en sık yazdığı üç biçimi ele alıyor: csv modülü ve pandas ile CSV, JSON ve JSON Lines, çalıştırmalar arasındaki tekrarları upsert ile ayıklayan SQLite. Dosyaya nasıl güvenle ekleme yapılacağını, Excel'in hangi kodlamayı beklediğini, her biçimin ne kadar yer kapladığını ve SQLite'tan PostgreSQL'e ne zaman geçileceğini göreceksiniz. Bütün örnekleri 28 Eylül 2026'da, kazıma alıştırması için kurulmuş bir deneme sitesi olan quotes.toscrape.com üzerinde Python 3.13, Requests 2.34, beautifulsoup4 4.15, pandas 3.0 ve SQLite 3.50 ile çalıştırdık. Ayrıştırma tarafını BeautifulSoup rehberinde anlattık; bu yazı, satırlar elinize geçtikten sonrasını ele alıyor.
Kazınan veriyi saklamak için hangi seçenekler var?
İki aile var. Düz dosyalar (CSV, JSON, JSON Lines, XLSX) baştan sona yazılan tek dosyalardır. Sunucu gerektirmezler ve alışılmış araçlarla açılırlar, ama tekrarın ne olduğunu bilmezler. Veritabanları (SQLite, PostgreSQL, MySQL) satırları anahtarları ve kısıtları olan tablolarda tutar: tekrarları sizin yerinize birleştirir, sorulara SQL ile cevap verir ve yazma sırasında yaşanan bir çökmeden sağlam çıkar.
SQLite ikisinin arasında durur. Bir veritabanıdır, ama veritabanının tamamı diskteki sıradan bir dosyadır ve Python sqlite3 modülünü standart kütüphanesiyle birlikte getirir. Tek makinede çalışan bir scraper için bu birleşimin rakibi azdır.
Bir scraper satırlarını nasıl kaydeder?
Biçim ne olursa olsun, düzgün çalışan bir scraper aynı beş adımdan geçer:
- Her kaydı bir sözlüğe (dict) dönüştürün ve her seferinde aynı anahtarları kullanın:
quote_id,text,author,tagsve bir zaman damgası. - Her kayda değişmeyen bir anahtar verin. Sitenin kendi kimliği varsa onu kullanın (ürün SKU'su, URL'deki makale numarası gibi). quotes.toscrape.com'da böyle bir kimlik yok; bu yüzden yazar adıyla alıntı metninin hash'ini alıp 16 karakterlik bir anahtar üretiyoruz.
- Yazma kipini seçin. Dosyanın üzerine yazın (yeni bir anlık görüntü), dosyaya ekleyin (büyüyen bir kayıt defteri) ya da tabloya upsert yapın ("varsa güncelle, yoksa ekle": anahtar başına tek satır, yerinde güncellenir).
- Tek adımda yazın. Ya bütün parti yazılır ya hiçbiri: bir veritabanı işlemi (transaction) ya da tamamlandığında eski dosyanın yerine geçen geçici bir dosya.
- Geri okuyun. Dosyaya güvenmeden önce onu, veriyi kullanacak kişinin ya da programın okuyacağı araçla açın (Excel, pandas,
json.loads).
Betiklerin çoğu 2. adımı atlar; tekrarlar da buradan doğar.
CSV, JSON Lines ve SQLite karşılaştırması
| CSV | JSON (tek dizi) | JSON Lines | SQLite | |
|---|---|---|---|---|
| İç içe alanlar (etiket listeleri) | Düzleştirilir, örneğin love|life | Doğal olarak | Doğal olarak | Bir sütunda JSON metni, json_each ile sorgulanır |
| Her çalıştırmada ekleme | Evet, başlık bir kez yazılır | Hayır, kapanıştaki ] yeniden yazılmalı | Evet, kayıt başına bir satır | Evet, upsert ile |
| Tekrarları ayıklama | Hayır | Hayır | Hayır | Evet, birincil anahtara göre |
| Excel'de açılma | Evet, UTF-8 BOM ile | Hayır | Hayır | Hayır (önce dışa aktarılır) |
| Yazma sırasında çökmeye dayanma | Son satır kesilebilir | Dosya okunamaz hâle gelebilir | Yalnız son satır kaybolur | Evet, işlemler geri alınır |
| Tamamını yüklemeden sorgulama | Hayır | Hayır | Satır satır | Evet, SQL ve indekslerle |
| 100 alıntımızın boyutu | 25,9 KB | 42,4 KB (girintili) | 35,8 KB | 49,2 KB (bir indeksle) |
Boyutlar kendi test çalıştırmamızdan. SQLite burada en büyük dosya, çünkü veriyi sabit 4 KB'lık sayfalarda tutuyor (bu tablo ve indeksi için 12 sayfa); tablo büyüdükçe bu ek yük azalır. Karşılaştırma için: aynı veri XLSX olarak 17,0 KB tuttu, çünkü XLSX sıkıştırılmış bir biçimdir.
Kazınan veri Python'da CSV'ye nasıl kaydedilir?
Standart kütüphanedeki csv modülü tırnaklamayı, alan içindeki virgülleri ve tırnak içindeki satır sonlarını kendisi halleder. DictWriter her sözlüğü sütun adlarına göre bir satıra yerleştirir:
import csv
from pathlib import Path
FIELDS = ["quote_id", "text", "author", "author_url", "tags", "page", "scraped_at"]
def append_csv(rows, path: Path) -> None:
new_file = not path.exists() or path.stat().st_size == 0
with path.open("a", newline="", encoding="utf-8-sig" if new_file else "utf-8") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS)
if new_file:
writer.writeheader()
for row in rows:
writer.writerow({**row, "tags": "|".join(row["tags"])})Bu fonksiyondaki üç ayrıntı, CSV ile ilgili olağan şikâyetlerin önüne geçer:
newline="". Python csv belgeleri bunu hem okuyucular hem yazıcılar için istiyor. Bu parametre olmazsa tırnak içindeki satır sonları yanlış okunur, Windows'ta da her satır fazladan bir\rile biter; birçok okuyucu bunu her kaydın ardından boş bir satır olarak gösterir.- Yalnız ilk yazmada
utf-8-sig. Bu kodek dosyanın başına bir bayt sırası işareti (BOM) yazar (EF BB BF); codecs belgeleri bunu Microsoft'un kullandığı UTF-8 çeşidi olarak tanımlıyor. Excel bu işareti görünce dosyayı UTF-8 olarak çözer ve\u201ckarakteri\u201colarak kalır. Betik sonraki çalıştırmalarda dosyaya eklerken düzutf-8'e geçer; aksi hâlde dosyanın ortasına ikinci bir işaret düşer. Test dosyamızda iki çalıştırmadan sonra tam olarak bir işaret vardı. - Başlık yalnız dosya yeniyken. Her çalıştırmada başlık eklemek, verinin arasında başıboş
quote_id,text,...satırları bırakır.
Etiketler, değerlerde hiç geçmeyen | ayırıcısıyla birleştiriliyor.
BOM'lu bir dosyayı okurken yine encoding="utf-8-sig" kullanın. Düz utf-8 ile ilk sütunun adı '\ufeffquote_id' olarak gelir ve row["quote_id"] bir KeyError fırlatır. Testte biz de buna takıldık; diğer kodlama tuzaklarını Python Unicode kodlama hataları yazısında bulabilirsiniz.
pandas'ta aynı dosyayı yazmak tek satır sürer; drop_duplicates de içinde tekrarlar biriktirmiş bir CSV ya da JSON Lines dosyasını düzeltir:
import pandas as pd
df = pd.read_json("data/quotes.jsonl", lines=True, convert_dates=False)
df = df.drop_duplicates(subset="quote_id", keep="last")
df.assign(tags=df["tags"].str.join("|")).to_csv(
"data/quotes_clean.csv", index=False, encoding="utf-8-sig")
df.assign(tags=df["tags"].str.join(", ")).to_excel(
"data/quotes.xlsx", index=False, sheet_name="quotes") # openpyxl gerekirMicrosoft'un belirtim ve sınırlar sayfasına göre bir Excel çalışma sayfası en fazla 1.048.576 satır alır. CSV dosyasının böyle bir sınırı yoktur, ama Excel ondan yalnız bu kadar satırı gösterir.
Kazınan veri JSON ya da JSON Lines olarak nasıl kaydedilir?
Başka bir programın tek seferde yüklediği bir anlık görüntü için tek bir JSON dizisi uygundur. Dosyayı ensure_ascii=False ile yazın ki İngilizce dışındaki metin \u201c kaçış dizilerine dönüşmeden okunabilir kalsın. Yazmayı da atomik yapın: veriyi aynı klasördeki geçici bir dosyaya döküp os.replace ile yerine koyun. İşlem yarıda kesilirse eski dosya sağlam kalır.
import json, os, tempfile
from pathlib import Path
def write_json_atomic(data, path: Path) -> None:
fd, tmp = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
os.replace(tmp, path)JSON dizisinin zayıf noktası eklemedir. Dosya ] ile bittiği için tek bir kayıt eklemek bile bütün dosyayı okuyup yeniden yazmayı gerektirir. JSON Lines bu sorunu ortadan kaldırır: her satır tam bir JSON değeridir, dosya UTF-8'dir ve satırlar \n ile biter. Ekleme sıradan bir yazma işlemidir ve bir çökme en fazla son satırı bozabilir:
def append_jsonl(rows, path: Path) -> None:
with path.open("a", encoding="utf-8") as f:
for row in rows:
f.write(json.dumps(row, ensure_ascii=False) + "\n")pandas bu dosyayı pd.read_json(path, lines=True) ile okur; chunksize= parametresi büyük bir dosyayı parça parça işlemenizi sağlar. Testimizde bir sürprizle karşılaştık: read_json belgelerine göre adı _at ya da _time ile biten sütunlar varsayılan olarak tarih diye ayrıştırılıyor. scraped_at sütunumuz saat dilimi bilgisi taşıyan bir datetime'a dönüştü, ardından to_excel() "Excel does not support datetimes with timezones" (Excel saat dilimli tarihleri desteklemiyor) hatasıyla durdu. Metnin olduğu gibi kalmasını istiyorsanız convert_dates=False verin.
Bir dosya JSONDecodeError ile yüklenmeyi reddediyorsa olağan nedenleri (yarım kalmış bir yazma, tek dosyada iki dizi, tek belge gibi okunan JSON Lines) JSONDecodeError: Expecting value yazısında anlattık.
Kazınan veri SQLite'ta tekrarsız nasıl saklanır?
Değişmeyen anahtarı birincil anahtar olarak tanımlayın ve ekleme mi güncelleme mi yapılacağına veritabanı karar versin. SQLite 3.24.0 sürümünden beri bunu upsert yan tümcesiyle destekliyor. Upsert, "update" ve "insert" sözcüklerinden türemiştir ve "varsa güncelle, yoksa ekle" demektir; DO UPDATE bölümündeki özel excluded. öneki, reddedilen eklemenin yazmaya çalıştığı değerleri gösterir (SQLite UPSERT belgeleri).
CREATE TABLE IF NOT EXISTS quotes (
quote_id TEXT PRIMARY KEY,
text TEXT NOT NULL,
author TEXT NOT NULL,
author_url TEXT,
tags TEXT, -- JSON dizisi
first_seen TEXT NOT NULL,
last_seen TEXT NOT NULL
);
INSERT INTO quotes (quote_id, text, author, author_url, tags, first_seen, last_seen)
VALUES (:quote_id, :text, :author, :author_url, :tags, :scraped_at, :scraped_at)
ON CONFLICT(quote_id) DO UPDATE SET
tags = excluded.tags,
author_url = excluded.author_url,
last_seen = excluded.last_seen;first_seen yalnız satır yeniyken yazılır; last_seen her çalıştırmada ileri taşınır. Bu ikili, ek bir iş gerektirmeden artımlı bir geçmiş tutar: last_seen değeri son çalıştırmadan eski olan bir satır siteden kalkmıştır, first_seen değeri son çalıştırmaya eşit olan satır ise yenidir. Fiyat takip araçları ve değişiklik izleyiciler tam olarak bu kalıp üzerine kurulur.
Aşağıdaki tam betiği iki kez çalıştırdığımızda şu çıktıyı aldık:
scraped 100 rows: 100 new, 0 already in the database
scraped 100 rows: 0 new, 100 already in the databaseAynı iki çalıştırmanın CSV ve JSON Lines dosyalarında 200'er kayıt vardı. Veritabanında ise 100.
Veri SQLite'a girdikten sonra sorular sorguya dönüşür. Aşağıdakileri test veritabanımızda çalıştırdık:
import sqlite3
con = sqlite3.connect("data/quotes.db")
# En çok alıntısı olan yazarlar
for author, n in con.execute(
"SELECT author, COUNT(*) AS n FROM quotes GROUP BY author ORDER BY n DESC LIMIT 3"):
print(author, n)
# "love" etiketli alıntılar (etiketler JSON dizisi olarak saklanıyor)
print(con.execute("""
SELECT COUNT(*) FROM quotes, json_each(quotes.tags)
WHERE json_each.value = 'love'""").fetchone()[0])
# Son çalıştırmada sitede olmayan satırlar
print(con.execute("""
SELECT COUNT(*) FROM quotes
WHERE last_seen < (SELECT MAX(last_seen) FROM quotes)""").fetchone()[0])
con.close()Albert Einstein 10
J.K. Rowling 9
Marilyn Monroe 7
14
0Analiz için pd.read_sql_query(sql, con) sonucu bir DataFrame olarak döndürür. Bunun ardından genellikle gelen temizlik adımlarını kazınan veriyi pandas ile temizleme yazısında anlatıyoruz.
Tam betik: kazı, sonra CSV, JSON Lines ve SQLite'a yaz
Betik, sitenin "Next" bağlantısını kaybolana kadar izler; 429 ve 5xx yanıtlarında üstel bekleme (exponential backoff) ile yeniden dener (urllib3'ün Retry sınıfı Retry-After başlığına da uyar), sayfalar arasında bir saniye bekler ve last_seen karşılaştırmaları doğru çalışsın diye her çalıştırmaya tek bir zaman damgası yazar. quotes.toscrape.com'da 10 sayfa ve 100 alıntı var.
pip install requests beautifulsoup4 lxml pandas openpyxl"""quotes.toscrape.com'u kazır, satırları CSV, JSON Lines ve SQLite'a kaydeder."""
import csv
import hashlib
import json
import sqlite3
import time
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
BASE = "https://quotes.toscrape.com/"
FIELDS = ["quote_id", "text", "author", "author_url", "tags", "page", "scraped_at"]
def make_session() -> requests.Session:
retry = Retry(total=4, backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET"])
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry))
session.headers["User-Agent"] = "quotes-storage-demo/1.0 (contact: you@example.com)"
return session
def quote_key(text: str, author: str) -> str:
# Sitede alıntı kimliği yok; bu yüzden içerikten değişmeyen bir anahtar türetiyoruz.
return hashlib.sha1(f"{author}\n{text}".encode("utf-8")).hexdigest()[:16]
def scrape(session: requests.Session, delay: float = 1.0):
url, page = BASE, 1
run_at = datetime.now(timezone.utc).isoformat(timespec="seconds") # çalıştırma başına tek zaman damgası
while url:
resp = session.get(url, timeout=20)
resp.raise_for_status()
soup = BeautifulSoup(resp.content, "lxml")
for q in soup.select("div.quote"):
text = q.select_one("span.text").get_text(strip=True)
author = q.select_one("small.author").get_text(strip=True)
yield {
"quote_id": quote_key(text, author),
"text": text,
"author": author,
"author_url": urljoin(BASE, q.select_one("span a")["href"]),
"tags": [t.get_text(strip=True) for t in q.select("a.tag")],
"page": page,
"scraped_at": run_at,
}
nxt = soup.select_one("li.next a")
url = urljoin(url, nxt["href"]) if nxt else None
page += 1
time.sleep(delay)
def append_csv(rows, path: Path) -> None:
new_file = not path.exists() or path.stat().st_size == 0
with path.open("a", newline="", encoding="utf-8-sig" if new_file else "utf-8") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS)
if new_file:
writer.writeheader()
for row in rows:
writer.writerow({**row, "tags": "|".join(row["tags"])})
def append_jsonl(rows, path: Path) -> None:
with path.open("a", encoding="utf-8") as f:
for row in rows:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
SCHEMA = """
CREATE TABLE IF NOT EXISTS quotes (
quote_id TEXT PRIMARY KEY,
text TEXT NOT NULL,
author TEXT NOT NULL,
author_url TEXT,
tags TEXT, -- JSON dizisi
first_seen TEXT NOT NULL,
last_seen TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_quotes_author ON quotes(author);
"""
UPSERT = """
INSERT INTO quotes (quote_id, text, author, author_url, tags, first_seen, last_seen)
VALUES (:quote_id, :text, :author, :author_url, :tags, :scraped_at, :scraped_at)
ON CONFLICT(quote_id) DO UPDATE SET
tags = excluded.tags,
author_url = excluded.author_url,
last_seen = excluded.last_seen
"""
def save_sqlite(rows, path: Path) -> tuple[int, int]:
con = sqlite3.connect(path)
try:
con.executescript(SCHEMA)
before = con.execute("SELECT COUNT(*) FROM quotes").fetchone()[0]
with con: # tek işlem: başarılıysa commit, hata olursa geri alma
con.executemany(UPSERT, [{**r, "tags": json.dumps(r["tags"])} for r in rows])
after = con.execute("SELECT COUNT(*) FROM quotes").fetchone()[0]
return after - before, len(rows) - (after - before)
finally:
con.close()
if __name__ == "__main__":
out = Path("data")
out.mkdir(exist_ok=True)
rows = list(scrape(make_session()))
append_csv(rows, out / "quotes.csv")
append_jsonl(rows, out / "quotes.jsonl")
new, seen = save_sqlite(rows, out / "quotes.db")
print(f"scraped {len(rows)} rows: {new} new, {seen} already in the database")with con: upsert'i tek bir işleme (transaction) sarar: satırlardan biri başarısız olursa hiçbiri yazılmaz. Bağlantıyı kapatmaz; bu yüzden kapatma işini finally bloğu yapar. Adlandırılmış yer tutucular (:quote_id), executemany'nin CSV yazıcısının kullandığı sözlüklerin aynısını almasını sağlar ve kesme işaretlerinin SQL'i bozmasını önler.
Gerçek bir sitede aynı yapıyı koruyup seçicileri, anahtarı ve bekleme süresini değiştirin. Önce sitenin kullanım koşullarını ve robots.txt dosyasını kontrol edin (robots.txt dosyası nedir), resmî bir API varsa onu kullanın. Bir işi HTTP 429 hatalarından uzak tutan da aynı bir saniyelik bekleme ve yeniden deneme ayarlarıdır. Scraper, isteklerini birkaç çıkış IP'sine dağıtan zamanlanmış bir crawler'a dönüştüğünde Rotating Proxy ve Residential Proxy, proxies ayarı üzerinden aynı requests.Session nesnesine bağlanır; kayıt kodu değişmez.
SQLite'tan PostgreSQL'e ne zaman geçilmeli?
SQLite projesinin kendi rehberi sınırlar konusunda açık: veritabanı dosyası başına aynı anda tek bir yazıcıyı destekler ve veri ile uygulama farklı makinelerde duruyorsa yanlış seçimdir. Bir kazıma projesinde bu, üç işarete karşılık gelir:
- Birden fazla işçi aynı anda yazıyor. Üç sunucudaki crawler süreçleri bir veritabanı sunucusuna ihtiyaç duyar; tek makinedeki birkaç süreç ise sırayla yazabilir.
- Veritabanına ağ üzerinden erişilmesi gerekiyor. Ayrı sunucularda çalışan bir pano, bir API ve bir scraper, ağ paylaşımındaki bir
.dbdosyasını değil PostgreSQL'i paylaşmalıdır. - Başka kişilere yetki vermeniz gerekiyor. Kullanıcılar, roller ve salt okunur erişim sunucu özellikleridir.
Boyut tek başına nadiren belirleyici olur: belgelenen üst sınır yaklaşık 281 TB, yani tek bir scraper'ın üreteceğinin çok üzerinde. Geçişin kendisi de küçüktür. PostgreSQL aynı INSERT ... ON CONFLICT (key) DO UPDATE söz dizimini EXCLUDED ile birlikte kullanır; yukarıdaki upsert neredeyse hiç değişmeden taşınır. Birkaç aşamaya büyüyen veri hatlarını ETL nedir? yazısında ele alıyoruz.
Kullanım alanları
- Fiyat izleme: SKU ve tarihle anahtarlanmış, ürün başına günde bir satır; fiyat takibinin ve rakip fiyat takibinin temelini oluşturur.
- Değişiklik tespiti:
first_seenvelast_seenyeni ve kaldırılmış öğeleri gösterir; web sitesi değişiklik izlemenin özü budur. - Araştırma veri setleri: JSON Lines dosyaları analistlere kolayca teslim edilir ve pazar araştırması için not defterlerine (notebook) yüklenir.
- Büyük taramalar: binlerce URL ziyaret eden bir web crawler, sıradaki URL listesini ve sonuçlarını SQLite'ta tutabilir; Python ile web crawler yazma yazısında olduğu gibi.
- Düzenli kazıma işleri: dünün satırlarını çoğaltmaması gereken zamanlanmış veri kazıma çalıştırmaları.
Yaygın hatalar
- Değişmeyen bir anahtar olmaması. Anahtar yoksa her çalıştırma veri setinin tamamını yeniden ekler. Sitenin kimliğini kullanın ya da bir kaydı tanımlayan alanlardan hash üretin.
- CSV dosyalarını
newline=""olmadan açmak. Windows'ta fazladan\rkarakterleri ve bozulan çok satırlı alanlar. - Excel'de açılacak bir CSV için düz
utf-8. Aksanlı harfler ve kıvrık tırnaklarégibi dizilere dönüşür. - Çıktı dosyasının üzerine doğrudan yazmak. Bir çökme geride yarım bir dosya bırakır. Geçici bir dosyaya yazın ve
os.replaceile yerine koyun. - SQLite'ta her satır için ayrı commit. Her commit diske eşitleme yapar. Bir partiyi
executemanyile tek bir işleme sarın. - SQL'i f-string ile kurmak. Kesme işareti içeren bir alıntı ifadeyi bozar. Yer tutucu kullanın.
- Büyük bir taramada her şeyi bellekte tutmak. Sayfa ya da parti başına yazın; böylece bir çökme çalıştırmanın tamamını değil, yalnız bir sayfayı kaybettirir.
Karar rehberi
| İhtiyaç | Öneri |
|---|---|
| Bir ekip arkadaşının Excel'de açacağı dosya | utf-8-sig ile CSV ya da pandas ile XLSX |
| Başka bir program için iç içe kayıtlar | Atomik olarak yazılmış JSON (tek dizi) |
| Her çalıştırmanın eklendiği bir kayıt defteri | JSON Lines |
| Tekrar üretmeyen düzenli çalıştırmalar | Birincil anahtar ve upsert ile SQLite |
| Geçmiş: her öğe ilk ve son ne zaman görüldü | first_seen ve last_seen ile SQLite |
| Aynı anda yazan birden fazla makine | Aynı ON CONFLICT upsert'iyle PostgreSQL |
| Anlık analiz | Yukarıdakilerden herhangi biri, pandas'a yüklenmiş olarak |
Sıkça sorulan sorular
Kazınan veriyi kaydetmek için en uygun biçim hangisi?
Veriyi kimin okuyacağına bağlı: tablo programları için CSV, iç içe kayıtlar ve sürekli eklenen kayıt defterleri için JSON Lines, satırları çoğaltmaması gereken zamanlanmış scraper'lar için SQLite. Birçok proje SQLite'ı asıl kaynak olarak tutar ve insanlar için CSV dışa aktarır.
Kazıdığım CSV Excel'de neden bozuk görünüyor?
CSV dosyasında bayt sırası işareti yoksa Windows'taki Excel dosyayı çoğu zaman UTF-8 yerine sistemin eski kod sayfasıyla çözer; bu yüzden çok baytlı her karakter iki ya da üç yanlış karaktere dönüşür. Dosyayı encoding="utf-8-sig" ile yazın ya da Excel'in veri içe aktarma penceresinden açıp UTF-8'i seçin.
Kazınan veriyi mevcut bir CSV'ye başlığı tekrarlamadan nasıl eklerim?
Dosyayı "a" kipinde açın ve başlığı yalnız dosya yeni ya da boşken yazın; yukarıdaki append_csv fonksiyonu bunu yapıyor. Ekleme tekrarları ayıklamaz; bunun için dosyayı pandas ile yükleyip drop_duplicates çağırın ya da veriyi bir anahtarla SQLite'ta saklayın.
JSON ile JSON Lines arasındaki fark nedir?
Bir JSON dosyası tek bir değer, genellikle bir kayıt dizisi tutar; bu yüzden bir bütün olarak okunup yazılması gerekir. JSON Lines her satırda bir JSON değeri tutar; böylece tek bir yazma işlemiyle kayıt ekleyebilir, büyük bir dosyayı satır satır okuyabilirsiniz.
SQLite büyük bir kazıma projesini kaldırabilir mi?
Tek makinede genellikle evet; boyut sınırı bir scraper'ın ürettiğinin çok üzerindedir. Asıl önemli sınır eş zamanlılıktır: dosya başına aynı anda tek bir yazıcı. Birden fazla makine aynı anda yazıyorsa PostgreSQL gerekir.
Scraper'ı yeniden çalıştırdığımda tekrarlanan satırları nasıl önlerim?
Her kayda değişmeyen bir anahtar verin, onu SQLite'ta birincil anahtar yapın ve ON CONFLICT(key) DO UPDATE ile ekleyin. Düz dosyalarda veriyi yükledikten sonra drop_duplicates(subset="key") ile tekrarları ayıklayın.
Özet
Bir scraper'ın ilk çalıştırmadan sonra da işe yarayıp yaramayacağını kayıt biçimi belirler. utf-8-sig ve newline="" ile yazılmış CSV, veriyi tablo programlarına teslim etmek için uygundur; JSON Lines ise yapılandırılmış kayıtları güvenle eklemenin en basit yoludur. Değişmeyen bir anahtar ve upsert ile kurulan SQLite, tekrarlanan çalıştırmaları ilk ve son görülme geçmişi olan, sorgulanabilir tek bir tabloya dönüştürür; birden fazla makine aynı anda yazmaya başladığında bayrağı PostgreSQL devralır. Çalıştırmalarınız, veriyi ölçülü bir hızla toplamak için tek bir IP adresinin yetmediği noktaya geldiyse Proxynet'in proxy paketlerine göz atın.




