Class: Seo::GscKeywordSyncService

Inherits:
BaseService show all
Defined in:
app/services/seo/gsc_keyword_sync_service.rb

Overview

Syncs keyword rankings from Google Search Console for a single SiteMap page.

Used as the primary keyword data source by KeywordSyncService.
Can also be called directly when Ahrefs fallback is not desired.

GSC advantages over Ahrefs for rank tracking:

  • Actual Google data (no estimates)
  • No per-page API quota limits
  • Covers ALL indexed pages including blog posts
  • Returns real clicks & impressions (better traffic_share accuracy)

GSC limitations:

  • No global search volume (filled by Google Keyword Planner)
  • Average position over the period, not a single-day snapshot
  • Only shows queries where the site appeared in search results

Examples:

Sync a single page

Seo::GscKeywordSyncService.new(site_map: site_map).process

Sync without Google KWP volume enrichment (faster)

Seo::GscKeywordSyncService.new(site_map: site_map, skip_google: true).process

Constant Summary collapse

MAX_KEYWORDS =
100
GOOGLE_BATCH_SIZE =
20
LOOKBACK_DAYS =
90

Instance Method Summary collapse

Methods inherited from BaseService

#log_debug, #log_error, #log_info, #log_warning, #logger, #options, #tagged_logger

Constructor Details

#initialize(options = {}) ⇒ GscKeywordSyncService

Returns a new instance of GscKeywordSyncService.

Raises:

  • (ArgumentError)


31
32
33
34
35
36
37
# File 'app/services/seo/gsc_keyword_sync_service.rb', line 31

def initialize(options = {})
  super
  @site_map = options[:site_map]
  @limit = options[:limit] || MAX_KEYWORDS
  @skip_google = options[:skip_google] || false
  raise ArgumentError, 'site_map is required' unless @site_map
end

Instance Method Details

#processObject



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'app/services/seo/gsc_keyword_sync_service.rb', line 39

def process
  @logger.info "[GscKeywordSyncService] Syncing #{@site_map.id}: #{@site_map.path}"

  queries = fetch_queries
  if queries.blank?
    @logger.info "[GscKeywordSyncService] No GSC queries for #{@site_map.path}"
    return { keywords_synced: 0, source: 'gsc' }
  end

  @logger.info "[GscKeywordSyncService] #{queries.size} queries from GSC"

  queries = enrich_with_search_volume(queries) unless @skip_google

  synced_count = save_keywords(queries)

  @site_map.update!(
    seo_synced_at: Time.current,
    seo_top_keyword: top_keyword_from(queries),
    seo_top_position: top_position_from(queries)
  )

  @logger.info "[GscKeywordSyncService] Saved #{synced_count} keywords for #{@site_map.path}"
  { keywords_synced: synced_count, source: 'gsc' }
rescue StandardError => e
  @logger.error "[GscKeywordSyncService] Error for #{@site_map.path}: #{e.message}"
  { keywords_synced: 0, error: e.message }
end