Bash Betiği ile “2 Biletin 1’i Bedava” Biletleri Bulmak

Hedefimiz, biletinial sitesinde Ankara konumunda bulunan “2 Biletin 1’i Bedava” tag’ine sahip tiyatro oyunlarını bulmak.

Bu amaca ulaşabilmek için öncelikle sitedeki Ankara konumunda bulunan tiyatro oyunlarının bir listesini almamız gerekiyor. Sonrasında da bu listedeki oyunların sayfalarına tek tek baktırarak, oyun Ankara konumunda oynandığı sırada hangi sahnelerde “2 Biletin 1’i Bedava” kampanyasını içeriyoru bulabiliriz.


Şehirdeki Tiyatro Oyunlarının Listesinin Alınması

İlk olarak amacımız Ankara’da bulunan tiyatro oyunlarının bir listesini almak. İstediğimiz oyunları bulabilmek için curl komutuyla siteye istekler atacağız. Öncelikle sitenin “/tiyatro/ankara” path’ine bir istek atarak oyunları çıkartıyoruz.

## curl için kullanılan -sS parametresiyle komutun sessiz çalışmasını sağlıyoruz.
curl -sS https://biletinial.com/tr-tr/tiyatro/ankara

<!doctype html>
<html class="no-js" lang="tr">
<head>
    
<meta charset="utf-8">
<title>
Ankara Tiyatro Oyunları  |  biletinial</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
curl: (23) Failure writing output to destination

Bu istek sonucunda Ankara konumundaki tiyatro oyunlarının listelendiği web sayfasının HTML çıktısını elde ediyoruz. Fakat bu çıktı oyunların yanı sıra, bizim işimize yaramayacak HTML satırlarını da içeriyor. Çıktıyı bu satırlardan temizleyip sadece istediğimiz alanlar kalacak duruma getirmemiz gerekiyor.

Bunun için elde ettiğimiz çıktıyı incelediğimizde ihtiyacımız olan alanların ortak yönü olarak hepsinin “tr-tr/tiyatro” alanını içerdiğini görüyoruz. Sadece bu alanları almak için curl çıktısını grep‘e yönlendirebiliriz.

curl -sS https://biletinial.com/tr-tr/tiyatro/ankara | grep tr-tr/tiyatro

        <link rel="canonical" href="https://biletinial.com/tr-tr/tiyatro" />
<meta property="og:url" content="https://biletinial.com/tr-tr/tiyatro/ankara" />
<meta property="twitter:url" content="https://biletinial.com/tr-tr/tiyatro/ankara" />
    <link rel="alternate" hreflang="tr" href="https://biletinial.com/tr-tr/tiyatro/ankara" />
                    <a href='/tr-tr/tiyatro' title='Tiyatro'>
                        <a href="/tr-tr/tiyatro/seviyorsan-git-ayril-bence" title="Seviyorsan Git Ayrıl Bence">

Bu haliyle biraz daha anlamlı bir çıktı elde ediyoruz fakat hala sonuç istediğimiz kadar temiz değil. Burada sonucun bize getirdiği tiyatro oyunlarını incelediğimizde bizim istediğimiz alanın sadece “/tr-tr/tiyatro/seviyorsan-git-ayril-bence” alanları olduğunu görüyoruz. Bu satırlar içerisinden de istediğimiz alanı çıkarma işlemini cut komutunu kullanarak yapabiliriz.

cut komutu ile satırları " karakterinden bölme işlemini yaptırıyoruz. Bölme işlemi sonrasında da ikinci alanı aldığımızda istediğimiz tiyatro oyununa dair path’i elde ediyoruz.

curl -sS https://biletinial.com/tr-tr/tiyatro/ankara | grep tr-tr/tiyatro | cut -d\" -f2

canonical
og:url
twitter:url
alternate
                    <a href='/tr-tr/tiyatro' title='Tiyatro'>
/tr-tr/tiyatro/seviyorsan-git-ayril-bence
/tr-tr/tiyatro/saatleri-ayarlama-enstitusu
/tr-tr/tiyatro/seda-yuz-stand-up-
/tr-tr/tiyatro/olun-bizi-ayirana-dek-grg

Elde ettiğimiz çıktı biraz daha anlamlı bir duruma geldi fakat hala istediğimiz temizlikte, sadece tiyatro oyunlarının path’lerini içeren çıktıyı elde edemedik. Burada çıktının tamamını incelediğimizde, problemin sadece ilk 15 satırda olduğunu görebiliyoruz. İlk 15 satır, sitenin gösterdiği oyun önerisi gibi alanları içeriyor. Sonrasında da bir search alanı bulunuyor ve sonrasında ilgili şehirdeki etkinlikleri sıralıyor. Bizim bu ilk 15 satırlık alandan kurtulmamız gerekiyor.

Bizim karşılaştığımız durumda gereksiz olan ilk 15 satır, her zaman ilk 15 satır olmak zorunda değil. Buradaki satır sayısı, önerilecek oyun sayısına göre değişiklik gösterebilir. Bu nedenle çıktıyı satır sayısından ziyade bir belirteçten bölmemiz gerekir. Bu belirteçi de öneriler tamamlandıktan sonra bulunan search alanını kullanarak yapabiliriz. Curl ile elde ettiğimiz çıktıyı bir dosyaya yazdırıp, dosyanın search kelimesinden önceki satırlarını temizlersek elimizde sadece oyunlar listesi kalır.

Bunun için öncelikle curl komutunun çıktısını bir dosyaya yazdırıyoruz. Sonrasında search kelimesinin hangi satırda olduğunu bulmamız gerekiyor. Bunun için dosyayı okuyup çıktısını grep komutuna yönlendiriyoruz. Komutun bize search kelimesinin bulunduğu satır numaralarını vermesini istiyoruz. Bu nedenle -n parametresini kullanıyoruz.

cat /tmp/ankara | grep -n search

14:search
15:search

Burada elde ettiğimiz alandan bize tek gereken satır numaraları. grep komutunda sadece satır numaralarını getir gibi bir parametre bulunmadığı için burada da cut komutuyla sadece istediğimiz numaraları alacağız. cut komutuyla satırları : karakterinden bölüp ilk alanı alıyoruz. Bu şekilde yalnızca satır numaralarını elde etmiş oluyoruz. Yapmak istediğimiz şey search kelimesini içeren son satırdan yukarısını silmek olduğu için en son search kelimesini içeren satır numarasını da tail komutuyla alıyoruz.

cat /tmp/ankara | grep -n search | cut -d: -f1 | tail -n1

15

İstediğimiz satırı aldık ettik. Şimdi dosyada bu satırdan yukarısını temizlememiz gerekiyor. Diğer bir bakış açısıyla, dosyada bu satırdan aşağısını almamız gerekiyor. Yine bu işlemi de tail komutunu kullanarak yapabiliriz. Bu komutla bir dosyanın ilk x satırından sonraki satırları alma işlemini, -n+(x+1) şeklinde parametre vererek yapabiliyoruz.

Burada işlem için biraz önce bulunduğumuz satır sayısını bir değişkene atıyoruz. Sonrasında atadığımız değişkeni kullanarak belirttiğim parametreyi veriyoruz.

LINE=$(cat /tmp/ankara | grep -n search | cut -d: -f1 | tail -n1)
cat /tmp/ankara | tail -n+$(( ${LINE}+1 ))

/tr-tr/tiyatro/bir-baba-hamlet
/tr-tr/tiyatro/bir-baba-hamlet
/tr-tr/tiyatro/zengin-mutfagi
/tr-tr/tiyatro/zengin-mutfagi
/tr-tr/tiyatro/cimri-fskhne
/tr-tr/tiyatro/cimri-fskhne

Bu işlem bize istediğimiz gibi sadece tiyatro oyunlarının listesini içeren bir çıktı veriyor. Fakat bu çıktı da gördüğünüz gibi birbirini tekrar eden satırlar içeriyor. Birbirini tekrar eden satırlardan kurtulmak için uniq komutunu kullanabiliriz. Fakat bu komutu kullanabilmemiz için de üzerinde işlem yapılacak verinin sıralı olması gerekiyor. Veriyi sıralamak için de sort komutunu kullanıyoruz.

cat /tmp/ankara | tail -n+$(( ${LINE}+1 )) | sort | uniq

/tr-tr/tiyatro/3lu-stand-up-gosterisi
/tr-tr/tiyatro/acikhava-yesilcam-gunleri-
/tr-tr/tiyatro/acik-mikrofon
/tr-tr/tiyatro/acik-mikrofon-
/tr-tr/tiyatro/acik-mikrofon-stand-up-sfralt

Bu işlemler sonucunda tam olarak istediğimiz veriyi elde edebiliyoruz. Bu veriyi betiğin devamında kullanabilmek için bir dosyaya yazdırıyoruz.

curl -sS https://biletinial.com/tr-tr/tiyatro/ankara | grep tr-tr/tiyatro | cut -d\" -f2 > /tmp/ankara

LINE=$(cat /tmp/ankara | grep -n search | cut -d: -f1 | tail -n1)

cat /tmp/ankara | tail -n+$(( ${LINE}+1 )) | sort | uniq > /tmp/tiyatro

Şehirde Oynan Sahnelerin Tespiti

Artık elimizde oyunların bir listesi var. Şimdi tek yapmamız gereken bu listedeki oyunları tek tek kontrol ederek içerisinde hangisi istediğimiz tag’e sahip diye kontrol etmek.

Listedeki oyunları tek tek kontrol etmeye başlamadan önce üzerinde çalışabileceğim bir örnek oyun belirliyorum. Sitede biraz gezinip istediğimiz “2 Biletin 1’i Bedava” tag’ini içeren, “Benimle Boşanır Mısın?” oyununu buluyorum ve bu oyun üzerinde denemelere başlıyorum.

curl -sS https://biletinial.com/tr-tr/tiyatro/benimle-bosanir-misin

Çıktıyı biraz inceleyip, parse etme işlemi için bir HTML parser aramaya karar veriyorum. Biraz bakındığımda pup isimli bir parser’ı kullanabileceğimi görüyorum. Sayfayı bu komutu kullanarak parse etmeye çalışıyorum.

Not: İlk başlıkta belirtilen işlemler de pup yardımıyla, belki daha kolay olarak gerçekleştirilebilir fakat parser kullanmadan da biraz çabayla parse etmekte bir problem görmediğim için ilk başlığı olduğu gibi bırakıyorum.

İncelediğimiz oyunun sayfasında bir çok şehirdeki oyunlar bulunuyor. Bana yalnızca Ankara konumundaki oyunlar gerekiyor. Yalnızca bu konumdaki oyunları alabilmek için sayfadaki tag’leri incelediğimde Ankara konumundaki oyunların toplam iç div içerisinde olduğunu görüyorum. Son div tag’i de data-sehir="Ankara" attribute’unu içeriyor. pup ile bu şekilde ayıklamaya çalışıyorum

curl -sS https://biletinial.com/tr-tr/tiyatro/benimle-bosanir-misin | pup 'div div div[data-sehir="Ankara"]'

Bu şekilde oyunun sadece Ankara konumundaki sahnelerini elde edebiliyorum. Bana gereken alana gelebilmek için kodu biraz daha incelediğimde, bu alandan sonra da işime yarayacak iki div buluyorum. İlk olarak class="ed-biletler__sehir__gun" attribute’unu içeren div’in altına gitmem gerekiyor. Sonrasında da class="ed-biletler__sehir__gun__detay" attribute’u bulunan div’e eriştiğimde istediğim alana çok yaklaşmış oluyorum.

curl -sS https://biletinial.com/tr-tr/tiyatro/benimle-bosanir-misin | pup 'div div div[data-sehir="Ankara"] div[class="ed-biletler__sehir__gun"] div[class="ed-biletler__sehir__gun__detay"]'

Buradan sonra artık çıktıyı biraz temizlemem gerekiyor. Bu elementin altındaki ilk child’a gittiğimde direkt olarak istediğim alanlara erişebileceğimi görüyorum. Bunun için de pup komutunda :first-child parametresini kullanıyorum.

curl -sS https://biletinial.com/tr-tr/tiyatro/benimle-bosanir-misin | pup 'div div div[data-sehir="Ankara"] div[class="ed-biletler__sehir__gun"] div[class="ed-biletler__sehir__gun__detay"] :first-child'

<time itemprop="startDate" content="2024-06-19T20:00">
 19 Haziran Çarşamba, 20:00
</time>
<address itemprop="name">
 <img src="/dist/assets/img/blocation.svg" alt="Adres">
 Şato Yazar Sahne
</address>
<mark priority="1">
 2 Biletin 1&#39;i Bedava
</mark>
<time itemprop="startDate" content="2024-07-06T19:30">
 06 Temmuz Cumartesi, 19:30
</time>
<address itemprop="name">
 <img src="/dist/assets/img/blocation.svg" alt="Adres">
 Şato Yazar Sahne
</address>
<time itemprop="startDate" content="2024-07-14T19:30">
 14 Temmuz Pazar, 19:30
</time>
<address itemprop="name">
 <img src="/dist/assets/img/blocation.svg" alt="Adres">
 Şato Yazar Sahne
</address>

Son olarak pup komutuna bu çıktıyı farklı bir formatta vermesini söyleyebiliyoruz. Burada çıktıyı text olarak almak için text{} parametresini kullanıyorum ve:

curl -sS https://biletinial.com/tr-tr/tiyatro/benimle-bosanir-misin | pup 'div div div[data-sehir="Ankara"] div[class="ed-biletler__sehir__gun"] div[class="ed-biletler__sehir__gun__detay"] :first-child text{}'

19 Haziran Çarşamba, 20:00
Şato Yazar Sahne
2 Biletin 1&#39;i Bedava
06 Temmuz Cumartesi, 19:30
Şato Yazar Sahne
14 Temmuz Pazar, 19:30
Şato Yazar Sahne

Bu şekilde Ankara konumundaki ilgili oyuna ait sahnelerin listesini ve istediğimiz tag’i içeriyor mu bilgisini alabiliyoruz.


İstenilen Özelliğe Sahip Oyunların Tespiti

Oyunun hangi sahnelerde, hangi saatlerde ve hangi tag’ler ile sahnelendiği bilgisini elde ettik. Tek yapmamız gereken burada istediğimiz tag’e sahip oyunları ayıklamak.

Oyunun bilgilerini satır satır olacak şekilde elde ediyoruz. Bu bağlamda bizim “2 Biletin 1’i Bedava” satırından önceki iki satırı almamız, istediğimiz şeyi elde etmemizi sağlayacak. Bunun için de grep komutunu ve -B parametresini kullanabiliriz.

curl -sS https://biletinial.com/tr-tr/tiyatro/benimle-bosanir-misin | pup 'div div div[data-sehir="Ankara"] div[class="ed-biletler__sehir__gun"] div[class="ed-biletler__sehir__gun__detay"] :first-child text{}' | grep -B2 "2 Biletin"

19 Haziran Çarşamba, 20:00
Şato Yazar Sahne
2 Biletin 1&#39;i Bedava

Bu noktada bize “2 Biletin 1’i Bedava” alanı gerekmiyor. Bu nedenle bu alanı da sed komutuyla kırpıp boşluk haline getirebiliriz. Bu şekilde alt alta iki sahnede oyun aynı tag ile oynanacaksa aralarında bir boşluk ile gösterilmiş olurlar.

curl -sS https://biletinial.com/tr-tr/tiyatro/benimle-bosanir-misin | pup 'div div div[data-sehir="Ankara"] div[class="ed-biletler__sehir__gun"] div[class="ed-biletler__sehir__gun__detay"] :first-child text{}' | grep -B2 "2 Biletin" | sed 's/2 Biletin.*//g'

19 Haziran Çarşamba, 20:00
Şato Yazar Sahne

Oyunun hangi sahnede istediğimiz tag ile sahnelendiği bilgisine ulaştık. Fakat oyunun adına nereden ulaşıyoruz. En başta olduğumuz oyunun path’i bilgisi adını da içeriyor tabii ama onun yerine daha temiz şekilde adını da sayfaya bir curl daha atarak bulabiliriz.

curl -sS https://biletinial.com/tr-tr/tiyatro/benimle-bosanir-misin | pup 'h1 text{}'

Benimle Boşanır Mısın?

Betiği Birleştirme

İstediğimiz alanları elde ettik. Şimdi yukarıda yaptıklarımı birleştirerek tam olarak betiği elde ediyoruz.

Oyun kısmındaki örneği yalnızca bir oyun üzerinden yapmıştık fakat betik içerisinde oyunların listesini aldığımız dosyayı bir while döngüsü içerisinde döndürerek her oyun için gerçekleştireceğiz.

#!/bin/bash

curl -sS https://biletinial.com/tr-tr/tiyatro/ankara | grep tr-tr/tiyatro | cut -d\" -f2 > /tmp/ankara
LINE=$(cat /tmp/ankara | grep -n search | cut -d: -f1 | tail -n1)
cat /tmp/ankara | tail -n+$(( ${LINE}+1 )) | sort | uniq > /tmp/tiyatro

while read oyun 
do   
    curl -sS https://biletinial.com${oyun} > /tmp/oyun   

    AD=$(cat /tmp/oyun | pup 'h1 text{}')   
    BEDEVE=$(cat /tmp/oyun | pup 'div div div[data-sehir="Ankara"] div[class="ed-biletler__sehir__gun"] div[class="ed-biletler__sehir__gun__detay"] :first-child text{}' | grep -B2 "2 Biletin" | sed 's/2 Biletin.*//g')

    if [ ! -z "$BEDEVE" ];   
    then     
        MESSAGE="""
            ${AD}
            ${BEDEVE}
        """

        echo $MESSAGE
    fi 
done</tmp/tiyatro