Rehber TDK Sözlük'ten Türkçe kelime listesi elde etme

  • Konuyu başlatan Konuyu başlatan brkdnmz
  • Başlangıç Tarihi Başlangıç Tarihi
  • Mesaj Mesaj 2
  • Görüntüleme Görüntüleme 4B
Merhaba,

Wordle'dan esinlendiğim bir projem için ihtiyaç duyduğum veriyi nasıl elde ettiğimi aktarmak istedim sizlere.

İyi okumalar.


Arka plan​

Wordle'ı duymamış olan yoktur. 5 harfli bir kelimeyi 6 denemede bulmaya çalıştığımız bir bulmaca oyunu. Böyle bir oyun için tabii ki veriye ihtiyaç var: kelime listesi.

Wordle'da bu listenin nerede barındırıldığını hep merak etmişimdir, halbuki bu merakı gidermek çok zor değilmiş. Tarayıcı konsolunda "Network" (Ağ) sekmesi açıkken bir kelime yazıp Enter'a bastığımda kelime kontrolü için herhangi bir istek gönderilip gönderilmediğine baktım:


Gönderilmiyor. Demek ki (1) gerekli veri çoktan çekilmiş veya (2) kaynak koduna gömülü. Sayfa yüklenirken "Network" sekmesini kontrol ettiğimde verinin direkt çekilmiyor olduğunu da gördüm, demek ki 2. ihtimal daha olası. Bunun için "Sources" (Kaynaklar) sekmesine başvurdum:


Görünen JavaScript dosyalarında herhangi bir 5 harfli Türkçe kelimeyi arattım ve:


İkinci dosyada sonuç elde ettim.

Aslında ilk fikrim bir kelimeyi aratmak değildi... : D

Kelime kontrolü için .includes metodunun kullanıldığını tahmin ederek kelime listesini ta şuradan başlayarak bulmuştum:


İşte Ta -no pun intended- değişkenini büyük/küçük harfe duyarlılık modunu açarak aratmıştım, öyle bulmuştum. Bu iyi ki çok uğraştırmamıştı.

Kelime aratmanın ilk fikrim olmamasının sebebi galiba şeydi: Kelime listesinin gömülü olduğunu düşünmüyordum, onu "bir şekilde gizlice" hallettiklerini düşünüp yalnızca kelime kontrolünü aramaya odaklanmıştım.

Uzun mu uzun bir liste. E, bu listeyi nereden elde etmişler peki?


TDK Sözlük

Başta bir süre araştırma yaptıktan sonra aklıma hemen hâlihazırda kullanıyor olduğum site aklıma geldi: TDK Sözlük. (Sosyal sağ olsun...)

Bu sitede Türkçe kelimeleri aratabiliyorsunuz hatta siz daha kelimeyi yazmayı bitirmeden öneriler sunuluyor:


İkinci bir Wordle vakası: Bu öneriler için istek gönderilmiyor!


Ekran görüntüsü pek sağlıklı bir "kanıt" olmayabilir. : D Dilerseniz siz de deneyip görün.

İstek gönderilmiyorsa bu kelimeler bir yerde saklanıyor olmalı. Sayfa yüklenirkenki isteklere göz attım ve...


Güzel. Bu sefer kaynak koduna gömülü bir listeyle karşılaşmadık, onun yerine güzelce hazırlanmış JSON(lar) karşılıyor. Asıl olay autocomplete.json:


İsimlerden anlaşılacağı üzere otomatik tamamlama için direkt bu veriler kullanılıyor olmalı. Diğer iki JSON, kelime listesi için pek önemli değil çünkü incelediğim kadarıyla autocomplete.json yetiyor da artıyor: 90 bin'den fazla girdi. Diğer ikisinde olan veriler bunda da mevcut diye gördüm, yanlışım olabilir.

Bu JSON'ın içeriğini direkt buradan görüntüleyebilirsiniz.


Veriyi otomatik çekip işleme​

Kıymetli verimiz internette https://sozluk.gov.tr/autocomplete.json adresinde barınıyor. Yapılacak basit: Bu adrese istek yollayıp çektiğimiz veriyi elden geçireceğiz.

Bu işlem için dilediğiniz araçları kullanabilirsiniz. Ben Python'u tercih ettim. Şu kodu çalıştırmayı denedim, çalışacağını düşündüm:

Python:
import requests
r = requests.get("https://sozluk.gov.tr/autocomplete.json")

O da ne? Hata aldım:

Bash:
ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

Bu yazıyı yazarkenki araştırmalarım ve denemelerim sonucunda gördüm ki -tahminimce- requests.get'in varsayılan User-Agent header'ı olan python-requests/<versiyon> bu hataya sebep oluyor. Galiba bu tür scriptlerin önüne geçmek istemişler, pek bilmiyorum.

Şu StackOverflow konusuna göz atabilirsiniz: What happens if I don't specify the user agent in requests.get()?

Kısaca şu, aradığınız şey:

Python:
requests.utils.default_headers()

Ben birkaç deneme yaptım ve sorunun, User-Agent'ın içinde python geçiyor olması olduğunu düşünüyorum. Mesela şu bile hata veriyor:

Python:
requests.get("https://sozluk.gov.tr/autocomplete.json", headers={"User-Agent": "pythonyilannnn"})

E, üstesinden gelmek fazla basit:

Python:
r = requests.get("https://sozluk.gov.tr/autocomplete.json", headers={"User-Agent": "yilannntisss"})

Boş bıraksanız yani "" yazsanız bile sıkıntı olmuyor. Bu işlerden çok anlamadığım için bu yaptığımın ne kadar sağlıklı olduğunu bilmiyorum. : D

Neyse, elde ettiğimiz JSON'a bakalım:

Python:
r.json()

[{'madde': '-den yana'},
 {'madde': '-den yana çıkmak'},
 {'madde': '-den yana olmak'},
 {'madde': "-di'li geçmiş zaman"},
 {'madde': "-miş'li geçmiş zaman"},
 {'madde': '...-e gelince'},
 {'madde': '... -e kuvvet'},
 {'madde': '...-inde değil'},
 {'madde': '...-in karesi'},
 {'madde': '...-meye görsün (veya gör)'},
 ...
]
Oldukça basit bir yapısı var: {"madde": <string>} nesnelerinden oluşan bir liste. "madde"'leri bir kenara atıp sadece değerlerden oluşan bir liste hazırlayabiliriz:

Python:
autocompletions = [girdi["madde"] for girdi in r.json()]

['-den yana',
 '-den yana çıkmak',
 '-den yana olmak',
 "-di'li geçmiş zaman",
 "-miş'li geçmiş zaman",
 '...-e gelince',
 '... -e kuvvet',
 '...-inde değil',
 '...-in karesi',
 '...-meye görsün (veya gör)',
 ...
]

99227 girdi var.

Geriye, (özel olmayan) kelimeleri ayıklamak kaldı:
  • Sadece küçük harflerden oluşacak, boşluk vs. olmayacak. (Özel olmayanları hariç tutmak için büyük harf içerenleri almıyorum.)
  • Boş olmayacak. (Evet, boş girdiler de var. Bu şartı es geçince gördüm. : D)
Neyse ki Python'un .isalpha metodu Türkçe harfler hatta şapkalılar için bile uygun:

Python:
word_list = [ac for ac in autocompletions if len(ac) and all(letter.isalpha() and letter.islower() for letter in ac)]

['a',
 'ab',
 'aba',
 'aba',
 'abacı',
 'abacılık',
 'abadi',
 'abajur',
 'abajurcu',
 'abajurculuk',
 'abajurlu',
 'abajursuz',
 'abaküs',
 ...
]

Bu son liste, 62574 kelimeden oluşuyor. Ne var ki her kelime, kaç anlamı varsa o kadar kere geçiyormuş listede. Ben de şimdi fark ettim. Fazlalıkları elemek için şunu yapabiliriz:

Python:
word_list = list(set(word_list))

set, her elemanın bir defa geçtiği bir veri yapısı ve list ile tekrar listeye çeviriyoruz. Yaygın bir metot.

Bir de şapkalı harflerin şapkalarını kaldırmak istersek şöyle bir şey yazabiliriz:

Python:
SPECIAL_LETTERS_MAP = {"â": "a", "û": "u", "î": "i"}

for special_letter, hat_removed in SPECIAL_LETTERS_MAP.items():
    word_list = [word.replace(special_letter, hat_removed) for word in word_list]

word_list = list(set(word_list))

Son satıra ihtiyacımız var, evet çünkü mesela "ala" diye bir kelime de var, "âlâ" diye de. Şapkaları kaldırınca ikisi aynı oluyor.

Aslında anlatım için bu kadarının yeterli olduğunu düşünüyorum. Kendi kullandığım, biraz daha kapsamlı olan kodu da paylaşayım:

Python:
import json
import os
import sys
from typing import List

import requests
from pydantic import BaseModel, RootModel

""" JSON formati
[
    ...
    {"madde": "ala"},
    {"madde": "geyik"},
    ...
]
"""


class Autocompletion(BaseModel):
    madde: str


class TdkData(RootModel):
    root: List[Autocompletion]


# fmt: off
# Normalde alfabede ı, i'den sonra geliyor.
LOWERCASE_LETTERS = ["a", "b", "c", "ç", "d", "e", "f", "g", "ğ", "h", "ı", "i", "j", "k", "l", "m", "n", "o", "ö", "p", "r", "s", "ş", "t", "u", "ü", "v", "y", "z"]
SPECIAL_LETTERS_MAP = {"â": "a", "û": "u", "î": "i"}
# fmt: on


def fetch_autocompletions() -> List[str]:
    req = requests.get(
        "https://sozluk.gov.tr/autocomplete.json",
        headers={
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
        },
    )
    autocompletions = TdkData.model_validate(req.json()).root
    autocompletions = [ac.madde for ac in autocompletions]
    return autocompletions


def get_word_list(autocompletions: List[str]) -> List[str]:
    """
    Kelime olma sartlari:
    - Bos olmayacak
    - Sadece harflerden olusacak
    - Kucuk harflerden olusacak (Ozel kelimeleri haric tutmak icin)
    """
    is_word = lambda expr: len(expr) > 0 and all(
        char.isalpha() and char.islower() for char in expr
    )
    word_list = [ac for ac in autocompletions if is_word(ac)]

    # Ozel harfleri halledelim
    for i in range(len(word_list)):
        for original_letter, new_letter in SPECIAL_LETTERS_MAP.items():
            word_list[i] = word_list[i].replace(original_letter, new_letter)

    # Onceki islemden sonra "ala" gibi kelimeler birden fazla kez gececek
    word_list = list(set(word_list))

    # Kelimeleri siralayalim
    word_list.sort(key=lambda word: list(map(LOWERCASE_LETTERS.index, word)))

    return word_list


if __name__ == "__main__":
    autocompletions = fetch_autocompletions()
    words = get_word_list(autocompletions)

    with open(os.path.join(sys.path[0], "words.json"), "w", encoding="utf-8") as f:
        json.dump(words, f, indent=2, ensure_ascii=False)

Listeyi alfabetik olarak sıralıyor ve JSON formatında words.json isimli bir dosyaya yazdırıyor.

Ek olarak şu da TypeScript kodum:

JavaScript:
import fs from "node:fs/promises";
import path, { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { z } from "zod";

const __dirname = dirname(fileURLToPath(import.meta.url));

async function fetchAutocompletions(): Promise<string[]> {
  let data: unknown;
  try {
    data = await (
      await fetch("https://sozluk.gov.tr/autocomplete.json")
    ).json();
  } catch (e) {
    console.log("Veriyi cekerken bir hata meydana geldi:");
    throw e;
  }

  // {madde: <kelime>} listesi
  const autocompletionsSchema = z
    .object({
      madde: z.string(),
    })
    .array();

  // Format kontrolu
  const parseResult = autocompletionsSchema.safeParse(data);

  if (parseResult.error) {
    throw new Error("Beklenmedik veri formatiyla karsilasildi");
  }

  const autocompletions = parseResult.data.map(({ madde }) => madde);
  autocompletions.sort(Intl.Collator("tr").compare); // Turkce alfabesine gore sirala

  // Alfabede normalde i, ı'dan once geliyor ama JS'te durum tam tersi...

  return autocompletions;
}

function extractWords(autocompletions: string[]): string[] {
  /*
    Tum harfleri kucuk olan Turkce (Unicode) kelime kontrolu.

    \p{Ll} -> Lowercase Unicode harf
    +      -> En az bir harf
    ^...$  -> Bastan sona kadar
    u      -> Unicode destegi etkinlestirme modifier'i
  */
  function isLowerCaseWord(str: string): boolean {
    return /^\p{Ll}+$/u.test(str);
  }

  const words = autocompletions.filter((ac) => isLowerCaseWord(ac));

  const specialLetterMapping = { â: "a", û: "u", î: "i" };
  const specialLetters = Object.keys(specialLetterMapping);

  const words_specialLettersReplaced = words.map((word) =>
    word.replace(new RegExp(specialLetters.join("|"), "gu"), (m) => {
      return specialLetterMapping[m];
    }),
  );

  const uniqueWords = [...new Set(words_specialLettersReplaced)];

  return uniqueWords;
}

const autocompletions = await fetchAutocompletions();
const words = extractWords(autocompletions);

await fs.writeFile(
  path.join(__dirname, "words.json"),
  JSON.stringify(words, null, 2),
);


Projeler​

GitHub'da bulduğum, bununla alakalı birkaç projeyi burada paylaşmayı uygun gördüm:


Okuduğunuz için teşekkür eder ve iyi Sosyaller dilerim. : )
 
Ellerine sağlık dostum. Oyunu oynamıyorum ama yine de teşekkür ederim <3
 
Bu siteyi kullanmak için çerezler gereklidir. Siteyi kullanmaya devam etmek için çerezleri kabul etmelisiniz. Daha Fazlasını Öğren.…