R İle Site Haritası (Sitemap) Oluşturma

R ile ilgili yakın zamanda yayınladığım yazılarda sitemap içeriğinin kontrolü ve web sitesi tarama işlemlerine değinmiştim. Bu yazıda ise, önceki yazıları birleştirip farklı bir açıdan yeniden ele alacağım.

AA

Sitemap (site haritası), ile ilgili yayınladığım Google Search Console İçin Site Haritası başlıklı yazıda da belirttiğim üzere, site haritası tüm site içi bağlantıları içereceği gibi içerik türleri ve/veya görseller, videolar gibi belirli bağlamlarda da hazırlanabilir.

Aşağıda yer alan örnekte site içi bağlantılarda yer alan tanımlara göre farklı site haritaları oluşturulacak. Akıl karışıklığı olmaması adına önden bilgilendirmek istedim.

Rcrawler İle Site Tarama İşlemi

R İle Web Sitesi Tarama ve Veri Kazıma İşlemi başlıklı yazıda Rcrawler paketinden detaylıca bahsetmeye çalışmış ve örnek olarak bir tarama işlemi gerçekleştirmiştim1. Bu yazıda yine Rcrawler paketini kullanacak ve benzer şekilde bir web sitesini tarayacağım. Ardından, edindiğim URL bilgilerini kullanarak site haritaları oluşturacağım. Örnek işlem için doparank tarafından paylaşılmış How to create XML Sitemap with R başlıklı yazıyı temel aldım2.

Örnek işlem sürecinde, genel olarak Rcrawler, dplyr ve stringr paketlerini kullanacağım.

library(Rcrawler)
library(dplyr)
library(stringr)

CustomXPaths <- c("//link[@rel='canonical']/@href",
                  "//meta[@name='robots']/@content")

CustomLabels <- c("link_canonical",
                  "meta_robots")

CustomXPaths kazıyacağımız alanları belirtip CustomLabels ile bu alanlara başlık vermiş oluyoruz. Eğer sayfa tipleriniz CMS aracılığı ile bir class olarak body etiketine iliştiriliyor ya da meta veri olarak sunuluyorsa elbete bu alanları da CustomXPaths içerisine dahil etmek oldukça faydalı olacaktır. Aksi durumda, URL içerisinde yer alan ifadelere göre ayrıştırmak gerekecek. Eğer içerik türlerini (post, page, listing, product, vb.) ayrıştıran tanımlar URL içerisinde yer almıyorsa işler biraz daha zorlaşacaktır.

https://domain.com/product/product-name
https://domain.com/post/post-name

https://domain.com/product-name
https://domain.com/post-name

Yukarıdaki örnekte de görüldüğü üzere ilk 2 URL tanımını ayrışırmak çok daha kolay olacaktır. Artık çalışma dizinimizi de belirtip tarama işlemini başlatabiliriz.

setwd("~/Desktop")

Rcrawler(Website = "https://domain.com/",
         ExtractXpathPat = CustomXPaths, 
         PatternsNames = CustomLabels)

saveRDS(DATA, file="DATA.rds")
saveRDS(INDEX, file="INDEX.rds")
Site Haritası

URL Sınıflandırma ve Sitemap Oluşturma İşlemi

Tarama işleminin sonuçlanması web sitesinde yer alan sayfa sayısına göre farklılık gösterecektir. Bu işlem sonucunda elde edilecek DATA ve INDEX listelerini farkı bir zaman içerisinde yeniden kullanmak üzere kayıt altına alabilirsiniz.

Artık bu 2 tabloyu listeyi birleştirip veri tiplerini düzenleyerek bir sonraki aşamaya geçebiliriz.

mergedCrawl <- cbind(INDEX, data.frame(do.call(rbind, DATA)))
mergedCrawl$Id <- as.integer(mergedCrawl$Id)

Indexable_pages <- mergedCrawl %>%
  mutate(Canonical_Indexability = ifelse(Url == link_canonical | is.na(mergedCrawl$link_canonical), TRUE, FALSE)) %>%
  mutate(Indexation = ifelse(grepl("NOINDEX|noindex", mergedCrawl$meta_robots), FALSE, TRUE)) %>%
  filter(Canonical_Indexability == TRUE & Indexation == TRUE)

Yukarıdaki işlem ile birlikte yeni 2 sütun oluşturduk. Url ile link_canonical alanlarını karşılaştırıp ilgili sayfanın canonical tanımına sahip ve index için uygun olup olmadığını kontrol ettik. Her iki sütunun da TRUE değerine sahip olması ilgili URL'in sitemap içerisinde yer alması için yeterli.

Artık URL içeriğine bağlı olarak URL'leri ayrıştırabiliriz3.

Sitemaps <- Indexable_pages %>%
  filter(`Http Resp` == '200' & `Content Type` == 'text/html') %>%
  select(Url) %>%
  mutate(Content_type =
           ifelse(str_detect(Indexable_pages$Url, "/caegory|tag/"), "Taxonomy",
                  ifelse(str_detect(Indexable_pages$Url, "/list|listings/"), "Listing",
                         ifelse(str_detect(Indexable_pages$Url, "/shop|contact/"), "Pages",
                                ifelse(str_detect(Indexable_pages$Url, "/locations"), "Locations",
                                       ifelse(str_detect(Indexable_pages$Url, "/product"), "Products", "Posts")))))) %>%
  group_by(Content_type) %>%
  unique() %>%
  arrange(Content_type)

Sitemaps$Content_type <- as.factor(Sitemaps$Content_type)

Bu işlemin sonunda Content_type adında belirlediğimiz içerik tiplerini içeren bir sütun daha oluşturmuş olduk. İlgili sütuna ait veri tipini factor olarak belirleyebiliriz.

Sıradaki işlemimiz belirlediğimiz Content_type içeriğine bağlı olarak sayfaları ayrıştırmak.

Sitemap_taxonomy <- Sitemaps %>% filter(Content_type == "Taxonomy")
Sitemap_listing <- Sitemaps %>% filter(Content_type == "Listing")
Sitemap_pages <- Sitemaps %>% filter(Content_type == "Pages")
Sitemap_places <- Sitemaps %>% filter(Content_type == "Places")
Sitemap_products <- Sitemaps %>% filter(Content_type == "Products")
Sitemap_posts <- Sitemaps %>% filter(Content_type == "Posts")

Hangi içerik tipinde kaçar adet URL'in yer aldığına bir bakalım.

Sitemaps %>% 
  group_by(Content_type) %>%
  summarise(no_rows = n())

Bu aşamadan sonrası çok farklı biçimlerde ele alınabilir. Eğer ilgili URL'leri tekrar sorgulamak ve last-modified gibi HEADER içeriklerini kontrol etmek isterseni httr4 ya da curl gibi paketlerle istekler gerçekleştirip devam edebilirsiniz ya da herhangi bir ek istek gerçekleştirmeden istediğiniz herhangi bir tarihi lastmod değeri olarak atayabilirsiniz.

createSitemap <- function (links = list(), fileName = format(Sys.time(), "%y-%m-%d_%H-%M-%S")) {

  require(whisker)
  require(httr)

  cat("Please wait...", "\n")
  cat("Total Link: ", length(links), "\n")

  template <- '<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
 {{#links}}
   <url>
      <loc>{{{loc}}}</loc>
      <lastmod>{{{lastmod}}}</lastmod>
      <changefreq>{{{changefreq}}}</changefreq>
      <priority>{{{priority}}}</priority>
   </url>
 {{/links}}
</urlset>'

  map_links <- function(url) {
    tmp <- GET(url)
    # https://www.stat.berkeley.edu/~s133/dates.html
    date <- format(as.Date(strptime(tmp$headers$date, format = '%a, %d %b %Y %H:%M:%S', tz = "UTC")), "%Y-%m-%d")
    sys_date <- format(Sys.time(), "%Y-%m-%d")

    list(loc = url,
         lastmod = ifelse(!is.na(date), date, sys_date),
         changefreq = "monthly",
         priority = "0.8")
  }

  links <- lapply(links, map_links)
  cat(whisker.render(template, partials = links), file = paste(fileName, ".xml", sep = ""))
}

Görüldüğü üzere, createSitemap fonksiyonu httr ve whisker5 paketlerine ait çeşitli fonksiyonlar barındırmakta. Whisker ile ilgili daha önce yayınladığım R Dilinde Mustache Template Sistemi Kullanımı başlıklı yazıma da göz atabilirsiniz5. GET ile istekleri yönetirken, whisker.render ile XML şablonu içerisinde yer alan yer tutuculara ilgili veriler aktarılmakta ve cat ile dosya olarak kayıt edilmekte.

GET aşamasını es geçip doğrudan URL'leri yazdırabilirsiniz.

Son aşamada artık istediğimiz site haritalarını az önce oluşturduğumuz createSitemap fonksiyonu aracılığı ile dosya haline getirebiliriz.

createSitemap(links = Sitemap_listing$Url, file = "listing")
createSitemap(links = Sitemap_pages$Url, file = "pages")
createSitemap(links = Sitemap_places$Url, file = "places")
createSitemap(links = Sitemap_products$Url, file = "products")
createSitemap(links = Sitemap_posts$Url, file = "posts")
createSitemap(links = Sitemap_taxonomy$Url, file = "taxonomy")

İşlemlerimiz bu kadar. Oluşturulan site haritalarını uygun bir dizine ekleyip Search Console üzerinden dizine eklemeniz yeterli. Örnekte bölümler halinde açıkladığım R kod parçacığını bütün olarak görüntülemek için ceaksan/R_createXMLSitemap.R başlıklı gist'e göz atabilirsiniz.