자동 Indexing 요청 프로그램 - Google Gemini

2025. 9. 29. 22:52AI 프로그래밍

다음은 Google Gemini CLI를 이용하여 만든 자동 Indexing Too 입니다. Indexing은 Google Search Console과 Bing Webmaster에 대해 수행합니다.


개 요

자신의 블로그 글을 새로 발행했을 때에 naver나 google에서는 언제 이 글을 indxing 할지 모릅니다. Indexnow가 있어서 새롭게 글이 발행될때마다 indexing을 위해 바로 알릴 수 있다고는 하지만 티스토리 블로그에서는 이를 적용하기가 불가능합니다. (현재까지 제가 알기론..)

그래서 Google Gemini CLI를 통해서 Google Search Console과 Bing에서 새로운 글을 indexing할 수 있도록 자동으로 제출하는 툴을 만들어봤습니다.

처음에는 이 블로그를 통해 수집하고자하는 블로그 사이트 맵 주소를 치면 수집 요청을 할 수 있게 만들어보려 했으나 티스토리 블로그는 그러한 프로그램을 실행할 수 없게 되어 이를 하려면 별도의 서버에서 프로그램을 수행시켜 놓고 블로그 상에서는 링크를 통해 그 프로그램에 접속하는 방식이 되어야만 했습니다.

불필요하게 개인적인 API가 노출될 우려가 있고 복잡하기도해서 그냥 다음과 같이 파이썬 코드를 만들고 공유하고자 합니다. 

 


 

파이썬 코드

import os
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse
import time

# Google API 관련 라이브러리
from google.oauth2 import service_account
from google.auth.transport.requests import Request, AuthorizedSession

# --- 설정 ---
SITEMAP_URL = "https://자신의 블로그 주소/sitemap.xml"
BING_API_KEY = "자신의 Bing API Key"  # 사용자님의 API 키
SUBMITTED_URLS_FILE = 'submitted_urls.txt'

# Google과 Bing의 일일 제출 할당량 제한
GOOGLE_API_QUOTA_LIMIT = 200
BING_API_QUOTA_LIMIT = 100
# ---------------------------------------------------------

def get_google_credentials():
    """service_account.json 파일에서 구글 API 인증 정보를 가져옵니다."""
    creds_path = 'service_account.json'
    if not os.path.exists(creds_path):
        print("[안내] service_account.json 파일을 찾을 수 없어 구글 인덱싱을 건너뜁니다.")
        return None
    
    try:
        credentials = service_account.Credentials.from_service_account_file(
            creds_path,
            scopes=['https://www.googleapis.com/auth/indexing']
        )
        if credentials.expired:
            credentials.refresh(Request())
        return credentials
    except Exception as e:
        print(f"[오류] service_account.json 파일 처리 중 오류 발생: {e}")
        return None

def send_to_google_api(urls, credentials):
    """주어진 URL 목록을 구글 인덱싱 API로 전송합니다."""
    print(f"\n--- Google API에 제출 시작 ({len(urls)}개) ---")
    endpoint = 'https://indexing.googleapis.com/v3/urlNotifications:publish'
    authed_session = AuthorizedSession(credentials)
    
    success_urls = []
    for url in urls:
        payload = {'url': url, 'type': 'URL_UPDATED'}
        try:
            response = authed_session.post(endpoint, json=payload)
            response.raise_for_status()
            print(f"[성공] {url}")
            success_urls.append(url)
        except requests.exceptions.RequestException as e:
            try:
                error_message = e.response.json().get('error', {}).get('message', e.response.text)
            except:
                error_message = e.response.text
            print(f"[실패] {url} - {error_message}")
        time.sleep(0.3) # API 요청 사이에 약간의 지연을 줍니다. 
    print("--- Google API 제출 완료 ---")
    return success_urls

def send_to_bing_api(urls, site_url, api_key):
    """주어진 URL 목록을 빙 인덱싱 API로 100개씩 나누어 전송합니다."""
    print(f"\n--- Bing API에 제출 시작 ({len(urls)}개) ---")
    endpoint = f"https://ssl.bing.com/webmaster/api.svc/json/SubmitUrlbatch?apikey={api_key}"
    
    # URL 목록을 100개씩 나누기 (Bing의 일일 할당량 및 배치 한도 고려)
    chunk_size = 100
    url_chunks = [urls[i:i + chunk_size] for i in range(0, len(urls), chunk_size)]
    
    success_urls = []
    for i, chunk in enumerate(url_chunks):
        print(f"[{i+1}/{len(url_chunks)}] {len(chunk)}개 URL 묶음을 제출합니다.")
        payload = {
            "siteUrl": site_url,
            "urlList": chunk
        }
        try:
            response = requests.post(endpoint, json=payload, timeout=30)
            response.raise_for_status()
            data = response.json()
            # Bing API는 성공 시 응답에 'd' 키를 포함하지만 특별한 값을 가지지 않음
            if data.get('d') is None:
                print(f"[성공] {len(chunk)}개 URL 제출 요청이 수락되었습니다.")
                success_urls.extend(chunk)
            # 오류 코드가 있는 경우
            elif data.get('ErrorCode'):
                print(f"[실패] {data.get('ErrorCode')} - {data.get('Message')}")
            else:
                print(f"[알 수 없는 응답] {data}")

        except requests.exceptions.RequestException as e:
            error_details = e
            if e.response is not None:
                try:
                    error_details = e.response.json()
                except ValueError:
                    error_details = e.response.text
            print(f"[오류] Bing API 요청 중 오류 발생: {error_details}")
        time.sleep(1) # 각 API 호출 사이에 잠시 대기
            
    print("--- Bing API 제출 완료 ---")
    return success_urls

def load_submitted_urls():
    """제출 기록 파일에서 URL 목록을 불러옵니다."""
    if not os.path.exists(SUBMITTED_URLS_FILE):
        return set()
    with open(SUBMITTED_URLS_FILE, 'r', encoding='utf-8') as f:
        return set(line.strip() for line in f)

def save_submitted_urls(urls):
    """제출된 URL을 파일에 추가로 기록합니다."""
    with open(SUBMITTED_URLS_FILE, 'a', encoding='utf-8') as f:
        for url in urls:
            f.write(url + '\n')

def main():
    """메인 실행 함수"""
    print("인덱싱 스크립트를 시작합니다.")

    try:
        # 1. 사이트맵에서 전체 URL 목록 추출
        print(f"사이트맵 분석 중: {SITEMAP_URL}")
        response = requests.get(SITEMAP_URL, timeout=15)
        response.raise_for_status()
        soup = BeautifulSoup(response.content, 'lxml')
        all_urls = {loc.text for loc in soup.find_all('loc')}
        
        if not all_urls:
            print("[오류] 사이트맵에서 URL을 찾을 수 없습니다. 주소를 확인해주세요.")
            return

        print(f"사이트맵에서 총 {len(all_urls)}개의 URL을 찾았습니다.")

        # 2. 이전에 제출한 URL 목록과 비교하여 새로운 URL만 선별
        submitted_urls = load_submitted_urls()
        urls_to_submit = sorted(list(all_urls - submitted_urls), reverse=True) # 최신글부터 제출

        if not urls_to_submit:
            print("\n[알림] 제출할 새로운 URL이 없습니다. 모든 URL이 이미 제출되었습니다.")
            print("\n모든 작업이 완료되었습니다.")
            return

        print(f"\n제출할 새로운 URL {len(urls_to_submit)}개를 찾았습니다.")
        site_host = urlparse(SITEMAP_URL).netloc
        
        newly_submitted_urls = set()

        # 3. Google API 처리 (할당량에 맞춰 URL 목록 슬라이싱)
        google_creds = get_google_credentials()
        if google_creds:
            google_urls_for_today = urls_to_submit[:GOOGLE_API_QUOTA_LIMIT]
            if google_urls_for_today:
                google_success = send_to_google_api(google_urls_for_today, google_creds)
                newly_submitted_urls.update(google_success)

        # 4. Bing API 처리 (할당량에 맞춰 URL 목록 슬라이싱)
        if BING_API_KEY:
            bing_urls_for_today = urls_to_submit[:BING_API_QUOTA_LIMIT]
            if bing_urls_for_today:
                bing_success = send_to_bing_api(bing_urls_for_today, f"https://{site_host}", BING_API_KEY)
                newly_submitted_urls.update(bing_success)
        else:
            print("\n[안내] Bing API 키가 설정되지 않았으므로 빙 인덱싱을 건너뜁니다.")
            
        # 5. 성공적으로 제출된 URL 목록 파일에 저장
        if newly_submitted_urls:
            print(f"\n{len(newly_submitted_urls)}개의 URL을 제출 기록에 추가합니다.")
            save_submitted_urls(newly_submitted_urls)

        print("\n모든 작업이 완료되었습니다.")

    except requests.exceptions.RequestException as e:
        print(f"[오류] 사이트맵 주소에 접속할 수 없습니다: {e}")
    except Exception as e:
        print(f"[오류] 처리 중 예기치 않은 오류가 발생했습니다: {e}")

if __name__ == '__main__':
    main()

 

위의 코드에서 자신의 블로그 주소는 직접 넣어야 하며 Bing의 API 키도 확인하여 직접 넣어야 합니다.

 

위의 코드는 CMD 창에서 프로그램이 있는 해당 폴더로 들어가서 "python 프로그램명.py"로 실행시킬 수 있습니다.

실행 시키면 Google의 하루 indexing 요청 수량인 200개와 Bing의 하루 요청 가능 수량 100개에 대한 글 url이 제출되게 되어있습니다. 

제출된 url들은 동일 폴더에 "submitted_urls"란 txt 파일로 생성되도록 되어 있어서 제출된 url들을 확인할 수 있습니다.

 

자동 인덱싱 요청 프로그램 실행 화면
(자동 인덱싱 요청 프로그램 실행 화면)


 

결론

다시한번 느끼지만 요즘은 아이디어만 있으면 무엇이던지 쉽게 만들어볼 수 있는 세상인 것 같습니다. 그 아이디어 마저도 AI에 물어보는 세상이긴 하다는 것이 좀 아이러니 하기도 합니다.

이번 프로그램도 소스 코드 자체는 한줄 한줄 이해하지는 못합니다. 다만, 내가 원하는 기능을 하는지만 보면 되고 그게 아니면 계속해서 AI에게 수정을 요청하면 됩니다.

여기서 빠른 시간 내가 원하는 목적의 기능을 구현하려면 AI에게 어떻게 물어보고 어떻게 지시하냐가 매우 중요하다는 점을 느낍니다. 너무 간략히 요구하면 점점 프로그램은 이상한 방향으로 수정되고 결국은 프로젝트를 처음부터 다시 시작해야할 정도로 엉망이 되기도 합니다.

항상 올바른 방향으로의 답을 주는 "정답 프롬프트"는 없는 것 같습니다. AI가 명령을 주는 사람도 어떻게 수정되고 있는지 이해하면서 상황에 맞는 적절한 명령을 AI에게 주는 것이 최선인 것 같습니다.   

 

 

 


 

반응형