Konu Başlıkları Gizle
Merhaba,
Wordle'dan esinlendiğim bir projem için ihtiyaç duyduğum veriyi nasıl elde ettiğimi aktarmak istedim sizlere.
İyi okumalar.
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.
Uzun mu uzun bir liste. E, bu listeyi nereden elde etmişler peki?
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
İ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
Bu JSON'ın içeriğini direkt buradan görüntüleyebilirsiniz.
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:
O da ne? Hata aldım:
Bu yazıyı yazarkenki araştırmalarım ve denemelerim sonucunda gördüm ki -tahminimce-
Ben birkaç deneme yaptım ve sorunun,
E, üstesinden gelmek fazla basit:
Boş bıraksanız yani
Neyse, elde ettiğimiz JSON'a bakalım:
Geriye, (özel olmayan) kelimeleri ayıklamak kaldı:
Bu son liste,
Bir de şapkalı harflerin şapkalarını kaldırmak istersek şöyle bir şey yazabiliriz:
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:
Listeyi alfabetik olarak sıralıyor ve JSON formatında
Ek olarak şu da TypeScript kodum:
Okuduğunuz için teşekkür eder ve iyi Sosyaller dilerim. : )
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:
Aslında ilk fikrim bir kelimeyi aratmak değildi... : D
Kelime kontrolü için
İşte
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.
Kelime kontrolü için
.includes metodunun kullanıldığını tahmin ederek kelime listesini ta şuradan başlayarak bulmuştum:
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...
autocomplete.json:
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 internettehttps://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:
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. : DNeyse, 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)
.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:- GitHub - ogun/guncel-turkce-sozluk: Türkçe Sözlük'ünün 12. baskısının gözden geçirilip güncellenmiş olarak genel ağdan sunulan sürümüdür.
- GitHub - ncarkaci/TDKDictionaryCrawler
- GitHub - ekartal/turkce-kelime-database: Turkce Kelime Database
- Bu, projeden ziyade bir veri tabanı. Denk geldiğimden ötürü paylaştım.
Okuduğunuz için teşekkür eder ve iyi Sosyaller dilerim. : )