Veriyi anlamak ve doğru analiz edebilmek günümüzde strateji oluşturabilmenin temeli halindedir. Blog sayfalarının organik performansını analiz etmek, kullanıcı niyetini anlamak ve bu bağlamda da strateji geliştirip görünürlük sağlamak için kritik bir adımdır. Bu noktada ise Google BigQuery ve çeşitli LLM’ler kullanarak veri odaklı içgörüleri hızla ve etkili bir şekilde elde etme imkânı mümkün. Bu yazıda, BigQuery ve LLM’leri kullanarak blog sayfalarının organik performansını nasıl analiz edip, analiz doğrultusunda SEO optimizasyonunu nasıl yapabileceğini dilim döndüğünce anlatacağım.
1- Python Kullanarak Veri Setinin Hazırlanması
Analiz yapabilmek adına öncelikli olarak bir veri seti hazırlamanız gerekiyor. Ben veri setini hazırlamak için Python kullandım. Bu analizi de blog sayfaları için yapacağım için (Benim adıma bunu yapmak daha kolay siz çalıştığınız sektöre bağlı olarak farklı şekilde ilerleyebilirsiniz.) sadece blog içerisindeki URL’lere ihtiyacım var.
Başlamadan önce tüm süreç boyunca kullanacağımız kütüphaneler;
import advertools as adv
import requests
from bs4 import BeautifulSoup
import pandas as pd
import json
import os
from google.cloud import bigquery
from google.oauth2 import service_account
import numpy as np
from datetime import datetime, timedelta, timezone
import seaborn as sns
import matplotlib.pyplot as plt
from langchain_ollama import OllamaLLM
from langchain_core.prompts import ChatPromptTemplate
Blog sitemap adresine istek atarak, URL adreslerini topluyorum. Bunun için advertools kullanacağım. Kodu;
content = adv.sitemap_to_df('https://example.com/article-sitemap.xml')
content.head(10)
content_list = content["loc"]
content_df = content_list.to_frame(name='loc')
Buraya kadar olan kısımda sitemap üzerinden URL’leri çekip sonrasında bir dataframe haline getirdim.
İkinci aşamada analiz edilmesini istediğimiz parçaları scrape edeceğiz. Daha önce ifade ettiğim gibi bu kısmı siz ihtiyacınız doğrultusunda değiştirebilirsiniz. Bu yazı için seçtiklerim genelde hızlı scrape edebileceğim alanlar oldu. Seçtiklerim; H1, H2 elementleri, içeriğin uzunluğu, içeriğin kategorisi, yayınlanma ve güncelleme tarihi. Kategori ve yayınlanma-güncelleme tarihlerini schema içerisinden çektim ancak schema kullanmayan bir siteyi scrape edeceğim ben derseniz o kısımları BeautifulSoup kullanarak alabilirsiniz.
h1_values = []
h2_values = []
content_char_count = []
article_section_values = []
date_published_values = []
date_modified_values = []
for url in content_df['loc']:
try:
response = requests.get(url)
response.raise_for_status()
soup = BeautifulSoup(response.content, 'html.parser')
h1 = soup.find('h1')
h1_text = h1.get_text().strip() if h1 else "H1 bulunamadı"
h1_values.append(h1_text)
h2_tags = soup.find_all('h2')
h2_texts = [h2.get_text().strip() for h2 in h2_tags]
h2_values.append(h2_texts if h2_texts else ['H2 bulunamadı'])
article = soup.find('#element-name', class_='class-or-id-value') ## ihtiyacınıza göre değiştirin bunu.
article_text = article.get_text().strip() if article else ""
content_char_count.append(len(article_text))
schema_tag = soup.find('script', type='application/ld+json')
if schema_tag:
try:
schema_data = json.loads(schema_tag.string)
article_section = schema_data.get('articleSection', 'articleSection bulunamadı')
date_published = schema_data.get('datePublished', 'datePublished bulunamadı')
date_modified = schema_data.get('dateModified', 'dateModified bulunamadı')
except json.JSONDecodeError:
article_section = 'articleSection alınamadı'
date_published = 'datePublished alınamadı'
date_modified = 'dateModified alınamadı'
else:
article_section = 'articleSection bulunamadı'
date_published = 'datePublished bulunamadı'
date_modified = 'dateModified bulunamadı'
article_section_values.append(article_section)
date_published_values.append(date_published)
date_modified_values.append(date_modified)
except Exception as e:
print(f"Hata oluştu: {url}, {e}")
h1_values.append(None)
h2_values.append(['H2 alınamadı'])
content_char_count.append(None)
article_section_values.append('articleSection alınamadı')
date_published_values.append('datePublished alınamadı')
date_modified_values.append('dateModified alınamadı')
content_df['h1'] = h1_values
content_df['h2'] = h2_values
content_df['content_char_count'] = content_char_count
content_df['articleSection'] = article_section_values
content_df['datePublished'] = date_published_values
content_df['dateModified'] = date_modified_values
print(content_df)
Bu noktadan sonra sonuca göre içeriklere flag atabilirsiniz. Kişisel olarak flag atmanızı tavsiye ederim özellikle analiz kısmında sizi daha da rahatlatır. Ya da içerik uzunluğuna göre vs ascending olarak bir dizilim yaratabilirsiniz.
Ascending dizilim yapmak için;
content_df_sorted = content_df.sort_values(by='content_char_count', ascending=False)
print(content_df_sorted)
Flag atmak adına;
conditions = [
(content_df['content_char_count'] <= 200), (content_df['content_char_count'] > 200) & (content_df['content_char_count'] <= 1000), (content_df['content_char_count'] > 1000)
]
flags = ['Low-Quality Content', 'Good-Quality Content', 'Great-Quality Content']
content_df['flag'] = np.select(conditions, flags, default='unknown')
print(content_df)
Buradaki flagleri de ihtiyacınız ya da zevkiniz doğrultusunda kafanıza göre değiştirebilirsiniz.
BigQuery Üzerinden Trafik Verisinin Alınması
Bu aşamadan sonra BigQuery ‘de bulunan search console tablosuna istek atarak istediğimiz verileri çekeceğiz.
SCOPES = [
"https://www.googleapis.com/auth/bigquery",
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/bigquery.readonly"
]
credentials = service_account.Credentials.from_service_account_file(
'Service-account-json-key-file.json', ## burası kişisel kendi credentials’ınızı kullanmanız gerekiyor.
scopes=SCOPES
)
client = bigquery.Client(credentials=credentials, project='project-id') #proje-id değeriniz tam buraya gelecek.
urls = [url.lower().strip() for url in filtered_df['loc'].tolist()]
query = """
WITH normalized_data AS (
SELECT
LOWER(TRIM(url)) as normalized_url,
SUM(clicks) as total_clicks,
SUM(impressions) as total_impressions,
SUM(sum_position) as total_position
FROM `table-name`
WHERE DATE(data_date) >= '2023-10-01'## belli bir tarih sonrasını çekmek için burayı da istediğiniz gibi değiştirin.
GROUP BY LOWER(TRIM(url))
)
SELECT
normalized_url as url,
total_clicks as clicks,
total_impressions as impressions,
ROUND(total_position / NULLIF(total_impressions, 0) + 1, 2) AS avg_position
FROM normalized_data
WHERE normalized_url IN UNNEST(@urls)
"""
job_config = bigquery.QueryJobConfig(
query_parameters=[
bigquery.ArrayQueryParameter("urls", "STRING", urls)
]
)
query_job = client.query(query, job_config=job_config)
results = query_job.result()
rows = [dict(row) for row in results]
traffic_df = pd.DataFrame(rows)
if not traffic_df.empty:
filtered_df['clicks'] = 0
filtered_df['impressions'] = 0
filtered_df['avg_position'] = 0
for index, row in traffic_df.iterrows():
mask = filtered_df['loc'].str.lower().str.strip() == row['url']
filtered_df.loc[mask, 'clicks'] = row['clicks']
filtered_df.loc[mask, 'impressions'] = row['impressions']
filtered_df.loc[mask, 'avg_position'] = row['avg_position']
print(filtered_df[['loc', 'clicks', 'impressions', 'avg_position']].head())
else:
print("Veri gelmiyor ya da gelemedi. Tarih aralığını bir kontrol ediver.")
Filtered_df içerisine CTR verisini eklemek için;
filtered_df['ctr'] = np.where(filtered_df['impressions'] > 0,
filtered_df['clicks'] / filtered_df['impressions'],
0)
filtered_df['ctr'] = (filtered_df['ctr'] * 100).round(2).astype(str) + '%'
print(filtered_df[['loc', 'clicks', 'impressions', 'avg_position', 'ctr']].head())
Şimdi elimizde, ilgili URL’lerin organik performans değerleri de bulunuyor. Bununla beraber, BQ’da olmayan average position, ctr gibi değerleri de yine dataframe içine ekledik. Artık bu verilere bağlı olarak da analiz yapabiliriz ve flag atabiliriz. Atacağımız flagler,
Clicks verisine göre flag atıp performans değerlendirmesi;
def traffic_flag(row):
if row['clicks'] > 1000:
return 'Top Content'
elif row['clicks'] < 100:
return 'Low Performance'
else:
return 'Average Content'
filtered_df['traffic_flag'] = filtered_df.apply(traffic_flag, axis=1)
print(filtered_df[[‘loc’, ‘clicks’, ‘traffic_flag’]])
Güncelleme tarihine göre flag atılması
filtered_df['dateModified'] = pd.to_datetime(filtered_df['dateModified'], errors='coerce')
def content_recency_flag(row):
if pd.isnull(row['dateModified']):
return 'Unknown'
elif row['dateModified'] >= datetime.now(tz=row['dateModified'].tzinfo) - timedelta(days=30):
return 'Fresh Content'
elif row['dateModified'] < datetime.now(tz=row['dateModified'].tzinfo) - timedelta(days=365):
return 'Outdated Content'
else:
return 'Moderate Content'
filtered_df['recency_flag'] = filtered_df.apply(content_recency_flag, axis=1)
print(filtered_df[['loc', 'dateModified', 'recency_flag']])
Filtered_df içerisinde veri setimizi tamamlamış bulunmaktayız. Bu verilerin üstünden de analizi yapacağız.
Veri Setine Bağlı Analiz Yapılması
Veriyi analiz edebilmek için tek tek değerlere göre analizler çalışabiliriz. Örneğin, içerik uzunluğu ile trafik arasındaki korelasyon için bir kod yazabiliriz ya da son güncelleme tarih ile ortalama pozisyon arasındaki korelasyonu ölçümleyebiliriz. Hatta bir örnek kodu aşağıya bırakıyorum. Ancak tüm bunları yapmak temel hedefimize bizi yaklaştırmadığı gibi daha çok raporlama olmuş olacak yani bir analiz yapmamış, yapamamış olacağız.
Benim buradaki temel amacım ise bu analizleri otomatize edip nokta atış çıktıları görerek aksiyon önerilerini de almak.
Korelasyon ölçümlemek adına örnek kod;
correlation = filtered_df['clicks'].corr(filtered_df['content_char_count'])
print(f"Clicks ve Content Char Count Arasındaki Korelasyon: {correlation:.2f}")
plt.figure(figsize=(10, 6))
sns.scatterplot(data=filtered_df, x='content_char_count', y='clicks', alpha=0.6, color='royalblue')
plt.title('Clicks ve Content Char Count Arasındaki İlişki')
plt.xlabel('Content Character Count')
plt.ylabel('Clicks')
plt.show()
LLM Kullanarak Analiz Yapılması
Yukarıda bahsettiğim gibi analizi yapabilmek ve aksiyon çıkaracak to-do pointleri daha rahat görebilmek adına LLM kullanacağız. Bu aşamada istediğiniz LLM’i kullanabilirsiniz. Ben ücretsiz ve iyi çalıştığını düşündüğüm için Ollama’yı kullandım. Ollama’yı nasıl kurup, kullanabileceğinizi de aşağıda ayrıca anlatıyor olacağım.
Ollama ile analiz yapabilmek için aslında klasik olarak bir prompt yazmanız gerekiyor. Prompt noktasında oluşturduğunuz veri setine bağlı olarak ve tabi ki ihtiyacınıza göre bir prompt oluşturun. SEO analizi yapabilmek için kullanabileceğiniz Ollama kod örneğin;
model = OllamaLLM(model="llama3.1")
# Prompt
prompt_template = """
İstediğiniz promptu ve promt içerisinde kullanacağız başlıkları buraya değişken olarak tanımlayın. Bununla birlikte, kullanacağınız değişkenleri de aşağıdaki fonksiyon içerisine ekleyin.
"""
def analyze_with_ollama(row):
prompt = prompt_template.format(
loc=row[‘loc’],
h1=row[‘h1’],
content_char_count=row[‘content_char_count’],
clicks=row[‘clicks’]
)
response = model.predict(prompt)
return response
filtered_df[‘analysis’] = filtered_df.apply(analyze_with_ollama, axis=1)
filtered_df.to_csv(“verdigin-isim.csv”)
Yukarıdaki kodu da çalıştırdığınızda analizlerinde içerisinde bulunduğu bir dataframe oluşmuş olacak. Dediğim gibi buradaki analizi ihtiyacınıza göre prompt yazarak şekillendirebilirsiniz ve buna bağlı olarak da sonuçlar alabilirsiniz.
Ollama Nasıl Kurulur?
Kurulumu oldukça basit. Öncelikle https://ollama.com adresine gidip işletim sisteminiz neyse ona uygun olan dosyayı indirin. Kurulum tamamlandıktan sonra ise terminale “ollama” yazın. Çıkan ekranda zaten komutları görüyor olacaksınız.
Son olarak kullanmak istediğiniz modeli pull yapmanız gerekiyor. Ben 3.1 modelini kullandım. Pull yapmak için terminale aşağıdaki komutu yazın;
Ollama pull llama3.1
Diğer modelleri görmek için github linkini ziyaret edebilirsiniz.
https://github.com/ollama/ollama buradan bakıp bilgisayarınıza bağlı istediğiniz modeli indirebilirsiniz.
YASAL UYARI
Projeyi kullanırken oluşabilecek BigQuery sorgu maliyetleri veya diğer Google Cloud Platform hizmetlerinden kaynaklanan masraflar tamamen kullanıcı sorumluluğundadır. Proje geliştiricisi bu maliyetler ile ilgili herhangi bir sorumluluk kabul etmez.
Çalışmanın Örnek Çıktısı
Örnek çıktı için Zapier’in Blog sayfaları üzerinden bir çalışma yaptım. Ancak Zapier’in BigQuery hesabına erişemeyeceğim için Search Console verilerini örnek olarak verdim.
Yukarıda verdiğim veri seti oluşturma kodunu çalıştırınca böyle bir dataframe karşınıza çıkacak.
Şu an dataframe için 1 URL bulunuyor çünkü örnek oluşturabilmesi adına tek bir url seçip ekledim.
Ollama ile analiz yaptığımda ise gelen sonuç örneğinin ekran görüntüsünü aşağıda paylaşıyorum.
İlk Yorumu Siz Yapın