Puppeteer İle Sözlük Uygulaması
Tags: dictionary, puppeteer, webscrapping
Published: 2022-09-26 12:26:25

Birilerinden "rakkam" kelimesi duyuyorum bazen. Öyle bir kelime var mı ya? Bakalım TDK (Türk Dil Kurumu) **[sozluk.gov.tr](https://www.sozluk.gov.tr)** ye. Evet öyle bir kelime yok. Rakkam değil rakam ve rakam kelimesi fiyatlardan, ücretlerden bahsederken kullanılamaz çünkü rakamlar 0 ile 9 arasındaki sayılardır. "Rakkam 1,5 milyon" diyor adam. Neyse ne gerek var bu kadar kafaya takmaya ama artık üşenmeyip her merak ettiğim kelime için **sozluk.gov.tr** ye bakıyorum. Derken aklıma ne geldi? Bot yazabilirim, zaten puppeteer i kullanmaya yer arıyordum.

Ve ortaya çıkan uygulama bu: (nacizane pardon naçizane. Şimdi sözlükten baktım: Zarf [Haddi olmayarak, Çok küçük, önemsiz bir şey olarak "Bunun için sizlere naçizane bir tavsiyem olacak." - Tarık Buğra)

Kaynak kod: **[github.com/canerdemirci/sozluk_bot](https://www.github.com/canerdemirci/sozluk_bot)**

Bu uygulamada Node JS, Express, Puppeteer ve JS kullandım.

Ben örnek bir metin koydum ama ekranın sağ üst köşesinden istediğiniz metni yapıştırabilirsiniz. Kelimeler üzerinde gezip tıklarsanız kelime arama kutusuna taşınır. Enter tuşana bastığınızda arama başlar. Sonuçlar sol tarafta listelenir.

Şimdi birkaç teknik bilgi vereyim.

Önyüzden yazdığım rest api ye başvurup kelime arama sonuçlarını çekmek için basitçe fetch api kullandım.

```javascript
async function getResult(word) {
    word = filterWord(word);

    const options = {
        method: 'GET',
        headers: { 'Content-Type': 'application/json' }
    };

    try {
        const res = await fetch(`http://localhost:5000/word/${word}`, options);

        if (res.status == 500) {
            failed = true;
            return;
        } else if (res.status == 204) {
            noresult = true;
            return;
        } else if (res.status == 200) {
            results.push(await res.json());
            failed = false;
            noresult = false;
            return;
        }
    } catch(err) {
        failed = true;
    }
}
```

Tıkladığmız kelime arama kutusuna gitmeden önce filtreden geçiyor yani türkçe karakter içermeyen tüm karakterler kelimeden atılıyor.

```javascript
function filterWord(word) {
    const alphabet = `abcçdefgğhıijklmnoöprsştuüvyzABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ`;
    var newWord = '';

    for (let i=0; i<word.length; i++) {
        let isAlphabet = true;

        for (let j=0; j<alphabet.length; j++) {
            if (word[i] !== alphabet[j]) {
                isAlphabet = false;
            }
            else {
                isAlphabet = true;
                break;
            }
        }

        if (isAlphabet) newWord += word[i];
    }

    return newWord;
}
```

Metni tıklanabilir tuşlara dönüştüren kod:

```javascript
function transformTextToButtons() {
    const words = text.split(' ');

    articleSection.innerHTML = '';

    for (let i=0; i<words.length; i++) {
        const btn = document.createElement('a');
        const space = document.createElement('span');

        space.innerText = ' ';

        btn.href = '#';
        btn.innerText = words[i];

        btn.addEventListener('click', (e) => {
            e.preventDefault();

            searchBox.value = filterWord(words[i]); 
        });

        articleSection.appendChild(btn);
        articleSection.appendChild(space);
    }
}
```

Arama kutusunda enter tuşuna basıldıktan sonra veriler gelene kadar süren bekleme animasyonu css kodu: (1,5 saniye, doğrusal, sonsuz, 360 derece transform: rotate)

```css
#pageBlocker img {
    animation: loadingAnim infinite 1.5s linear;
    transform-origin: center;
}

@keyframes loadingAnim {
    from { transform: rotate(0); }
    to { transform: rotate(360deg); }
}
```

**/word rotamız; sonuçları elde ettiğinde 200 http durum koduyla, kelime anlamı bulunamadıysa 204 koduyla, hata oluştuysa 500 koduyla json olarak dönüş yapar.**

## Puppeteer

Tarayıcıyı aç (headless yani gizli biz pencereyi görmeyeceğiz) sonra bir sekme referans al.

```nodejs
const browser = await puppeteer.launch();
const page = await browser.newPage();
```

1. Adrese git. Bekle (500 milisaniye boyunca 0'dan fazla ağ bağlantısı olmayınca navigasyon bitmiş varsayar)
2. Sitede arama kutusu (input '#tdk-search-btn') görene kadar bekle
3. Arama kutusuna kelimeyi yaz. (Burada yazmak yerine input elementinin değerini de değiştirebilirdik)
4. Arama tuşuna tıkla.

```nodejs
await page.goto('https://sozluk.gov.tr/', { waitUntil: 'networkidle0' });
await page.waitForSelector(searchBoxSelector);
await page.type(searchBoxSelector, word);
await page.click(searchButtonSelector);
```

Arama tuşuna tıkladıktan sonra sayfa dinamik olarak içeriğini yeniliyor. Bu yüzden biraz bekliyoruz. Kelime anlamlarının yer aldığı html elementinin de ortaya çıkmasını en fazla 3 saniye bekliyoruz eğer çıkmazsa tarayıcıyı kapatıp null değeri döndürüyoruz. Yani kelime anlamı bulanamamış oluyor.

```nodejs
try {
    await page.waitForNetworkIdle({idleTime: 'networkidle0'});
    await page.waitForSelector(meansSectionSelector, { timeout: 3000 });
} catch (e) {
    await browser.close();
    return null;
}
```

Burada da kelimenin anlamını, türlerini, örnek cümleyi ve söyleyen kişiyi alıp bir obje haline dönüştürüyoruz. Burada önemli olan $$eval(selector, function) methodu. Bu method birden fazla elementi ele alarak işlemenizi sağlıyor.

```nodejs
const means = await page.$$eval(meansElementsSelector, el => el.map(e => {
    const kinds = e.querySelector('i:nth-child(2)')?.innerText.split(',').map(k => k.trim());
    const mean = e?.childNodes[2]?.nodeValue ?? e?.innerText;
    const sampleSentence = e.querySelector('i:nth-child(4)')?.innerText;
    const sampleSentenceAuthor = e.querySelector('b:nth-child(5)')?.innerText;

    return {
        kinds,
        mean,
        sampleSentence,
        sampleSentenceAuthor
    };
}));
```

Kodun tamamını github'tan inceleyebilirsiniz. github.com/canerdemirci/sozluk_bot

Son olarak şuna değinmek istiyorum; İş hayatında, özellikle mühendislik alanında zaten bir miktar yozlaşmış dilimizi iyice kötüye götürdüğümüzü görüyorum. Biri teknik bir konudan bahsederken Türkçesini bilmediği ya da İngilizcesini daha havalı bulduğu bir kelimeyi Türkçe konuşmasının arasında İngilizce olarak kullanıyor. Map ettim, push ettim, set ettim demek yerine Türkçesini kullanmaya gayret edelim çünkü biz bu alışkanlığı çalışma arkadaşlarımıza da bulaştırıyoruz. (Geçenlerde bizim finans müdürü "Opportunity cost" demişti online toplantıda gülmemek için zor tuttum kendimi. Fırsat maliyeti desene be adam. Bu konuyuda iktisadi idari bilimler okuyan herkes bilir)