Blogger API와 OAuth 2.0으로 블로그 글 자동 발행하기

블로그 글을 코드로 자동 발행하려면 Blogger API와 OAuth 2.0 인증이 필요합니다. 실제로 이 블로그를 운영하는 자동화 서버가 사용하는 흐름을 기준으로, 액세스 토큰 발급부터 글 생성과 공개 전환까지 정리합니다.

이 글의 가정: Google 계정과 Blogger 블로그가 있고, Google Cloud에서 OAuth 클라이언트(client_id, client_secret)와 refresh token을 이미 발급받았다고 가정합니다. 발급 절차 자체는 Google 공식 인증 흐름을 따릅니다. 비밀값은 코드에 직접 쓰지 말고 환경변수로 관리합니다.

OAuth 2.0 refresh token 흐름

refresh token(장기 보관용 재발급 토큰)은 한 번 발급받아 두고, API를 호출할 때마다 짧은 수명의 access token(실제 요청에 쓰는 접근 토큰)으로 교환해서 사용합니다. 즉 "refresh token → access token → API 호출" 순서입니다.

액세스 토큰 발급

토큰 교환은 Google 토큰 엔드포인트에 grant_type=refresh_token으로 요청합니다. 아래는 비동기 HTTP 클라이언트 httpx를 사용한 예입니다.

import os, httpx

TOKEN_URL = "https://oauth2.googleapis.com/token"
API_BASE = "https://www.googleapis.com/blogger/v3"

async def get_access_token():
    async with httpx.AsyncClient(timeout=10.0) as client:
        resp = await client.post(TOKEN_URL, data={
            "client_id": os.environ["BLOGGER_CLIENT_ID"],
            "client_secret": os.environ["BLOGGER_CLIENT_SECRET"],
            "refresh_token": os.environ["BLOGGER_REFRESH_TOKEN"],
            "grant_type": "refresh_token",
        })
        resp.raise_for_status()
        return resp.json()["access_token"]

여기서는 호출할 때마다 토큰을 새로 받는 단순한 방식을 씁니다. 토큰을 캐싱해 재사용하는 최적화는 별도로 추가할 수 있습니다.

글 생성 (초안 또는 공개)

글 생성은 posts 엔드포인트에 제목과 본문 HTML을 실어 보냅니다. isDraft 파라미터로 초안으로 만들지, 바로 공개할지 정합니다.

async def create_post(blog_id, title, html, as_draft=True):
    token = await get_access_token()
    params = {"isDraft": "true"} if as_draft else {}
    url = f"{API_BASE}/blogs/{blog_id}/posts/"
    async with httpx.AsyncClient(timeout=10.0) as client:
        resp = await client.post(
            url,
            headers={"Authorization": f"Bearer {token}"},
            json={"title": title, "content": html},
            params=params,
        )
        resp.raise_for_status()
        return resp.json()["id"]

초안을 공개로 전환

초안으로 만들어 둔 글은 별도의 publish 엔드포인트로 공개 상태로 바꿉니다.

async def publish_draft(blog_id, post_id):
    token = await get_access_token()
    url = f"{API_BASE}/blogs/{blog_id}/posts/{post_id}/publish"
    async with httpx.AsyncClient(timeout=10.0) as client:
        resp = await client.post(url, headers={"Authorization": f"Bearer {token}"})
        resp.raise_for_status()
        return resp.json().get("url")

정리하면 현재 흐름은 글 생성(create) + 발행(publish)으로 구성됩니다. 즉 새 글을 올리고 공개 상태로 전환하는 데까지가 이 코드의 범위입니다.

재시도 처리

외부 API는 일시적으로 실패할 수 있어, 어떤 오류는 다시 시도하고 어떤 오류는 즉시 멈추는 편이 안전합니다. 인증·권한·잘못된 요청 계열(400, 401, 403, 404)은 다시 보내도 결과가 같으므로 즉시 실패시키고, 서버 일시 오류(500, 502, 503)나 타임아웃만 몇 차례 재시도합니다.

NO_RETRY = {400, 401, 403, 404}
RETRY = {500, 502, 503}

마무리

refresh token으로 access token을 교환한 뒤 글을 생성하고 공개하는 것이 Blogger 자동 발행의 핵심 흐름입니다. 위 예제에서 반복적으로 쓰인 비동기 HTTP 호출 패턴은 httpx AsyncClient로 외부 API 비동기 호출하기에서 더 자세히 다룹니다. 발행 서버의 기반이 되는 FastAPI와 PostgreSQL 연동하기, 그리고 전체 자동화 그림은 유튜브 쇼츠를 Python으로 자동 생성하는 방법을 함께 보면 좋습니다.

이 글이 도움이 됐다면 구독과 공유 부탁드립니다.

댓글

이 블로그의 인기 게시물

UGREEN DXP4800PLUS NAS로 유튜브 쇼츠 자동화 시스템 구축기 (Docker · FastAPI · PostgreSQL · Edge-TTS)

YouTube Data API 쿼터 초과를 피하는 방법 (700개 쇼츠 수집 사례)

FastAPI app.mount 사용 후 API가 404가 되는 이유와 해결 방법