---
title: "JSONDecodeError: Expecting Value Hatası Nasıl Çözülür?"
description: "JSONDecodeError: Expecting Value, gövdenin ilk karakterinde JSON olmadığını söyler: yanıt boş, HTML ya da düz metindir. Nedenini ve çözümünü anlatıyoruz."
url: https://proxynet.io/tr/blog/jsondecodeerror-expecting-value
date: 2026-09-24
author: "Acar Diveroli"
category: "Nasıl Yapılır, Web Scraping"
lang: tr
---

# JSONDecodeError: Expecting Value Hatası Nasıl Çözülür?

Bir mağazanın ürün listesini, tarayıcının Network panelinde bulduğunuz `/api/products?page=1` adresinden, yani sitenin veriyi JSON olarak veren endpoint'inden topluyorsunuz. Betik ilk 300 sayfayı sorunsuz geçiyor, sonra `data = r.json()` satırında `requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)` hatasıyla duruyor. Adres aynı, kod aynı. Araya tek bir `print(r.status_code, r.headers.get("Content-Type"), r.text[:200])` satırı ekleyince tablo değişiyor: `403`, `text/html` ve `<!DOCTYPE html>` ile başlayan bir sayfa. Sunucu JSON yerine bir web sayfası göndermiş, JSON ayrıştırıcısı (parser) da ilk karakterde, `<` işaretinde vazgeçmiş.

Bu yazıda mesajın ve içindeki konum bilgisinin ne anlama geldiğini, hangi Python kütüphanesinin hangi istisnayı (exception) fırlattığını ve nedeni bulan üç satırlık kontrolü anlatıyoruz. Ardından boş yanıtları, HTML sayfalarını, proxy'nin `407` cevabını ve yalnız JSON'a benzeyen gövdeleri ele alıyor, sonda denenmiş bir `parse_json()` yardımcı fonksiyonu veriyoruz.

> **Not: Kısa cevap**
>
> `JSONDecodeError: Expecting value: line 1 column 1 (char 0)`, Python'un JSON ayrıştırıcısının metnin ilk karakterinde bir JSON değeri bulamadığı anlamına gelir. Bağlantı kurulmuştur; sorun yanıtın gövdesinde (body), yani sunucunun gönderdiği içeriktedir. Gövde ya boştur (`204`, bir `HEAD` isteği, boş bir `200`) ya da JSON değildir: bir engel sayfası, giriş sayfasına yönlendirme, sunucu hata sayfası, proxy'nin `407` sayfası, düz metin bir hız sınırı (rate limit) cevabı ya da JSONP. `r.json()` çağırmadan önce `r.status_code`, `r.headers.get("Content-Type")` ve `r.text[:200]` değerlerine bakın. Durum kodu `2xx` değilse ya da tür `application/json` değilse ayrıştırmayın; önce sunucunun neden başka bir şey gönderdiğini bulun.

## JSONDecodeError: Expecting value ne anlama gelir?

Ayrıştırıcı bir JSON değerinin başlamasını bekliyordu, ama başka bir şeyle karşılaştı. [RFC 8259](https://www.rfc-editor.org/rfc/rfc8259) tanımına göre bir JSON metni tek bir değerdir: bir nesne, bir dizi, çift tırnak içinde bir dize (string), bir sayı, `true`, `false` ya da `null`. Bu yüzden geçerli bir metin, baştaki isteğe bağlı boşluklardan sonra yalnız `{`, `[`, `"`, bir rakam, `-`, `t`, `f` ya da `n` ile başlayabilir. Ayrıştırıcı bir HTML sayfasının `<` işaretine, `Too Many Requests` metninin `T` harfine ya da boş bir dizenin sonuna rastladığında "Expecting value" diyerek durur.

Bu bir bağlantı hatası değildir. Sunucuya hiç ulaşamayan bir istek daha önce, `requests.get()` içinde, `ConnectionError` ya da `ProxyError` gibi hatalarla düşer ([Max Retries Exceeded With URL](/tr/blog/max-retries-exceeded-with-url)). `JSONDecodeError` görüyorsanız bir yanıt gelmiştir; yalnız içeriği JSON olarak okunamamıştır.

## "line 1 column 1 (char 0)" size ne söyler?

Sayılar ayrıştırmanın nerede başarısız olduğunu gösterir. `json.JSONDecodeError` bunları nitelik (attribute) olarak taşır: `msg` (neden), `doc` (metnin tamamı), `pos` (hatanın oluştuğu karakterin sıra numarası), `lineno` ve `colno` ([Python json dokümantasyonu](https://docs.python.org/3/library/json.html#json.JSONDecodeError)). `char 0` gövdenin ilk karakteridir; yani hiçbir JSON başlamamıştır.

Başka konumlar daha fazlasını söyler:

- `line 1 column 4 (char 3)`: gövde yalnız üç boşluktan oluşuyor. Boşluklar atlanır, sonra metin biter.
- `line 2 column 1 (char 1)`: gövde bir satır sonuyla başlıyor ve ardından JSON olmayan bir şey geliyor; çoğu zaman bir HTML sayfası.
- Metnin derinlerinde bir konum: JSON başlamış ama sonradan bozulmuş, örneğin yarıda kesilmiş bir indirmede.

Bir `except` bloğunun içinde `e.doc[:200]` bu metnin başını gösterir.

## requests, json, httpx ve aiohttp hangi istisnayı fırlatır?

Her kütüphaneyi Python 3.13, Requests 2.34.2, HTTPX 0.28.1 ve AIOHTTP 3.14.3 ile denedik:

- **json:** `json.loads()`, `ValueError` sınıfının bir alt sınıfı olan `json.JSONDecodeError` fırlatır.
- **Requests:** 2.27.0 sürümünden (Ocak 2022) bu yana `r.json()`, `requests.exceptions.JSONDecodeError` fırlatır. [Requests değişiklik günlüğüne](https://github.com/psf/requests/blob/main/HISTORY.md) göre bu sınıf daha önce fırlatılan istisnalardan türer, aynı zamanda `RequestException` sınıfının da alt sınıfıdır.
- **simplejson kuruluyken Requests:** üst sınıf `simplejson.errors.JSONDecodeError` olur. Testimizde `except json.JSONDecodeError` bu durumda hatayı kaçırdı; `except ValueError` yine yakaladı.
- **HTTPX:** `Response.json()` standart `json.decoder.JSONDecodeError` fırlatır.
- **AIOHTTP:** `await resp.json()` önce Content-Type'a bakar ve ayrıştırmaya hiç girmeden `ContentTypeError` fırlatır (`Attempt to decode JSON with unexpected mimetype: text/html`). `content_type=None` verildiğinde `json.JSONDecodeError` fırlatır.

Requests kullanıyorsanız `requests.exceptions.JSONDecodeError` yakalayın: iki durumda da çalışır. İstemciler arasındaki diğer farkları [HTTPX, Requests ve AIOHTTP Karşılaştırması](/tr/blog/httpx-vs-requests-vs-aiohttp) yazısında anlattık.

## Nedeni üç satırda nasıl bulursunuz?

Ayrıştırmadan önce gelen yanıtı yazdırın:

```python
print(r.status_code, r.history, r.url)
print(r.headers.get("Content-Type"), len(r.content))
print(r.text[:200])
```

Sonra çıktıyı şu sırayla okuyun:

1. **Durum kodu ve geçmiş.** Durum kodu `2xx` mi? Yolda bir `301` ya da `302` yönlendirmesi oldu mu? Yönlendirmeden sonra `r.status_code` son yanıtın kodunu, yani `200` gösterir; `[<Response [302]>]` yalnız `r.history` içinde görünür.
2. **Son URL.** `r.url`, yönlendirmelerden sonraki adrestir. Adres `/login` ya da `/consent` ile bitiyorsa API'ye ulaşmamışsınız demektir.
3. **Content-Type.** Görmek istediğiniz tür `application/json` ya da `+json` ile biten bir türdür. Başka her şey aşağıdaki nedenlerden birine işaret eder.
4. **Uzunluk ve ilk karakterler.** `0` boş demektir, `<` HTML, `{'` metin olarak yazdırılmış bir Python sözlüğü (dict), `cb(` ise JSONP.
5. **Sonucu aşağıdaki tabloyla eşleştirin.**

Log'a yalnız ilk 200 karakteri yazın: gövdenin tamamında token'lar ya da kişisel veriler olabilir.

## Gövdenin ilk karakterleri size ne söyler?

Aşağıdaki mesajların hepsi, Python 3.13.9 ve Requests 2.34.2 ile yerel bir test sunucusuna karşı yaptığımız çalıştırmadan geliyor; başka Python sürümleri aynı hatayı farklı sözcüklerle yazabilir.

| Gövdenin başı | Tipik durum kodu ve Content-Type | `r.json()` mesajı | Olası neden | Ne yapmalı |
|---|---|---|---|---|
| (hiçbir şey) | `204`, `304`, `HEAD` ya da boş bir `200` | `Expecting value: line 1 column 1 (char 0)` | Endpoint içerik döndürmüyor | Ayrıştırmadan önce durum kodunu ve `len(r.content)` değerini kontrol edin |
| `<!DOCTYPE html>` | `403`, `429`, `503` ya da bir `302` sonrası `200`; `text/html` | `Expecting value: line 1 column 1 (char 0)` | Engel sayfası, giriş yönlendirmesi, hata sayfası | Önce durum kodunun nedenini giderin |
| `<html>...407...` ya da hiçbir şey | `407` ve `Proxy-Authenticate`, `http://` hedef | `Expecting value: line 1 column 1 (char 0)` | Yanlış proxy kimlik bilgisi ya da IP | `user:pass` bilgisini ve IP whitelist'i kontrol edin |
| `Too Many Requests` | `429`, `text/plain` | `Expecting value: line 1 column 1 (char 0)` | Düz metin bir hız sınırı cevabı | Yavaşlayın; `Retry-After` header'ını okuyun |
| `cb({"items": ...});` | `200`, `application/javascript` | `Expecting value: line 1 column 1 (char 0)` | JSONP | Endpoint'i callback olmadan kullanın |
| `{"id":1}`, yeni satır, `{"id":2}` | `200`, `application/x-ndjson` | `Extra data: line 2 column 1 (char 9)` | NDJSON | Satır satır ayrıştırın |
| `{'id': 1, ...}` | `200`, çoğunlukla `text/plain` | `Expecting property name enclosed in double quotes: line 1 column 2 (char 1)` | `str()` ile yazılmış bir dict | Veriyi üreten tarafta `json.dumps()` kullanın |
| Görünmez bir BOM, sonra `{` | `200`, `application/json` | `Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)` | Bayt sırası işareti (BOM) | `utf-8-sig` ile decode edin |

İlk beş satır aynı mesajı veriyor. Yani mesaj tek başına nedeni hiçbir zaman söylemez; nedeni durum kodu ve Content-Type söyler.

## Boş yanıtlar: 204, HEAD ve 304

Bazı cevapların tanım gereği gövdesi yoktur. [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.3.5), `204 No Content` yanıtının içerik taşıyamayacağını, `304 Not Modified` yanıtında da içerik bulunmadığını ve bir `HEAD` isteğine gelen cevabın yalnız header'lardan (üst bilgi satırlarından) oluştuğunu söyler. Birçok API `DELETE` ve `PUT` isteklerine `204` ile cevap verir: işlem başarılıdır ve ayrıştırılacak bir şey yoktur, ama `r.json()` yine de `Expecting value` fırlatır.

Bu cevapları hata değil, "veri yok" olarak ele alın ve ayrıştırmadan önce `r.status_code` değerine bakın. Boş bir `200` ise başka bir durumdur: genellikle bir sunucu hatasına ya da yanlış bir endpoint'e işaret eder ve log'a yazılmalıdır.

## JSON yerine HTML: engel, giriş ve hata sayfaları

JSON beklenen yere üç tür HTML sayfası gelir; hangisinin geldiğini durum kodu söyler.

**Engel ya da doğrulama sayfası.** `text/html` ile gelen bir `403`, `429` ya da `503` çoğu zaman bir bot korumasının cevabıdır. Onu tanımak için "Just a moment..." ya da "Access denied" gibi `<title>` metni yeter. Bu sayfayı ayrıştırmayın, döngü içinde yeniden de denemeyin. Her kodun ne anlama geldiğini [Scraping'de HTTP Hata Kodları](/tr/blog/http-status-codes-web-scraping), Cloudflare sayfalarını [Cloudflare Scraper](/tr/blog/cloudflare-scraper), bot puanlamasını da [Bot Tespiti Nasıl Yapılır?](/tr/blog/how-bot-detection-works) yazısında anlattık. Meşru yollar resmî bir API, `robots.txt` kurallarına uyan daha düşük bir istek hızı ya da site sahibinin iznidir.

**Yönlendirme sonrası giriş sayfası.** Durum kodu `200` olduğu için bu durum kolayca gözden kaçar. `r.history` içinde `[<Response [302]>]` görünür ve `r.url` değeri `/login` ile biter: oturumunuzun süresi dolmuştur. Kendi hesabınızda çözüm oturum yönetimidir ([Python'da Oturum ve Çerez Yönetimi](/tr/blog/python-login-session-cookies)).

**Sunucu hata sayfası.** HTML ile gelen bir `500`, `502` ya da `504`, sunucunun kendisinden ya da önündeki bir ağ geçidinden (gateway) gelir: sorun ayrıştırıcınızda değil, API'dedir.

URL, API'yi değil de sayfanın kendisini gösterdiğinde de HTML gelir. JSON, sayfanın yaptığı ayrı bir istekle gelir ve bu isteği Network panelinde bulursunuz ([önce API isteğini bulmak](/tr/blog/static-vs-dynamic-pages)).

## Proxy üzerinden: 407 cevabı

Düz bir `http://` isteğini yanlış şifreyle bir proxy üzerinden gönderdiğinizde `407 Proxy Authentication Required` cevabını proxy'nin kendisi verir. Requests bu cevabı, proxy hangi gövdeyi gönderdiyse onunla birlikte normal bir yanıt olarak kodunuza iletir: bir HTML sayfası, kısa bir metin ya da hiçbir şey. Biri HTML, diğeri boş gövde gönderen iki yerel test proxy'siyle denediğimizde `r.json()` iki seferde de `Expecting value` verdi.

Hedef `https://` ise `407` tünel kurulurken gelir. Bu yüzden `requests.get()` çağrısı `ProxyError: Tunnel connection failed: 407` fırlatır ve `r.json()` satırına hiç gelinmez ([Max Retries Exceeded With URL](/tr/blog/max-retries-exceeded-with-url)).

`Proxy-Authenticate` header'ıyla gelen bir `407` sorunun hedefte değil, proxy'de olduğunu gösterir. Kullanıcı adını ve şifreyi kontrol edin, proxy URL'sindeki özel karakterleri kodlayın (`@` işareti `%40` olur) ya da IP adresinizin whitelist'te olduğunu doğrulayın ([Proxy Kimlik Doğrulama: User:Pass ve IP Whitelist](/tr/blog/proxy-authentication-methods)).

## JSON'a benzeyen ama JSON olmayan gövdeler: JSONP, NDJSON ve tek tırnak

Bazı gövdeler JSON içerir, ama tek ve temiz bir değer olarak değil.

### JSONP

JSONP, JSON'u bir fonksiyon çağrısının içine sarar: `cb({"items": [1]});`. Genellikle `application/javascript` türüyle gönderilir. Ayrıştırıcı `c` harfini görür ve `char 0` konumunda `Expecting value` bildirir. Endpoint'i `callback` parametresi olmadan kullanın ya da ilk `(` ile son `)` arasındaki metni alın.

### NDJSON ve JSON Lines

Dışa aktarma (export) ve akış (streaming) endpoint'leri çoğu zaman her satıra bir JSON değeri yazar; bu biçim [jsonlines.org](https://jsonlines.org/) adresinde tanımlanır. İlk satır ayrıştırılır, sonra ayrıştırıcı arkasında başka metin bulur ve `Extra data: line 2 column 1` diyerek durur. Böyle gövdeleri `r.iter_lines()` ile satır satır okuyun.

### Tek tırnak ve Python değerleri

`{'id': 1}` gibi bir gövde `json.dumps()` yerine Python'un `str()` fonksiyonuyla yazılmıştır; ayrıştırıcı `char 1` konumunda `Expecting property name enclosed in double quotes` diyerek durur. Python yazımıyla `None` ya da `True` olan bir gövde de `Expecting value` ile düşer, çünkü JSON bunları `null` ve `true` diye yazar. Veriyi yazan kodu düzeltin: `text.replace("'", '"')` kesme işareti içeren her değeri bozar, `ast.literal_eval()` ise yalnız kendi ürettiğiniz veride güvenlidir. Veriyi bir API'ye Python Requests ile siz gönderiyorsanız dict'i `str()` ile metne çevirmek yerine `json=` argümanına verin; `json=` ile `data=` farkını [Python Requests ile POST ve JSON: cURL Karşılıkları](/tr/blog/python-requests-post-json) yazısında anlattık.

Gövdenin başında bir bayt sırası işareti (BOM) varsa `Unexpected UTF-8 BOM` alırsınız; kodlama tarafını [Python Türkçe Karakter Sorunu](/tr/blog/python-unicode-encoding-errors) yazısında anlattık.

## Tam örnek: gövdeyi kontrol edip JSON okuyan parse_json()

Bu yardımcı fonksiyon üç satırlık kontrolü koda döker. Ayrıştırılmış veriyi döndürür, tanım gereği gövdesi olmayan cevaplarda `None` verir ya da durum kodunu, yönlendirmeleri, Content-Type'ı, son URL'yi ve gövdenin başını yazan tek bir `NotJSON` hatası fırlatır. Requests'i `pip install requests` ile kurun.

```python
"""JSON yanıtını okur ya da gövdenin neden JSON olmadığını tek satırda söyler."""
import json

import requests

PROXY = "http://user:pass@pr.proxynet.io:8000"
PROXIES = {"http": PROXY, "https": PROXY}

class NotJSON(ValueError):
    """Sunucu cevap verdi, ama istediğimiz JSON ile değil."""

def describe(r):
    """Durum kodu, yönlendirmeler, Content-Type, son URL ve ilk 200 karakter."""
    hops = "".join(f"{h.status_code} -> " for h in r.history)
    ctype = r.headers.get("Content-Type", "none")
    start = r.text[:200].replace("\n", " ")
    return f"HTTP {hops}{r.status_code}, {ctype}, {r.url}, body {start!r}"

def media_type(r):
    return r.headers.get("Content-Type", "").split(";")[0].strip().lower()

def is_json_type(mtype):
    return mtype == "application/json" or mtype.endswith("+json")

def parse_json(r):
    """Ayrıştırılmış gövdeyi döndürür, "içerik yok" için None verir ya da nedeniyle NotJSON fırlatır."""
    if r.status_code in (204, 304) or r.request.method == "HEAD":
        return None  # bu cevaplar tanım gereği gövde taşımaz
    mtype = media_type(r)
    if not r.ok and not is_json_type(mtype):
        raise NotJSON(f"error response, not JSON: {describe(r)}")
    if not r.content:
        raise NotJSON(f"empty body: {describe(r)}")
    if mtype == "application/x-ndjson":
        return [json.loads(line) for line in r.iter_lines() if line.strip()]
    if r.text.lstrip().startswith("<"):
        raise NotJSON(f"HTML instead of JSON: {describe(r)}")
    try:
        return r.json()
    except requests.exceptions.JSONDecodeError as e:
        raise NotJSON(f"{e.msg} at char {e.pos}: {describe(r)}") from e

if __name__ == "__main__":
    url = "https://example.com/api/products?page=1"
    r = requests.get(url, proxies=PROXIES, timeout=(5, 30))
    try:
        data = parse_json(r)
    except NotJSON as e:
        print("stop:", e)
    else:
        if not r.ok:
            print("API error:", r.status_code, data)
        elif data is None:
            print("no content")
        else:
            print("ok:", type(data).__name__, len(data))
```

Hata durum koduyla gelen ve JSON olmayan bir gövde en başta durdurulur; böylece bir `403` sayfası ya da proxy'nin `407` cevabı hiç ayrıştırılmaz. Hata durum koduyla gelen ama gövdesi JSON olan bir yanıt, örneğin `{"error": ...}` içeren bir `400`, döndürülür, çünkü birçok API hatalarını bu şekilde açıklar; `r.ok` kontrolünü fonksiyonu çağıran taraf yapar. `PROXIES` isteğe bağlıdır; `timeout=(5, 30)` bağlantı için 5, cevap için 30 saniye tanır.

Yardımcı fonksiyon bilerek yeniden deneme yapmaz, IP döndürmez ve paralel çalışmaz. Hangi durum kodlarının yeniden denenmeye değdiğini [Scraping'de HTTP Hata Kodları](/tr/blog/http-status-codes-web-scraping) yazısında, IP döndürmeyi [Python'da Proxy'ler Nasıl Döndürülür?](/tr/blog/rotating-proxy-python) rehberinde, paralel istekleri de [Concurrency ve Parallelism](/tr/blog/concurrency-vs-parallelism) yazısında anlattık.

### Çıktı nasıl görünüyor?

`parse_json()` fonksiyonunu, her yola tablodaki gövdelerden biriyle cevap veren yerel bir test sunucusuna karşı çalıştırdık; son satır, HTML ile `407` cevabı veren bir test proxy'sinden geçti.

```text
/api/products -> {'items': [1, 2, 3]}
/api/items/7 -> None
/api/empty -> NotJSON: empty body: HTTP 200, application/json, http://127.0.0.1:8111/api/empty, body ''
/api/blocked -> NotJSON: error response, not JSON: HTTP 403, text/html; charset=utf-8, http://127.0.0.1:8111/api/blocked, body '<!DOCTYPE html><html><head><title>Just a moment...</title></head></html>'
/api/private -> NotJSON: HTML instead of JSON: HTTP 302 -> 200, text/html; charset=utf-8, http://127.0.0.1:8111/login, body '<!DOCTYPE html> <html><head><title>Sign in</title></head></html>'
/api/slow -> NotJSON: error response, not JSON: HTTP 429, text/plain, http://127.0.0.1:8111/api/slow, body 'Too Many Requests'
/api/jsonp -> NotJSON: Expecting value at char 0: HTTP 200, application/javascript, http://127.0.0.1:8111/api/jsonp, body 'cb({"items": [1]});'
/api/export -> [{'id': 1}, {'id': 2}]
/api/dict -> NotJSON: Expecting property name enclosed in double quotes at char 1: HTTP 200, text/plain, http://127.0.0.1:8111/api/dict, body "{'id': 1, 'name': 'Lamp'}"
/api/bad-request -> {'error': 'page must be a number'} (status 400)
proxy, wrong password -> NotJSON: error response, not JSON: HTTP 407, text/html, http://example.com/api/products, body '<html><head><title>407 Proxy Authentication Required</title></head><body><h1>407</h1></body></html>'
```

`/api/items/7` yolu `204` ile cevap verdi, `/api/export` ise NDJSON gönderdi; ikisi de hata değil. `/api/private` satırı, yalnız durum koduna bakan bir kontrolün kaçırdığı yönlendirmeyi gösteriyor: `302 -> 200`, sonunda da `/login`.

## Kullanım alanları: JSON bekleyen hangi betiklerde çıkar?

- **Bir sitenin kendi API'sini çağırmak:** Network panelinden kopyaladığınız istek, arkasındaki oturumun ya da token'ın süresi dolunca çalışmaz olur ([statik ve dinamik sayfalar](/tr/blog/static-vs-dynamic-pages)).
- **Fiyat takibi:** ürün JSON'unu okuyan günlük bir iş, fazla hızlı çalıştığı gün bir engel sayfası alır ([rakip fiyat takibi](/tr/blog/competitor-price-tracking)).
- **API sayfalama:** son sayfadan sonraki sayfa boş bir liste yerine `204` ya da boş bir gövde döndürebilir ([scraping'de sayfalama](/tr/blog/pagination-web-scraping)).
- **Otomasyon araçları:** JSON bekleyen bir n8n HTTP Request düğümü (node) HTML bir hata sayfası alır ([n8n proxy ayarları](/tr/blog/n8n-proxy)).
- **Veri hatları (pipeline):** binlerce JSON cevabının arasındaki tek bir HTML cevabı veritabanına yazılmamalı, o toplu işi durdurmalı ([veri kazıma](/tr/data-scraping)).
- **Crawler'lar:** birçok host'taki JSON endpoint'lerini okuyan bir crawler, başarısız olan her host için tek satırlık açık bir mesaj yazmalıdır ([web crawler](/tr/web-crawler)).

## Sık yapılan hatalar

- **Hatayı yutmak.** `except JSONDecodeError: pass` hiçbir şey kaydetmez ve nedeni gizler.
- **200 durum koduna güvenmek.** Bir `302` sonrasında gelen giriş sayfası da `200` olarak gelir. `r.history` ve `r.url` değerlerine bakın.
- **Tek tırnakları `replace()` ile çift tırnağa çevirmek.** Kesme işareti içeren her değeri bozar.
- **Yanıt gövdesine `eval()` uygulamak.** Sunucu hangi kodu gönderdiyse onu çalıştırır.
- **Engel sayfasını aynı hızda yeniden denemek.** Aynı hızda gönderilen aynı istekler aynı `429` ya da `403` cevabını alır; önce hızı düşürün ([429 Too Many Requests](/tr/blog/http-429-too-many-requests)).
- **Gövdenin tamamını log'a yazmak.** Nedeni ilk 200 karakter söyler; geri kalanında token'lar ve kişisel veriler olabilir.
- **`get()` ve `json()` çağrılarını tek bir `except RequestException` ile sarmak.** 2.27.0 sürümünden bu yana bu blok ikisini de yakalar; ağ hatası ile ayrıştırma hatası aynı görünür.
- **Hatayı json modülünde aramak.** Ayrıştırıcı doğru çalışıyor; gövde JSON değil.

## Karar rehberi

| Ne görüyorsunuz | Ne yapmalı |
|---|---|
| Durum kodu `204` ya da gövde boş | `r.json()` çağırmayın; "veri yok" olarak ele alın |
| `text/html` ile `403`, `429` ya da `503` | Ayrıştırmayı bırakın ve durum kodunun nedenini giderin ([Scraping'de HTTP Hata Kodları](/tr/blog/http-status-codes-web-scraping)) |
| `200`, ama `r.history` bir giriş sayfasına `302` gösteriyor | Oturumunuzu yenileyin ([Python'da Oturum ve Çerez Yönetimi](/tr/blog/python-login-session-cookies)) |
| HTML ya da boş gövdeyle gelen `407` | Proxy kimlik bilgilerini ve IP whitelist'i kontrol edin |
| `ProxyError: Tunnel connection failed: 407` | `https://` hedeflerde aynı neden ([Max Retries Exceeded With URL](/tr/blog/max-retries-exceeded-with-url)) |
| `Extra data` | `r.iter_lines()` ile satır satır ayrıştırın |
| Gövde `callback(` ile başlıyor | Endpoint'i callback olmadan kullanın ya da sarmalayıcıyı çıkarın |
| `Unexpected UTF-8 BOM` | `utf-8-sig` ile decode edin ([Python Türkçe Karakter Sorunu](/tr/blog/python-unicode-encoding-errors)) |

## Sıkça sorulan sorular

### Durum kodu 200 iken r.json() neden hata veriyor?

`200` gövdenin biçimi hakkında hiçbir şey söylemez. Yönlendirme sonrası gelen bir giriş sayfası, bir JSONP cevabı ya da boş bir gövde `200` ile gelebilir. Content-Type'a, `r.history` değerine ve `r.text` metninin başına bakın.

### requests.exceptions.JSONDecodeError ile json.JSONDecodeError arasındaki fark nedir?

`r.json()`, Requests 2.27.0'dan bu yana `requests.exceptions.JSONDecodeError` fırlatır. Bu sınıf hem JSON kütüphanesinin `JSONDecodeError` sınıfının hem de Requests'in kendi `RequestException` sınıfının alt sınıfıdır; bu yüzden ikisi de onu yakalar. Tek istisna simplejson'dır: kuruluysa `except json.JSONDecodeError` hatayı kaçırır, bu yüzden Requests'in sınıfını yakalayın.

### JSONDecodeError: Extra data neden çıkar?

Ayrıştırıcı tam bir JSON değeri okudu, sonra arkasında başka metin buldu: genellikle NDJSON ya da art arda yazılmış iki nesne. Satır satır ayrıştırın ya da değerleri tek tek okumak için `json.JSONDecoder().raw_decode()` kullanın.

### "Expecting property name enclosed in double quotes" ne demek?

Bir nesnenin içindeki anahtar çift tırnak içinde değil demektir. Genellikle nedeni, `str()` ile yazılmış ve tek tırnak kullanan bir Python dict'idir. Python 3.12 ve öncesinde `}` işaretinden önce kalan fazladan bir virgül de bu mesajı verir; Python 3.13 aynı durumu `Illegal trailing comma before end of object` diye bildirir.

### Bu hatayı yfinance ya da spotdl kullanırken alıyorum. Ne yapmalıyım?

Kütüphane uzaktaki bir servisten JSON istemiş ve başka bir şey almıştır. Kütüphaneyi güncelleyin, daha seyrek çağırın ve projenin issue sayfasında aynı mesajı arayın.

### Aynı hata JavaScript'te nasıl görünür?

Node.js 24'te `JSON.parse()` bir HTML sayfasında `SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON`, boş bir dizede ise `SyntaxError: Unexpected end of JSON input` fırlatır. Orada da `response.json()` çağırmadan önce durum kodunu ve Content-Type'ı kontrol edin ([JavaScript'te cURL](/tr/blog/curl-in-javascript)). WordPress editöründeki "The response is not a valid JSON response" uyarısı ise başka bir sorundur.

## Özet

`JSONDecodeError: Expecting value` bir belirtidir, neden değildir. Bağlantı kurulmuş ve sunucu cevap vermiştir, ama gövde boştur ya da JSON değildir. Nedeni üç kontrolle bulursunuz: `r.history` ile birlikte durum kodu, Content-Type ve gövdenin ilk 200 karakteri. Boş bir `204` normaldir; HTML sayfası engel, giriş ya da hata sayfası demektir; `407` proxy'yi gösterir; `Extra data` ya da tek tırnak ise gövdenin yalnız JSON'a benzediğini söyler. JSON endpoint'lerini proxy üzerinden okuyan işler için kullanabileceğiniz proxy türlerini [proxy hizmetlerimiz](/tr/proxy) sayfasında bulabilirsiniz.
