R ile WordPress İçeriklerin Listenmesi

R ile WordPress REST API üzerinden içerikleri çekip analiz edin. httr2 ve tidyverse ile modern veri işleme.

Ceyhun Enki Aksan
Ceyhun Enki Aksan Girişimci, Maker

TL;DR

WordPress REST API + Application Passwords ile R’dan içerik çekin. httr2 ile API çağrıları, tidyverse ile veri manipülasyonu yapın. Draft, publish, pending içerikleri listeleyin ve analiz edin.

BileşenAraç/Yöntem
API İstemcisihttr2::request()
Kimlik DoğrulamaApplication Passwords
Veri İşlemetidyverse (tibble, dplyr)
Endpoint/wp-json/wp/v2/posts
Max Per Page100 (pagination gerekli)

WordPress sitelerinde çoklu yazarlar, AI destekli içerik üretimi ve editoryal süreçler nedeniyle farklı durumlarda bekleyen çok sayıda içerik birikebiliyor. Taslaklar, zamanlanmış içerikler, yetim sayfalar… Bunları düzenli takip etmek, içerik stratejisinin sağlığı için kritik.

Bu yazıda R ile WordPress içeriklerinizi nasıl listeleyeceğinizi ve temel analizler yapacağınızı anlatıyorum.

öneri

İleri seviye analiz ve interaktif dashboard için R Shiny Content Intelligence Dashboard yazısına göz atın.

Gereksinimler

# Paket kurulumu
install.packages(c("httr2", "tidyverse", "jsonlite"))

library(httr2)
library(jsonlite)
library(tidyverse)

WordPress REST API Bağlantısı

WordPress 4.7+ sürümlerinde REST API varsayılan olarak aktif. Kimlik doğrulama için Application Passwords kullanacağız (WordPress 5.6+).

Application Password Oluşturma

  1. WordPress Admin → Users → Profile
  2. “Application Passwords” bölümüne gidin
  3. Yeni bir isim verin (örn: “R Script”)
  4. “Add New Application Password” tıklayın
  5. Oluşan şifreyi kopyalayın (bir kez gösterilir)
dikkat

Application Password’ü güvenli saklayın. .Renviron dosyasında environment variable olarak tutmanızı öneririm.

Yapılandırma

# .Renviron dosyasına ekleyin:
# WP_USER=kullaniciadi
# WP_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx
# WP_SITE_URL=https://siteniz.com

# R'da kullanım
wp_config <- list(
  base_url = paste0(Sys.getenv("WP_SITE_URL"), "/wp-json/wp/v2/"),
  user = Sys.getenv("WP_USER"),
  app_password = Sys.getenv("WP_APP_PASSWORD")
)

İçerikleri Çekme

#' WordPress postlarını çek
#' @param status Post durumu: publish, draft, pending, private, future
#' @param per_page Sayfa başına içerik (max 100)
#' @param page Sayfa numarası
get_wp_posts <- function(status = "publish", per_page = 100, page = 1) {

  response <- request(wp_config$base_url) |>
    req_url_path_append("posts") |>
    req_url_query(
      status = status,
      per_page = per_page,
      page = page,
      `_fields` = "id,title,excerpt,date,modified,status,categories,tags"
    ) |>
    req_auth_basic(wp_config$user, wp_config$app_password) |>
    req_perform()

  # Response'u tibble'a çevir
  content <- resp_body_json(response)

  tibble(
    id = map_int(content, "id"),
    title = map_chr(content, ~.x$title$rendered),
    excerpt = map_chr(content, ~.x$excerpt$rendered),
    date = map_chr(content, "date"),
    modified = map_chr(content, "modified"),
    status = map_chr(content, "status"),
    categories = map(content, "categories"),
    tags = map(content, "tags")
  )
}

Taslakları Listeleme

# Draft'ları çek
drafts <- get_wp_posts(status = "draft")

# Başlıkları görüntüle
drafts |>
  select(id, title, modified) |>
  arrange(desc(modified)) |>
  print(n = 20)

Çıktı:

# A tibble: 15 × 3
      id title                              modified
   <int> <chr>                              <chr>
 1   892 R Shiny ile Dashboard Geliştirme   2025-01-10T14:30:00
 2   887 WordPress API Best Practices       2025-01-08T09:15:00
 3   845 Semantic SEO Stratejileri          2024-12-22T16:45:00
...

Tüm Durumları Çekme

#' Tüm içerikleri çek (pagination ile)
get_all_posts <- function(status = "publish") {
  all_posts <- tibble()
  page <- 1
  total_pages <- Inf

  while (page <= total_pages) {
    response <- request(wp_config$base_url) |>
      req_url_path_append("posts") |>
      req_url_query(
        status = status,
        per_page = 100,
        page = page,
        `_fields` = "id,title,excerpt,date,modified,status,categories,tags"
      ) |>
      req_auth_basic(wp_config$user, wp_config$app_password) |>
      req_perform()

    if (page == 1) {
      total_pages <- as.integer(resp_header(response, "X-WP-TotalPages"))
    }

    content <- resp_body_json(response)

    posts <- tibble(
      id = map_int(content, "id"),
      title = map_chr(content, ~.x$title$rendered),
      excerpt = map_chr(content, ~.x$excerpt$rendered),
      date = map_chr(content, "date"),
      modified = map_chr(content, "modified"),
      status = map_chr(content, "status"),
      categories = map(content, "categories"),
      tags = map(content, "tags")
    )

    all_posts <- bind_rows(all_posts, posts)
    page <- page + 1
    Sys.sleep(0.5)
  }

  all_posts
}

# Tüm durumları birleştir
all_content <- bind_rows(
  get_all_posts("publish") |> mutate(status = "publish"),
  get_all_posts("draft") |> mutate(status = "draft"),
  get_all_posts("pending") |> mutate(status = "pending")
)

# Özet
all_content |>
  count(status) |>
  arrange(desc(n))

Temel Analizler

Category Dağılımı

# Category'leri çek
get_categories <- function() {
  response <- request(wp_config$base_url) |>
    req_url_path_append("categories") |>
    req_url_query(per_page = 100) |>
    req_perform()

  content <- resp_body_json(response)

  tibble(
    id = map_int(content, "id"),
    name = map_chr(content, "name"),
    count = map_int(content, "count")
  )
}

categories <- get_categories()

# En çok içerik olan kategoriler
categories |>
  arrange(desc(count)) |>
  head(10)

İçerik Yaşı Analizi

all_content |>
  mutate(
    date = as.Date(date),
    age_days = as.numeric(Sys.Date() - date),
    age_group = case_when(
      age_days < 30 ~ "Son 30 gün",
      age_days < 90 ~ "1-3 ay",
      age_days < 365 ~ "3-12 ay",
      TRUE ~ "1 yıl+"
    )
  ) |>
  count(status, age_group) |>
  pivot_wider(names_from = status, values_from = n, values_fill = 0)

Eski Draft’ları Bulma

# 90 günden eski draft'lar
stale_drafts <- drafts |>
  mutate(
    modified_date = as.Date(modified),
    days_stale = as.numeric(Sys.Date() - modified_date)
  ) |>
  filter(days_stale > 90) |>
  arrange(desc(days_stale)) |>
  select(id, title, days_stale)

cat("90+ gün önce güncellenen draft sayısı:", nrow(stale_drafts), "\n")
print(stale_drafts)

Sonraki Adımlar

Bu temel yapı ile WordPress içeriklerinizi R’dan yönetebilirsiniz. Daha ileri analizler için:

  • Semantic analiz: Embedding’lerle içerik benzerliği
  • Graph görselleştirme: İç link ilişkileri
  • Gap analizi: Eksik içerik tespiti
  • SEO alignment: Hedef keyword coverage

Bu konuları interaktif bir dashboard’da birleştiren R Shiny Content Intelligence Dashboard yazısına göz atın.

Eski Yöntem: XML-RPC (Deprecated)

dikkat

XML-RPC güvenlik riskleri nedeniyle önerilmiyor. Birçok hosting sağlayıcısı varsayılan olarak devre dışı bırakıyor. REST API kullanın.

Eski RWordPress paketi XML-RPC kullanıyordu:

# ❌ ESKİ YÖNTEM - Kullanmayın
# library("RWordPress")
# options(WordPressLogin = c(user = "pass"),
#         WordPressURL = "http://site.com/xmlrpc.php")
# getPosts()

Sık Sorulan Sorular (FAQ)

WordPress REST API R’dan nasıl kullanılır?

httr2 paketi ile req_auth_basic() fonksiyonu kullanarak Application Passwords ile kimlik doğrulama yapılır. wp-json/wp/v2/posts endpoint’ine GET isteği gönderilir.

request(base_url) |>
  req_url_path_append("posts") |>
  req_auth_basic(user, app_password) |>
  req_perform()

WordPress Application Password nedir?

WordPress 5.6+ ile gelen, REST API için güvenli kimlik doğrulama yöntemi. Admin → Users → Profile → Application Passwords bölümünden oluşturulur. Bir kez gösterilen şifreyi .Renviron dosyasında saklayın.

R ile WordPress draft’ları nasıl listelenir?

status = "draft" parametresi ile:

drafts <- get_wp_posts(status = "draft")

100’den fazla içerik için get_all_posts() fonksiyonu pagination yapar.

XML-RPC neden önerilmiyor?

XML-RPC güvenlik riskleri nedeniyle birçok hosting sağlayıcısı tarafından varsayılan olarak kapatılıyor. REST API daha güvenli, daha hızlı ve daha esnek bir alternatif.

WordPress içerik yaşı analizi nasıl yapılır?

R’da mutate() ve case_when() ile tarih farkı hesaplanır:

all_content |>
  mutate(
    age_days = as.numeric(Sys.Date() - as.Date(date)),
    age_group = case_when(
      age_days < 30 ~ "Son 30 gün",
      age_days < 90 ~ "1-3 ay",
      TRUE ~ "3 ay+"
    )
  )

Özet: Temel Çıkarımlar

  1. httr2 + tidyverse kombinasyonu modern WordPress API entegrasyonu sağlar
  2. Application Passwords güvenli kimlik doğrulama için zorunlu
  3. status parametresi ile draft/publish/pending/future filtreleme yapılır
  4. Pagination ile 100’den fazla içerik çekilebilir (X-WP-TotalPages header)
  5. XML-RPC deprecated - güvenlik nedeniyle REST API tercih edin

*[API]: Application Programming Interface *[REST]: Representational State Transfer