Python Emojilerle Instagram Üzerinden Duygu Analizi

Bu makalemizde web uygulamalarını test etmek için yazılmış olan python kütüphanesi "selenium" ile Instagram üzerinden gönderilerin yorumlarını çekerek emoji bazlı bir duygu analizi yapacağız. Okuduğunuz zaman karmaşık gelebilir lakin olabilecek en basit düzeyde bir kaç hamleyle neler yapabildiğini görünce sizde şaşıracaksınız. Makalaye başlamadan önce ekşisözlük'ün gündem olan başlıklarını toplayan uygulamamızada ilginizi çekerse şuradan gidebilirsiniz.

İlk olarak size web sitelerinin işleyişinden biraz bahsetmek istiyorum. Bir siteye girdiğiniz zaman sizin gördüğünüz kısımın oluşması için arka planda aslında ".html" uzantılı bir metinlerle dolu dosya inmekte. Bizde selenium kütüphanesini kullanarak burada ekstradan bir şey daha yapıyoruz. İlk indirmenizde sayfanızında olmayan ama sonradan kullanıcının etkileşimleriyle gelen şeyleri de çekebiliyoruz. Örnek vermek gerekirse bir makaleyi çekmek istiyorsunuz diyelim. "habersitesi.com/ne-olucak-bu-python-in-hali" sitesine istek atarak, siteyi indirdiniz. Direkt olarak makaleye gittiğinizden, site ilk yüklendiğinde zaten makale orada olacağından selenium kütüphanesi kullanmadan "beatifulSoup" kütüphanesi kullanarak işinizi halledebilirsiz ama bizim örneğimiz olan Instagram'da bir kişinin profiline girdiğinizde ilk olarak kişinin fotoğrafları gelmekte. Fotoğrafların yorumlarını almak istiyorsanız, istediğiniz fotoğrafı açmanız gerekmekte. Akış diyagramında göstermek gerekirse:

Bizim projemizde kullanacağımız kısım bu değil ama açıklayıcı olması açısından bu örneği kullanmak istedim. Yazmaya başlarken, ilk olarak kütüphanelerimizi tanıtarak başlıyoruz:

# Kullanacağımız kütüphaneleri tanıtarak başlıyoruz.

# Bot için kullandığım kütüphaneler
from selenium import webdriver
from selenium.webdriver.support.ui import Select,WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
# Çıktı alma işlemi için kullandığım kütüphaneler
import matplotlib.pyplot as plt # Görselleştirme 
import time
# Emojileri barındıran kütüphanemiz
import emoji

Selenium kütüphanesini zaten yukarıda bahsettiğim için atlıyorum.

Pyplot kütüphanesinide bulduğum bulguları görselleştirmek için kullanıyoruz.

Emoji kütüphanesini de emojileri bulmak için kullanacağız. Kurulumu gayet basit. Kullandığınız komut istemcisine giderek "pip install emoji" yazarak kurabilirsiniz.

Fotoğraflardaki yorumları çekmek için ilk olarak kişinin profiline giderek, attığı gönderilerin linklerini almalıyız. Bu sayede istediğimiz gönderisine botumuzu yönlendirebiliriz. Bu işlem biraz tersine mühendislik gerektirmekte. Çalışmakta olan bir instagram adresine giderek, gönderileri tuttukları html etiketlerinin adresini bulmalıyız. Google Chrome'un "inspector" özelliği sayesinde çok rahat bir şekilde adresini öğrenmek istediğiniz etiketi bulabiliyorsunuz. Diğer tarayacılarda da benzer özellik bulunmaktadır fakat başka isimler altında bulunabilirler. Google chrome için örnek bir adres bulmayı incelersek:

 

Aldığımız adrese bakacak olursak: 

//*[@id="react-root"]/section/main/div/div[3]/article/div[1]/div/div[1]/div[1]/a/div/div[2]

Biraz daha kodu kısaltmak için tek tek en içteki div hücresinden başlayarak geriye doğru gidiyoruz. "article" adlı element "FyNDV" class'ı ile sadece sayfada bir kez kullanılmış. Bizim istediğimizde buydu. Adresimizin kısaltılmış hali şimdi:

//article[@class="FyNDV"]/div[1]/div/div[1]/div[1]/a/div/div[2]

Şimdi seçtiğimiz bu adres içerisinde diğer görsellerinde bulunması gerektiğinden bunu nasıl yapmışlar ona bakıyoruz. Bir örnek üzerinde anlatmak gerekirse:

Örnek resimde iç içe olan divleri görselleştirmeye çalıştım. Simetri rahatsızlığı olanlardan şimdiden özür dilerim.😀 En son seçmiş olduğumuz "article" etiketi sayesinde pembe olan kısmı seçmiş olduk. Bize lazım olanda fotoğrafın linki olduğundan sondaki iki tane "div" etiketinide atıyoruz. Geriye kalan:

//article[@class="FyNDV"]/div[1]/div/div[1]/div[1]/a

Şimdi bizim için resimde gözüken kırmızı ve mavi alanları kapsayan divleri bölmek kaldı. Buraları kapsayan div etiketlerini bulmak için tarayacınızın özelliğinden faydalabilirsiniz. Ben size cevabı söyleyeyim bu örnek için:

//article[@class="FyNDV"]/div[1]/div/div[Y]/div[X]/a

"Y" yazan yere satır sayısı, "X" yazan yer ise sütun sayısı. İstediğiniz fotoğrafın linkini çekmek için gerekli sayıları yazarak alabilirsiniz. Şimdi bunu koda dökmeden önce fonksiyonumuzu oluşturalım ve "selenium" botumuzu Google Chrome üzerinde ayağa kaldırmak için gerekli kodlarımızı yazalım.

def getComments(username,which_post):
    
    # Botun gideceği adresi tanıtıyoruz.
    url = "https://www.instagram.com/"+username
    
    driver = webdriver.Chrome()
    driver.implicitly_wait(10)
    driver.get(url)

    links = []
    # Gidilen adresteki başlıkların genel olarak içinde bulunduğu adresi xpath olarak veriyoruz.
    for i in range(1,8):
        for s in range(1,4):
            obje = driver.find_element_by_xpath('//article[@class="FyNDV"]/div[1]/div/div['+str(i)+']/div['+str(s)+']/a')
            links.append(obje.get_attribute("href"))
    driver.get(links[which_post])

 Görebileceğiniz üzere iki parametre ile çalıştırıyoruz fonksiyonumuzu. Gideceğimiz kullanıcının adı ve hangi gönderisini istediğimiz. İlk olarak kullanıcı adını "instagram.com" un ardına ekleyerek botumuzu orada ayağa kaldırıyoruz. Burada dikkat etmeniz gereken "kullanıcı girişi" yapmadığımızdan gönderileri gizli olan hesapları açamazsınız. Gerekli tanımlamaları yaptıktan sonra, gönderilerin linklerini tutacağımız listeyi tanımlıyoruz. Ardındanda iç içe iki farklı değer üzerinde oynacağımızdan iç içe iki for tanımlıyorum. Çok fazla eski gönderilerle işim olmadığından maksimumum 8 satır indirdim ama siz daha fazla da indirebilirsiniz.

Şimdi bizim makaleye girdiğimizden beri araştırdığımız gönderilerin adreslerinin bulunduğu XPATH yani adresini giriyoruz. Değişmesi gereken iki değeri de forlarımız sayaçlarına bağlıyoruz. Elimize geçen her objeyide bir listeye alarak "driver.get(links[which_post])" komutuyla kullanıcının istediği gönderiyi listeden çağırıyoruz. Yani ilk gönderiyi çekmek istiyorsanız, fonksiyonumuza "0" değeri girmelisiniz. Artık istediğimiz fotoğrafın sayfasına yönlendirmiş olduk botumuzu.

Sayfaya gireceğinizde fark edeceğiniz üzere yüksek yorumlu sayfalarda "Daha fazla yükle" adlı bir buton bulunmukta. Makaleye girişte bahsettiğim üzere bizim selenium'u kullanacağımız kısım burası. Sayfa ilk açıldığında sadece 20-30 yorum bulunmakta fakat biz 10.000 tane yorumlu bir gönderiyi çekmek istiyorsak, tek tek tıklamamız çok kötü bir vakit harcaması olur. Bu yüzden selenium botumuza bu butona tıklaması için komut vermek için, bu butonun adresini buluyoruz. Tekrar tekrar anlatıp makaleyi kalabalıklaştırmamak için direk butonu bulacağınız XPATH i vereyim:

//ul[@class ="k59kT"]/li[2]/button[@type="button"][.="Daha fazla yorum yükle"]

Şimdi bizim elimizde olan yorumların sayısını bilmediğimizi varsayarsak, "daha fazla yükle" butonu gidene kadar bu butona basılması gerektiğini düşünebiliriz. Bu durumda bir while döngüsü yazalım. Yazarken yaptığım testlerden edindiğim deneyimlerle bu döngünün içine bazı kontroller yerleştirdim. Döngüyü en sona koyacağım ama ilk önce açıklamak istiyorum. Bu kontrollerden ilki:

Doğru adreste miyiz?

Bot arka arkaya seri bir şekilde butonlara basarken bir bug dolayısıyla ne yaparsanız yapın yanlışlıkla başka insanların profillerine veya "#" lerin sayfalarına gidiyor. Bu durumu engellemek içinde döngünün en başına, bulunduğun adres doğru mu kontrolu yerleştirdim. Bu sayede yeni bir sayfaya gittiğinde, daha sorgu bozulmadan hemen geri dönerek çalışmaya devam edebilecek. Bunun için selenium kütüphanesinin sağladığı bir özellik olan "back()" metodundan faydalandım.

Buton aktif mi?

Arka arkaya butona bastığım sıralarda bazı durumlarda internet yavaşlığından dolayı verilerin gelmesi biraz süre almaktaydı. Bu sırada veriler yüklenirken basmaya çalıştığımız buton olan "daha fazla yorum" butonu ise "disabled" durumuna alınmakta, yani çalışmaz durumda bulunuyor. Bu yüzden de aktiflik kontrolu ekledim.

İstekler arası süre: Bu bir kontrol olmasada yazmak istedim. Instagram yazılımcılarıda aynı IP üzerinden gelen isteklere (ki bizim bastığımız her bir buton, yeni bir istek demek) DDOS saldırısı ihtimalinden dolayı engel koymakta. Bu yüzdende istekleri arka arkaya attırdığımızda Instagram tarafından engellenmekteydim. Bende bu yüzden her butona basmasından önce 3 saniye bekleme süresi kullandım.

Şimdi gelelim kodumuza:

    while does_it_continue:
        try:            
            if driver.current_url != links[which_post]:
                driver.back()
            else:
                loadMoreButton = driver.find_element_by_xpath('//ul[@class ="k59kT"]/li[2]/button[@type="button"][.="Daha fazla yorum yükle"]')
                if loadMoreButton.is_enabled():
                    ActionChains(driver).move_to_element(loadMoreButton).click(loadMoreButton).perform()
                    time.sleep(3)
                else:
                    continue

 Şimdi python üzerine biraz fikir sahibi olanların fark edebileceği üzerine burada bir eksiklik bulunmakta. "Try" parametresinin altında bir şey yapmasını söylüyorum fakat basamadığı durumda ne yapacağı yukarıdaki kodda bulunmamakta. Bizim için eğer butona basamıyorsak eğer, orada bir butonun kalmadığını gösterir. Yani yüklenecek daha fazla yorum kalmamış demek oluyor bu. O yüzden "except" parametresine tüm yorumları seçmesini sağlayan şu kodları yazıyoruz:

except:
            get_comments = driver.find_elements_by_xpath('//ul[@class ="k59kT"]/li/div/div/div')
            does_it_continue = False

 Tüm yorumları get_comments adlı listemize almış durumdayız. Ardından da döngüden çıkmak için kontrol değerimizi "False" yapıyoruz. Çekmiş olduğumuz tüm objeleri de tek tek dolaşarak listeye alan bir döngü yazıyoruz ki gereksiz HTML etiketlerinden kurtulalım. Bunun için:

    for option in get_comments:
        print(option.find_element_by_xpath('span').text)
        headers.append(option.find_element_by_xpath('span').text)
    return headers

Fonksiyonumuzu yazdıktan sonra ise, aşağıdaki şekilde çalıştırarak istediğiniz kişilerin yorumlarını çekmeniz mümkün.

headers = getComments("ahmtkural",0)

 Eğer makalenin burasına kadar okuduysanız teşekkür ederim. Ben yine bu kadar şeyi boşuna yapmamış olmamak için bu yaptığımız işlemlerin üzerine bir şeyler koymak istiyorum. Yılın popüler olayı olan yapay zekalara girmeden kendi duygusal analiz algoritmamızı yazmak istiyorum. Bunun için girişte kütüphaneleri tanıtırken belki fark etmişsinizdir "emoji" adlı bir kütüphane de ekledik. Artık onu kullanmanın vakti geldi.

İlk olarak metin üzerinde işlem yapabilmek için tüm yorumlarımızı bir "string" değişkeni üzerinde topluyorum. Bunun için bir fonksiyon işimi görmekte:

myText = ""
for comment in comments:
    myText= myText + " " + comment

 Tüm verimi bir değişkene topladıktan sonra bu metinde bulunan tüm emojileri ayırmamı sağlayan bir fonksiyon yazıyorum. Ardından da elimdeki metni vererek yeni bir değişkene çıkarıyorum:

def extract_emojis(str):
    return(''.join(c for c in str if c in emoji.UNICODE_EMOJI))
myEmojis = extract_emojis(metin)

Şimdi elimizdeki emojileri işaretleyebilmemiz için pozitif ve negatif emojileri elimle işaretledim. Sizde direk alarak kullanabilirsiniz. Bunları koda da dökmek gerekirse:

posEmoji = "😀😃😄😁😆😅😂🤣☺😊😇🙂🙃😉😌😍😘😗😙😚😋😛😝😜🤪💞🤩😏😭🤗💕❤️💙🧡💚💯👑❣️💖💫⭐🌟✨⚡🔥"
negEmoji = "😞😔😟😕🙁☹😣😖😫😩😢😓😪😭😤😠😡🤬🤯🤢🤮😷❌❗🤒🤕👹👺👻💀👽😾🖕🏿👎🏻👎🧟‍♂🧟‍♀"

Ardındanda python'a girerken temel bilgi olarak gösterilen, bir liste içerisinde sorduğunuz değerden olup olmamasına göre "True" veya "False" döndüren "in" özelliğini kullanıyorum. Elimdeli tüm pozitif ve negatif değişkenlerin sayısını tutmak içinde hepsine birer değişken oluşturup, döngüyü çalıştırıyorum:

posEmojiCount = 0
negEmojiCount = 0
notrCount = 0
for seciliEmoji in emojiler:
    if seciliEmoji in posEmoji:        
        posEmojiCount += 1
    elif seciliEmoji in negEmoji:
        negEmojiCount += 1
    else:
        notrCount += 1

 Sonrada elimdeki bu verileri görselleştirmek için "Pyplot" kütüphanesini kullanarak elma dilim grafiği üzerinde çıktımı alıyorum:

labels = 'Pozitif Emojiler', 'Negatif Emojiler', 'Notr Emojiler' #pastadaki başlıkları tanımlıyoruz.
sizes = [posEmojiCount,negEmojiCount, notrCount ] #pastada gözükecek verileri tanımlıyoruz.

fig1, ax1 = plt.subplots()
ax1.pie(sizes, labels=labels, autopct='%1.1f%%',
        shadow=True, startangle=90)
ax1.axis('equal')
plt.title("Instagram Sosyal Analizi") 
plt.savefig("analizCikti.png")
plt.show() #oluşturduğumuz pastayı çıktı alıyoruz.

 Kodumun çalışıp çalışmadığını test etmek içinde son günlerin aktif konusu olan "Ahmet Kural" - "Sıla" olaylarında başrol olan "Ahmet Kural" ın şahsi instagram hesabı üzerinde denedim. Olaylardan önceki bir gönderi, olay zamanı yollanan ve son gönderisinin analizlerini yan yana koyunca ülkemizin sosyal durumunu yansıtan bir tablo çıktı. Yorumunu size bırakıyorum. Umarım sizde kendi analizlerini yaparsınız.

Çalışmanın kodlarına Github üzerinden erişebilirsiniz: link 

Add comment