Kazınan Veri CSV, JSON ve SQLite'a Nasıl Kaydedilir?

Yayın tarihi:

12 dk okuma

Acar Diveroli
Yazar: Acar Diveroli
Ortadaki kazınmış kayıt bloğu CSV, JSON, SQLITE ve XLSX yazan dört karta bağlı; SQLite bağlantısı mavi.

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:

  1. Her kaydı bir sözlüğe (dict) dönüştürün ve her seferinde aynı anahtarları kullanın: quote_id, text, author, tags ve bir zaman damgası.
  2. 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.
  3. 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).
  4. 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.
  5. 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ı

CSVJSON (tek dizi)JSON LinesSQLite
İç içe alanlar (etiket listeleri)Düzleştirilir, örneğin love|lifeDoğal olarakDoğal olarakBir sütunda JSON metni, json_each ile sorgulanır
Her çalıştırmada eklemeEvet, başlık bir kez yazılırHayır, kapanıştaki ] yeniden yazılmalıEvet, kayıt başına bir satırEvet, upsert ile
Tekrarları ayıklamaHayırHayırHayırEvet, birincil anahtara göre
Excel'de açılmaEvet, UTF-8 BOM ileHayırHayırHayır (önce dışa aktarılır)
Yazma sırasında çökmeye dayanmaSon satır kesilebilirDosya okunamaz hâle gelebilirYalnız son satır kaybolurEvet, işlemler geri alınır
Tamamını yüklemeden sorgulamaHayırHayırSatır satırEvet, SQL ve indekslerle
100 alıntımızın boyutu25,9 KB42,4 KB (girintili)35,8 KB49,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:

python
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 \r ile 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 \u201c karakteri \u201c olarak kalır. Betik sonraki çalıştırmalarda dosyaya eklerken düz utf-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:

python
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 gerekir

Microsoft'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.

python
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:

python
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).

sql
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:

text
scraped 100 rows: 100 new, 0 already in the database
scraped 100 rows: 0 new, 100 already in the database

Aynı 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:

python
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()
text
Albert Einstein 10
J.K. Rowling 9
Marilyn Monroe 7
14
0

Analiz 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.

bash
pip install requests beautifulsoup4 lxml pandas openpyxl
python
"""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 .db dosyası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_seen ve last_seen yeni 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 \r karakterleri 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.replace ile yerine koyun.
  • SQLite'ta her satır için ayrı commit. Her commit diske eşitleme yapar. Bir partiyi executemany ile 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ğı dosyautf-8-sig ile CSV ya da pandas ile XLSX
Başka bir program için iç içe kayıtlarAtomik olarak yazılmış JSON (tek dizi)
Her çalıştırmanın eklendiği bir kayıt defteriJSON Lines
Tekrar üretmeyen düzenli çalıştırmalarBirincil 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 makineAynı ON CONFLICT upsert'iyle PostgreSQL
Anlık analizYukarı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.

ChatGPT'ye sorClaude'a sor