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

Blogger API와 OAuth 2.0을 이용해 액세스 토큰을 발급하고 글을 작성·공개하는 흐름을 정리합니다.

블로그 글을 코드로 자동 발행하려면 Blogger API와 OAuth 2.0 인증이 필요합니다. Blogger 자동 발행을 예로 액세스 토큰 발급부터 글 생성과 공개 전환까지 정리합니다.

이 글의 가정: 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는 상태코드뿐 아니라 요청이 멱등한지를 함께 봐야 합니다. GET처럼 다시 보내도 같은 자원을 읽는 요청은 429나 일시적인 5xx에서 제한된 횟수로 재시도할 수 있습니다. 반면 posts.insert는 새 글을 만드는 POST이므로, 요청을 보낸 뒤 타임아웃이나 5xx가 났다고 곧바로 같은 요청을 다시 보내면 첫 요청이 이미 처리된 경우 중복 글이 생길 수 있습니다.

  • 읽기 요청: 429·일시적 5xx에서 Retry-After 또는 지수 백오프를 적용합니다.
  • 글 생성(posts.insert): 응답 결과가 불명확하면 자동 재시도를 멈추고 최근 글 목록이나 관리자 화면에서 생성 여부를 먼저 확인합니다. 성공 응답을 받았다면 반환된 post ID를 저장합니다.
  • 발행(publish): 이미 알고 있는 post ID를 사용하되, 응답이 불명확하면 현재 게시 상태를 조회한 뒤 다시 호출할지 결정합니다.
  • 400·403·404: 요청·권한·대상 오류를 먼저 수정하고 같은 요청을 반복하지 않습니다. 401은 토큰 갱신 뒤 제한적으로 다시 시도합니다.

자동화에서는 로컬 작업 ID와 Blogger post ID를 함께 기록해, 같은 작업을 다시 실행할 때 이미 만들어진 글을 재사용할 수 있게 두는 편이 안전합니다.

마무리

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

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