검색 엔진에서 자신의 웹사이트가 어떤 키워드로 몇 위에 나타나는지 알아야 SEO 전략을 세울 수 있다. 다만 Google Search Console의 웹 인터페이스에서 수동으로 확인하다 보면 시간이 너무 오래 걸리고, 매달 순위 변동을 추적하기도 어렵다. 이번에는 Google Search Console API를 PHP에서 직접 연동해서 검색 순위와 클릭수, 노출수를 자동으로 수집하고 데이터베이스에 저장하는 완벽한 방법을 소개하겠다.

 

1단계: Google Search Console API 인증 설정하기
Google Search Console API를 사용하려면 먼저 Google Cloud Console에서 프로젝트를 만들고 서비스 계정 인증 키를 발급받아야 한다.

✓ Google Cloud Console에서 새 프로젝트 만들기

1. Google Cloud Console에 접속한다.
2. 상단의 프로젝트 선택 드롭다운 → "새 프로젝트" 클릭
3. 프로젝트 이름 입력 (예: "SEO-Monitoring") → "만들기" 클릭
4. 프로젝트가 생성될 때까지 기다린 후 해당 프로젝트 선택

✓ Search Console API 활성화하기

1. 좌측 메뉴에서 "API 및 서비스" → "라이브러리" 클릭
2. 검색창에 "Search Console API" 입력
3. Search Console API 선택 → "활성화" 클릭

✓ 서비스 계정 키 발급받기

1. "API 및 서비스" → "사용자 인증 정보" 클릭
2. "사용자 인증 정보 만들기" → "서비스 계정" 선택
3. 서비스 계정 이름 입력 (예: "seo-api") → "만들기" 클릭
4. "이 단계는 선택사항입니다" 부분은 건너뛰고 → "계속" 클릭
5. 서비스 계정 목록에서 방금 만든 계정 클릭 → "키" 탭
6. "키 추가" → "새 키" → "JSON" 선택 → "만들기" 클릭
7. JSON 파일이 자동 다운로드됨 (이 파일을 안전한 곳에 저장)

✓ Google Search Console에 서비스 계정 추가하기

1. Google Search Console에 접속
2. 모니터링할 속성(웹사이트) 선택
3. "설정" → "사용자 및 권한" 클릭
4. "사용자 추가" 클릭
5. 다운로드한 JSON 파일에서 "client_email" 값을 복사해서 입력
6. 권한 선택: "소유자" 또는 "편집자" → "초대" 클릭

 

2단계: PHP에서 Google Search Console API 연동하기
PHP에서 Google Search Console API를 사용하려면 Google API 클라이언트 라이브러리를 설치해야 한다.
composer require google/apiclient

Composer를 설치하지 않았다면 프로젝트 디렉토리에서 먼저 composer를 초기화하고 라이브러리를 설치하자.

composer init
composer require google/apiclient

 

기본 연동 코드 구조
<?php
// Composer 자동로더 포함
require 'vendor/autoload.php';

use Google\Client;
use Google\Service\SearchConsole;

// Google API 클라이언트 인스턴스 생성
$client = new Client();

// 다운로드한 서비스 계정 JSON 파일 경로 설정
$client->setAuthConfig('/path/to/service-account-key.json');

// Search Console API에 필요한 스코프 설정
$client->addScope('https://www.googleapis.com/auth/webmasters');

// Search Console 서비스 인스턴스 생성
$searchConsole = new SearchConsole($client);

// API 요청 예: 특정 속성의 검색 성능 데이터 조회
$siteUrl = 'https://example.com'; // 모니터링할 사이트 URL

// 요청 본문 생성
$requestBody = new \Google\Service\SearchConsole\SearchAnalyticsQueryRequest();
$requestBody->setStartDate(date('Y-m-d', strtotime('-30 days'))); // 30일 이전부터
$requestBody->setEndDate(date('Y-m-d')); // 오늘까지
$requestBody->setDimensions(['query', 'page', 'country', 'device']); // 조회할 데이터 차원
requestBody->setRowLimit(10000); // 최대 10,000행 반환

// API 호출
$response = $searchConsole->searchanalytics->query($siteUrl, $requestBody);

// 결과 처리
if (!empty($response->getRows())) {
    foreach ($response->getRows() as $row) {
        echo "쿼리: " . $row->getKeys()[0] . "\n";
        echo "클릭수: " . $row->getClicks() . "\n";
        echo "노출수: " . $row->getImpressions() . "\n";
        echo "평균 순위: " . number_format($row->getPosition(), 1) . "\n\n";
    }
} else {
    echo "데이터가 없습니다.\n";
}
?>

 

✓ 올바른 코드 설명

위 코드에서 중요한 부분들:

  • setAuthConfig() - 다운로드한 서비스 계정 JSON 파일의 절대경로를 입력. 상대경로 사용 금지
  • addScope() - Search Console API 접근 권한. "webmasters" 스코프는 읽기 전용
  • setDimensions() - 조회할 데이터 차원. 'query'(검색어), 'page'(페이지), 'country'(국가), 'device'(기기) 등 조합 가능
  • setRowLimit() - 한 번에 반환받을 최대 행 수. 기본값 1000, 최대 10,000
  • getPosition() - 평균 검색 순위 반환 (소수점 포함)
  • getClicks() - 검색 결과 클릭 수
  • getImpressions() - 검색 결과에 노출된 횟수

 

3단계: 순위 데이터 자동 수집 및 저장
실무에서는 API 응답 데이터를 MySQL 데이터베이스에 저장해야 시간에 따른 순위 변동을 추적할 수 있다.
<?php
require 'vendor/autoload.php';

use Google\Client;
use Google\Service\SearchConsole;

// MySQL 데이터베이스 연결
$dsn = 'mysql:host=localhost;dbname=seo_monitoring;charset=utf8mb4';
$user = 'root';
$pass = '';
$pdo = new PDO($dsn, $user, $pass);

// 테이블이 없으면 생성
$createTableSQL = "CREATE TABLE IF NOT EXISTS search_rankings (
    id INT AUTO_INCREMENT PRIMARY KEY,
    site_url VARCHAR(255) NOT NULL,
    search_query VARCHAR(500) NOT NULL,
    page_url TEXT NOT NULL,
    clicks INT DEFAULT 0,
    impressions INT DEFAULT 0,
    position DECIMAL(5, 2),
    country VARCHAR(10) DEFAULT 'KR',
    device VARCHAR(20),
    date_tracked DATE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY unique_rank (site_url, search_query, page_url, date_tracked)
)";
$pdo->exec($createTableSQL);

// Google API 클라이언트 초기화
$client = new Client();
$client->setAuthConfig('/path/to/service-account-key.json');
$client->addScope('https://www.googleapis.com/auth/webmasters');
$searchConsole = new SearchConsole($client);

$siteUrl = 'https://example.com';
$today = date('Y-m-d');

// 과거 데이터 중복 저장 방지: 오늘 데이터가 이미 있는지 확인
$checkStmt = $pdo->prepare("SELECT COUNT(*) FROM search_rankings WHERE site_url = ? AND date_tracked = ?");
$checkStmt->execute([$siteUrl, $today]);
$rowCount = $checkStmt->fetchColumn();

if ($rowCount > 0) {
    echo "오늘 데이터는 이미 저장되었습니다.\n";
    exit;
}

// Search Console API 데이터 조회
$requestBody = new \Google\Service\SearchConsole\SearchAnalyticsQueryRequest();
$requestBody->setStartDate($today); // 오늘 데이터만 조회
$requestBody->setEndDate($today);
$requestBody->setDimensions(['query', 'page', 'device']);
$requestBody->setRowLimit(10000);

try {
    $response = $searchConsole->searchanalytics->query($siteUrl, $requestBody);
    
    if (!empty($response->getRows())) {
        $stmt = $pdo->prepare("INSERT INTO search_rankings 
            (site_url, search_query, page_url, clicks, impressions, position, device, date_tracked) 
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE 
            clicks = VALUES(clicks), 
            impressions = VALUES(impressions), 
            position = VALUES(position)"
        );
        
        $insertCount = 0;
        foreach ($response->getRows() as $row) {
            $keys = $row->getKeys();
            $query = $keys[0]; // 첫 번째 dimension
            $page = $keys[1];  // 두 번째 dimension
            $device = $keys[2] ?? 'all'; // 세 번째 dimension (있을 경우)
            
            $stmt->execute([
                $siteUrl,
                $query,
                $page,
                (int)$row->getClicks(),
                (int)$row->getImpressions(),
                (float)$row->getPosition(),
                $device,
                $today
            ]);
            $insertCount++;
        }
        
        echo "✓ {$insertCount}개의 검색 데이터가 저장되었습니다.\n";
    } else {
        echo "조회 가능한 데이터가 없습니다.\n";
    }
} catch (Exception $e) {
    echo "✗ API 호출 실패: " . $e->getMessage() . "\n";
}
?>

 

✗ 잘못된 접근 vs ✓ 올바른 접근
문제✗ 잘못된 코드✓ 올바른 해결
매일 중복으로 데이터 저장쿼리마다 바로 INSERTdate_tracked로 UNIQUE 제약, ON DUPLICATE KEY UPDATE 사용
오늘 데이터가 아직 불완전함오늘 데이터를 매번 새로 조회/저장어제 데이터부터 조회, 오늘은 다음날 새벽에만 저장
API 할당량 초과1시간마다 자주 호출하루 1~2회(새벽/저녁)만 호출, 크론잡으로 자동화
예외 처리 없음API 에러 무시하고 계속 진행try-catch로 에러 캐치, 로그 남기기

 

4단계: 순위 변동 추적 및 분석 쿼리
저장된 데이터를 분석해서 순위가 올랐는지 내렸는지 확인하는 쿼리들이다.
-- 지난 30일간 검색어별 평균 순위
SELECT 
    search_query,
    ROUND(AVG(position), 1) AS avg_position,
    MAX(position) AS worst_position,
    MIN(position) AS best_position,
    SUM(clicks) AS total_clicks,
    SUM(impressions) AS total_impressions,
    ROUND(SUM(clicks) / SUM(impressions) * 100, 2) AS ctr_percent
FROM search_rankings
WHERE site_url = 'https://example.com'
    AND date_tracked >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY search_query
ORDER BY avg_position ASC;

-- 순위가 상승한 검색어 (최근 7일 vs 이전 7일)
SELECT 
    sr1.search_query,
    ROUND(AVG(sr1.position), 1) AS recent_position,
    ROUND(AVG(sr2.position), 1) AS prev_position,
    ROUND(AVG(sr2.position) - AVG(sr1.position), 1) AS improvement
FROM search_rankings sr1
JOIN search_rankings sr2 
    ON sr1.search_query = sr2.search_query 
    AND sr1.site_url = sr2.site_url
WHERE sr1.site_url = 'https://example.com'
    AND sr1.date_tracked >= DATE_SUB(NOW(), INTERVAL 7 DAY)
    AND sr2.date_tracked BETWEEN DATE_SUB(NOW(), INTERVAL 14 DAY) 
        AND DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY sr1.search_query
HAVING improvement > 0
ORDER BY improvement DESC;

 

5단계: 크론 잡으로 자동 수집 스케줄링
매일 자동으로 순위 데이터를 수집하도록 Linux 크론 잡을 설정하자.
# crontab 편집 열기
crontab -e

# 매일 오전 8시에 데이터 수집 (한국 시간, UTC+9)
0 23 * * * /usr/bin/php /var/www/html/collect-rankings.php >> /var/log/seo-ranking.log 2>&1

# 설명:
# 0 23 - 매일 UTC 23시(한국시간 오전 8시) 실행
# * * * - 매월, 매주, 매일
# /usr/bin/php - PHP 실행 경로
# /var/www/html/collect-rankings.php - 위에서 만든 수집 스크립트 경로
# >> /var/log/seo-ranking.log 2>&1 - 출력을 로그 파일에 저장

크론 잡이 제대로 등록되었는지 확인하려면:

crontab -l

 

주의사항 및 흔한 실수

✗ JSON 파일을 웹 루트에 저장
서비스 계정 비밀키가 외부에 노출되어 악의적 API 호출 가능. 웹 서버 접근 불가능한 상위 디렉토리에 저장하거나, 환경 변수로 관리하자.

✓ 보안 저장 방식

// 환경 변수로 서비스 계정 정보 전달
$serviceAccountJson = getenv('GOOGLE_SERVICE_ACCOUNT_JSON');
$client->setAuthConfig(json_decode($serviceAccountJson, true));

// 또는 .env 파일 사용 (dotenv 라이브러리)
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$client->setAuthConfig($_ENV['GOOGLE_SERVICE_ACCOUNT_KEY_PATH']);

✗ 데이터가 없어도 계속 API 호출
Google Search Console은 최소 16개월의 데이터만 제공하고, 신규 사이트나 검색량 많지 않은 페이지는 데이터가 없을 수 있다. 무한정 호출하면 API 할당량 낭비.

✓ 데이터 존재 여부 확인 후 처리

// API 호출 전 최소 날짜 확인
$sixteenMonthsAgo = date('Y-m-d', strtotime('-16 months'));
if (strtotime($startDate) < strtotime($sixteenMonthsAgo)) {
    $startDate = $sixteenMonthsAgo;
}

// API 응답 빈 배열 처리
if (empty($response->getRows())) {
    echo "이 기간에는 조회 가능한 데이터가 없습니다.\n";
    exit; // 불필요한 계속 진행 방지
}

✗ 같은 속성의 HTTP/HTTPS 버전을 혼동
Google Search Console에서 "https://example.com"로 등록했는데 API에서 "http://example.com"으로 요청하면 데이터를 찾을 수 없다.

✓ 속성 URL 일치 확인

// Search Console에 등록된 정확한 속성 이름 사용
$siteUrl = 'https://example.com/'; // 슬래시 포함 주의
// 또는
$siteUrl = 'sc-domain:example.com'; // 도메인 속성인 경우

 

마무리

Google Search Console API는 검색 순위 데이터를 자동으로 수집해서 SEO 성과를 객관적으로 추적할 수 있는 강력한 도구다. 수동으로 Google Search Console 웹에 들어가 매번 확인하는 비효율에서 벗어나, 자동화된 모니터링으로 시간을 절약할 수 있다는 점을 잊지 말자. 이 글의 순위 수집 스크립트와 MySQL 저장 방식을 참고해 자신의 사이트에 맞게 커스터마이징하면, 앞으로 매일 자동으로 쌓이는 순위 데이터로 장기 SEO 전략을 수립하고 경쟁사와의 성과 비교도 가능해질 것이다.