본문 바로가기
🤖 1인 에이전트 구축기

Meta·Google Ads 데이터를 API로 수집하는 리포팅 파이프라인

by BRIEFER 2026. 6. 17.

Meta Ads와 Google Ads의 수치를 한 대시보드에 놓으려면 API 호출보다 먼저 같은 뜻의 행을 만드는 기준이 필요합니다. 날짜, 광고 계정 시간대, 통화, 기여 기간(attribution window), 전환 정의가 다르면 이름이 같은 conversions도 직접 비교할 수 없기 때문입니다.

실무 순서는 다음과 같습니다.

권한 준비 → 플랫폼별 원본 수집 → 정의 매핑 → 정규화 → 멱등 저장 → 백필 → 원장·UI 대사 → 대시보드 제공

API 연결 자체가 데이터 무결성이나 광고 성과를 보장하지는 않습니다. 이 글에서는 출처 없는 시간 절감률·오류율·ROAS 개선 수치를 제시하지 않습니다. 수집 주기 역시 데이터가 반드시 그만큼 빨리 확정된다는 SLA가 아닙니다.

먼저 결정할 것: 두 플랫폼 수치를 어디까지 같게 볼 것인가

대시보드를 만들기 전에 보고서 계약(reporting contract)을 한 장으로 고정합니다.

항목 Google Ads 원본 예시 Meta Ads 원본 예시 정규화 규칙
날짜 segments.date date_start, date_stop 일 단위 행만 적재하고 원본 계정 시간대를 함께 관리
비용 metrics.cost_micros spend 소수 오차를 피하도록 DECIMAL 사용. Google은 micros를 1,000,000으로 나눔
통화 광고 계정 통화 광고 계정 통화 currency 열을 필수화. 통화가 다르면 합산하지 않고 별도 환율 테이블로 변환
클릭 metrics.clicks clicks 플랫폼 정의값을 그대로 보존하고 교차 매체 KPI 정의를 별도 문서화
전환 수 metrics.conversions 또는 metrics.all_conversions actions의 선택한 action_type 어떤 필드·행동 유형을 쓸지 명시하고 임의로 합치지 않음
전환 가치 metrics.conversions_value action_values의 선택한 action_type 전환 수와 동일한 전환 정의·통화를 사용
기여 기준 계정의 전환 액션·기여 설정에 따른 보고값 요청한 action_attribution_windows 등에 따른 보고값 attribution_window와 전환 정의 버전을 행에 저장
계층 customer/campaign/ad group/ad account/campaign/ad set/ad 공통 entity_level, campaign_id 등으로 매핑하되 원본 ID 유지

purchase 하나만 보더라도 두 매체의 전환 신호, 기여 모델, 클릭·조회 후 인정 기간, 보고 시점이 같다고 가정하면 안 됩니다. 캠페인 ID와 날짜만 맞춘 뒤 전환을 더하면 그럴듯하지만 설명할 수 없는 숫자가 됩니다.

웹·앱 이벤트의 의미부터 정해야 한다면 이벤트 명세와 데이터 거버넌스 설계를 먼저 참고하세요. 광고 대상 조건까지 연결할 때는 GA4 리타게팅의 조건과 제한, 최종 지표 표현은 KPI 대시보드 설계 원칙과 함께 검토할 수 있습니다.

1단계: 인증과 광고 계정 권한 준비하기

Google Ads API

Google Ads API 보고 요청에는 다음 요소가 필요합니다.

  • Google Cloud 프로젝트와 OAuth 2.0 자격 증명
  • Google Ads API **개발자 토큰(developer token)**과 해당 토큰의 접근 수준
  • 조회할 Google Ads 고객 계정에 접근 가능한 사용자 또는 서비스 계정 구성
  • 관리자 계정을 통해 하위 계정을 조회한다면 올바른 login-customer-id
  • 하이픈을 제거한 조회 대상 customer_id

OAuth 인증에 성공해도 사용자가 광고 계정을 볼 권한이 없거나 개발자 토큰의 접근 수준이 맞지 않으면 보고 요청은 실패합니다. 개발자 토큰과 OAuth refresh token은 서버 비밀 저장소에 보관하고, 브라우저 코드·로그·저장소에 넣지 마세요.

Meta Marketing API

Meta의 Ads Insights 시작 문서는 API 접근에 Meta 앱과 ads_read 권한이 필요하다고 안내합니다. 읽기 전용 리포팅이라면 불필요한 쓰기 권한을 추가하지 않는 것이 좋습니다. 운영 구성에는 다음이 필요합니다.

  • Meta 앱과 현재 앱 검수·액세스 티어에 맞는 구성
  • 광고 계정을 읽을 수 있는 사용자 또는 시스템 사용자
  • 필요한 범위가 부여된 액세스 토큰
  • 대상 광고 계정 ID(act_... 경로에서 사용)
  • 조직 정책에 맞춘 토큰 만료·교체·회수 절차

권한은 “토큰이 발급되었는가”가 아니라 그 주체가 해당 광고 계정의 Insights를 읽을 수 있는가로 검증해야 합니다. 실제 토큰을 문서, 이슈, 채팅 또는 예제 파일에 붙여 넣지 마세요.

2단계: Google Ads를 GAQL로 일 단위 수집하기

Google Ads API 보고는 GoogleAdsService.Search 또는 SearchStream에 Google Ads Query Language(GAQL)를 전달하는 방식입니다. GAQL의 SELECTFROM은 필수이고 WHERE, ORDER BY, LIMIT, PARAMETERS는 선택 사항입니다.

다음 GAQL은 캠페인·날짜별 최소 지표를 조회합니다. 운영에서는 전환 정의에 따라 metrics.conversions 대신 또는 함께 metrics.all_conversions를 검토해야 합니다.

SELECT
  customer.id,
  campaign.id,
  campaign.name,
  segments.date,
  metrics.impressions,
  metrics.clicks,
  metrics.cost_micros,
  metrics.conversions,
  metrics.conversions_value
FROM campaign
WHERE segments.date BETWEEN '2026-07-18' AND '2026-07-19'
ORDER BY segments.date, campaign.id

아래는 REST Search 요청의 형식 예시입니다. API 버전은 고정된 예제값으로 복사하지 말고 배포 시점의 지원 버전을 환경 변수로 주입합니다. CUSTOMER_ID_PLACEHOLDER 같은 표시는 실제 ID가 아닙니다.

export GOOGLE_ADS_API_VERSION='SUPPORTED_VERSION'
export GOOGLE_ADS_CUSTOMER_ID='CUSTOMER_ID_PLACEHOLDER'
export GOOGLE_ADS_LOGIN_CUSTOMER_ID='MANAGER_ID_PLACEHOLDER'

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Authorization: Bearer ${GOOGLE_ADS_ACCESS_TOKEN}" \
  --header "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
  --header "login-customer-id: ${GOOGLE_ADS_LOGIN_CUSTOMER_ID}" \
  --header 'Content-Type: application/json' \
  --data '{
    "query": "SELECT campaign.id, campaign.name, segments.date, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions, metrics.conversions_value FROM campaign WHERE segments.date BETWEEN '\''2026-07-18'\'' AND '\''2026-07-19'\'' ORDER BY segments.date, campaign.id",
    "pageSize": 1000
  }' \
  "https://googleads.googleapis.com/${GOOGLE_ADS_API_VERSION}/customers/${GOOGLE_ADS_CUSTOMER_ID}/googleAds:search"

이 요청은 이 원고 검증 과정에서 실행하지 않았습니다. 실제 토큰·개발자 토큰·광고 계정이 필요하고 운영 데이터에 접근하기 때문입니다. 계정이 관리자 계정이 아니라면 login-customer-id 헤더가 필요하지 않을 수 있으므로 계정 계층에 맞춰 구성하세요.

Search 응답에 nextPageToken이 있으면 같은 GAQL과 다음 pageToken으로 끝까지 가져옵니다. 토큰을 직접 해석하거나 새로 만들지 않습니다. 대용량 결과에 SearchStream을 선택할 수도 있지만, 응답 크기와 실패 시 재처리 범위를 함께 설계해야 합니다.

3단계: Meta Ads Insights를 같은 기간·계층으로 요청하기

Meta Ads Insights는 광고 계정, 캠페인, 광고 세트, 광고의 /insights edge에서 조회할 수 있습니다. 기간과 필드, 계층, 일 단위 증가분, 기여 기간을 요청에 명시해 기본값 의존을 줄입니다.

export META_GRAPH_API_VERSION='SUPPORTED_VERSION'
export META_AD_ACCOUNT_ID='AD_ACCOUNT_ID_PLACEHOLDER'

curl --fail-with-body --silent --show-error --get \
  --header "Authorization: Bearer ${META_ACCESS_TOKEN}" \
  --data-urlencode 'level=campaign' \
  --data-urlencode 'time_increment=1' \
  --data-urlencode 'time_range={"since":"2026-07-18","until":"2026-07-19"}' \
  --data-urlencode 'fields=account_id,campaign_id,campaign_name,date_start,date_stop,impressions,clicks,spend,actions,action_values' \
  --data-urlencode 'action_attribution_windows=["7d_click","1d_view"]' \
  "https://graph.facebook.com/${META_GRAPH_API_VERSION}/act_${META_AD_ACCOUNT_ID}/insights"

이 예제도 실제 API를 호출하지 않았습니다. 운영에서는 지원 중인 Graph API 버전과 원하는 기여 기간 조합을 현재 Insights 참조 문서에서 확인하세요. actionsaction_values는 배열이므로, 합의한 action_type—예를 들어 purchase—만 골라야 합니다. 배열 전체를 더하거나 첫 항목을 구매로 간주하면 안 됩니다.

응답의 paging.next가 있으면 URL을 불투명한 다음 페이지 포인터로 취급해 끝까지 순회합니다. 긴 기간, 많은 breakdown, 계산량이 큰 지표 때문에 동기 요청이 시간 초과되면 기간을 나누거나 공식 비동기 Insights 흐름을 검토합니다.

4단계: 원본과 정규화 테이블을 분리하기

원본 응답을 바로 대시보드 테이블에 덮어쓰면 필드 매핑 오류를 재현하기 어렵습니다. 권장 구조는 세 층입니다.

  1. raw: 응답 본문, 요청 기간, API 버전, 수집 시각, 계정 ID를 접근 통제된 저장소에 보존
  2. staging: 숫자 형식, 날짜, 행동 배열을 파싱하고 품질 규칙 검사
  3. mart: 플랫폼 공통 열과 명시한 KPI 정의로 제공

정규화 일 테이블은 다음처럼 시작할 수 있습니다.

CREATE TABLE ad_daily (
  platform           VARCHAR(20)   NOT NULL,
  account_id         VARCHAR(64)   NOT NULL,
  campaign_id        VARCHAR(64)   NOT NULL,
  report_date        DATE          NOT NULL,
  account_timezone   VARCHAR(64)   NOT NULL,
  currency           CHAR(3)       NOT NULL,
  attribution_window VARCHAR(64)   NOT NULL,
  conversion_def_ver VARCHAR(32)   NOT NULL,
  impressions        BIGINT        NOT NULL,
  clicks             BIGINT        NOT NULL,
  spend              DECIMAL(20,6) NOT NULL,
  conversions        DECIMAL(20,6) NOT NULL,
  conversion_value   DECIMAL(20,6) NOT NULL,
  collected_at       TIMESTAMP     NOT NULL,
  PRIMARY KEY (
    platform, account_id, campaign_id, report_date,
    attribution_window, conversion_def_ver
  )
);

정수 전환만 가정하지 않은 이유는 플랫폼 보고 설정과 모델링에 따라 전환 지표가 소수로 나타날 수 있기 때문입니다. 금액은 이진 부동소수점 대신 DECIMAL로 저장합니다. 원본 계정 통화와 변환 통화를 섞지 말고, 환산이 필요하면 환율 출처·기준일·버전을 별도 열로 남깁니다.

5단계: 페이지네이션·사용 제한·부분 실패를 수집기에서 처리하기

페이지네이션

  • Google Search: nextPageToken이 사라질 때까지 같은 쿼리로 순회
  • Meta Insights: paging.next가 사라질 때까지 순회
  • 각 페이지 수, 원본 행 수, 마지막 토큰의 해시를 실행 로그에 기록
  • 모든 페이지가 성공하기 전에는 해당 날짜 파티션을 완료 상태로 바꾸지 않음

rate limit과 재시도

Google Ads API는 개발자 토큰 접근 수준과 요청 유형에 따른 한도·할당량을 적용하며, 한도 위반은 RESOURCE_EXHAUSTED 등으로 반환될 수 있습니다. Meta는 앱·광고 계정·비즈니스 사용 사례 등에 제한을 적용하고 X-Ad-Account-Usage, X-Business-Use-Case, X-FB-Ads-Insights-Throttle 같은 응답 헤더로 사용량 정보를 제공합니다.

운영 재시도 규칙은 다음처럼 명시합니다.

  • 429, 일시적 5xx, 네트워크 타임아웃만 제한적으로 재시도
  • Retry-After가 있으면 우선 따르고, 없으면 지수 백오프와 jitter 적용
  • 최대 시도 횟수와 최대 대기 시간을 넘으면 실패 큐로 이동
  • 401은 토큰 만료·회수, 403은 권한·계정 접근을 점검하고 맹목적으로 재시도하지 않음
  • 요청량을 줄이기 위해 필요한 필드만 선택하고 계정·기간을 적절히 분할
  • 제한 관련 응답 헤더와 요청 ID는 비밀값을 제거한 뒤 관측 시스템에 기록

정확한 할당량 숫자를 코드 상수로 박아 두지 마세요. 접근 티어, 활성 광고 수, 정책 및 API 버전에 따라 달라질 수 있으므로 현재 공식 문서와 실제 응답 헤더를 기준으로 운영합니다.

6단계: 백필과 중복 제거를 한 설계로 묶기

광고 지표와 전환은 최초 수집 후 수정될 수 있습니다. 따라서 증분 수집만 하지 말고 최근 N일을 다시 읽는 rolling backfill을 둡니다. 여기서 N일은 플랫폼 보장값이 아니라 팀이 전환 지연과 비용을 관찰해 정하는 운영 파라미터입니다.

예를 들어 스케줄이 실행될 때마다 다음을 수행합니다.

  1. 계정별 워터마크와 현재 날짜를 읽음
  2. 현재 날짜 - BACKFILL_DAYS부터 완료된 전일까지 재조회
  3. 임시 테이블에 모든 페이지를 적재
  4. 행 수, 날짜 범위, 필수 키, 음수·NULL 규칙 검사
  5. 복합 키로 UPSERT 또는 날짜 파티션 교체
  6. 성공한 범위만 워터마크 갱신
  7. 더 오래된 기간은 별도 수동 백필 작업으로 분리

중복 제거 키에는 최소 platform, account_id, entity_id, report_date가 필요합니다. 전환 정의나 기여 기간을 동시에 여러 버전으로 운영하면 이들도 키에 포함해야 합니다. 단순히 “이미 본 행 수만큼 건너뛰기”는 페이지 변경과 수정 보고를 처리하지 못합니다.

수집기를 15분마다 실행할 수는 있지만, 15분은 선택한 스케줄이지 데이터 신선도 보장값이 아닙니다. 원천 플랫폼에서 아직 확정되지 않은 값은 더 자주 호출해도 확정되지 않습니다. 대시보드에는 source_report_date, collected_at, 마지막 성공 시각, 백필 범위를 함께 표시하세요.

7단계: 실제 계정 없이 로컬 동작 검증하기

다음 로컬 데모는 네트워크를 사용하지 않습니다. placeholder ID와 합성 Google·Meta 응답으로 다음 네 가지를 검사합니다.

  • Google nextPageToken과 Meta paging.next 순회
  • 합성 429 한 번을 재시도
  • micros 비용과 Meta purchase 배열을 공통 행으로 변환
  • 같은 백필을 두 번 실행해도 SQLite 복합 키 UPSERT로 행이 늘지 않음

전체 검증 스크립트는 원고 작성 시 /tmp/entry44_demo.py로 실행했으며, 외부 패키지 없이 Python 표준 라이브러리만 사용했습니다. 핵심 적재 로직은 다음과 같습니다.

from decimal import Decimal


def fetch_google(pages):
    rows, token = [], None
    while True:
        page = pages[token]
        rows.extend(page["results"])
        token = page.get("nextPageToken")
        if not token:
            return rows


def fetch_meta(transport, max_retries=3):
    rows, cursor = [], None
    while True:
        for attempt in range(max_retries + 1):
            status, page, retry_after = transport.get(cursor)
            if status == 200:
                break
            if status != 429 or attempt == max_retries:
                raise RuntimeError(f"Meta request failed: HTTP {status}")
            print(f"synthetic 429: retry_after={retry_after}, attempt={attempt + 1}")
        rows.extend(page["data"])
        cursor = page.get("paging", {}).get("next")
        if not cursor:
            return rows


def selected_action(items, action_type="purchase"):
    return sum(
        (Decimal(item["value"]) for item in items
         if item["action_type"] == action_type),
        Decimal("0"),
    )


UPSERT_SQL = """
INSERT INTO ad_daily VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(platform, account_id, campaign_id, report_date, attribution_window)
DO UPDATE SET
  currency=excluded.currency,
  impressions=excluded.impressions,
  clicks=excluded.clicks,
  spend=excluded.spend,
  conversions=excluded.conversions,
  conversion_value=excluded.conversion_value
"""

합성 fixture와 테이블 생성·assertion을 포함한 전체 스크립트를 실제로 실행한 결과는 다음과 같았습니다.

synthetic 429: retry_after=0, attempt=1
google_rows=2 meta_rows=2 normalized_rows=4
rows_after_second_load=4 simulated_rate_limit_retries=1
local assertions: PASS

이 결과는 로컬 합성 데이터에 대한 코드 경로 검증일 뿐, 실제 광고계정 권한, API 응답 스키마, 수치 일치 또는 운영 성능을 증명하지 않습니다. 실제 배포 전에는 테스트 권한이 있는 계정에서 최소 기간·최소 필드로 별도 통합 테스트를 하고, 플랫폼 UI와 날짜·통화·전환별로 대사해야 합니다.

8단계: 운영 대사와 품질 경보 만들기

API가 요청에 성공했다고 숫자의 의미까지 맞는 것은 아닙니다. 최소한 다음 검사를 계정·날짜별로 자동화합니다.

검사 방법 실패 시 확인할 것
완전성 요청 날짜 수와 적재 날짜 수 비교 빈 결과, 시간대 경계, 중간 페이지 실패
유일성 복합 기본 키 중복 검사 재시도 시 append, 기여 기준 누락
유효성 필수 키·통화·시간대·정의 버전의 NULL 검사 신규 계정 설정, 스키마 변경
범위 비용·노출·클릭의 비정상 음수 또는 급변 탐지 환불성 지표 여부, 파싱 오류, 캠페인 변경
대사 같은 계정·기간·기여 기준으로 플랫폼 UI와 비교 UI 필터, 시간대, 전환 정의, 보고 지연
신선도 마지막 성공 시각과 대상 보고일 표시 인증 만료, 제한, 스케줄러 장애

“차이가 0이어야 한다”를 무조건 규칙으로 두기보다 허용 오차의 이유와 승인자를 문서화합니다. 백필이 값의 변경을 발견하면 이전 값, 새 값, 수집 시각, 실행 ID를 감사 로그에 남깁니다. 성공 행 수만 보지 말고 계정별 실패와 누락 날짜를 별도 경보로 올립니다.

실패 조건·비용·정책 한계

  • 권한과 검수: OAuth만으로 Google Ads 개발자 토큰 접근 승인이 대체되지 않으며, Meta 토큰만으로 앱 검수·광고 계정 권한이 대체되지 않습니다.
  • API 버전: 지원 종료 버전은 요청이 실패하거나 필드 의미가 바뀔 수 있습니다. 버전을 환경 설정으로 관리하고 업그레이드 회귀 테스트를 둡니다.
  • 할당량과 시간 초과: 큰 날짜 범위·많은 필드·세분화 조합은 비용과 실패 가능성을 키웁니다. 날짜를 분할하고 필요하면 비동기 흐름을 사용합니다.
  • 저장·컴퓨팅 비용: API 읽기 외에도 raw 보관, 웨어하우스 쿼리, 오케스트레이터, 모니터링 비용이 발생할 수 있습니다. 보존 기간과 파티셔닝을 정합니다.
  • 개인정보와 플랫폼 정책: 광고 데이터의 저장·결합·공유·보존은 적용 법률, 계약, Meta 플랫폼 약관과 Marketing API 개발자 정책, Google Ads API 정책을 함께 검토해야 합니다.
  • 보고값 수정: 전환 지연, 모델링, 무효 트래픽 처리, 설정 변경 등으로 과거 값이 달라질 수 있습니다. rolling backfill과 변경 이력을 운영합니다.
  • 비교 한계: 두 플랫폼의 클릭, 전환, 도달, 기여 기준은 동일한 측정기가 아닙니다. 공통 열 이름이 의미의 동일성을 보장하지 않습니다.

배포 체크리스트

  • Google OAuth, 개발자 토큰, 접근 수준, 고객 계정 권한을 각각 확인했다.
  • Meta 앱, ads_read, 토큰 주체, 광고 계정 접근 권한을 확인했다.
  • 비밀값을 서버 비밀 저장소에 두고 로그·코드·브라우저에서 제외했다.
  • 계정 시간대와 통화를 메타데이터로 저장한다.
  • 전환 필드·action_type·기여 기간·정의 버전을 매핑표에 고정했다.
  • Google과 Meta의 모든 페이지를 끝까지 순회한다.
  • 429·일시적 5xx만 제한적으로 재시도하고 영구 오류를 분리한다.
  • 최근 기간 rolling backfill과 오래된 기간 수동 백필 경로가 있다.
  • 복합 키 UPSERT 또는 파티션 교체가 재실행에도 중복을 만들지 않는다.
  • 부분 페이지 실패 시 완료 워터마크를 갱신하지 않는다.
  • API 버전과 원본 응답 스키마 변경을 감시한다.
  • 같은 날짜·시간대·통화·기여 기준으로 플랫폼 UI 대사를 했다.
  • 대시보드에 마지막 성공 시각과 데이터 기준을 표시한다.

자주 묻는 질문

Google Ads와 Meta Ads의 전환을 바로 더해도 되나요?

권장하지 않습니다. 전환 소스, 기여 모델·기간, 클릭·조회 포함 여부, 중복 처리 기준을 먼저 맞춰야 합니다. 맞출 수 없다면 플랫폼별 전환을 분리해 보여 주고 공통 KPI는 주문 원장처럼 별도의 기준 데이터로 계산합니다.

15분마다 수집하면 실시간 대시보드인가요?

아닙니다. 15분은 수집기가 요청을 시도하는 주기일 뿐입니다. 원천 데이터 반영 시간, API 제한, 백필, 웨어하우스 갱신 시간을 포함한 실제 신선도를 측정해 표시해야 합니다.

모든 원본 응답을 영구 보관해야 하나요?

반드시 영구 보관해야 한다는 뜻은 아닙니다. 재처리·감사 요구와 저장 비용, 플랫폼 정책, 개인정보·계약상 보존 제한을 함께 고려해 기간을 정하세요. 원본에는 액세스 통제와 삭제 정책이 필요합니다.

API 수집으로 수동 보고서 오류가 사라지나요?

아닙니다. 복사·붙여넣기 단계는 줄일 수 있지만 잘못된 쿼리, 누락된 페이지, 다른 시간대, 통화 혼합, 전환 정의 불일치 같은 자동화 오류가 생길 수 있습니다. 멱등 적재, 품질 검사, UI·원장 대사가 필요합니다.

공식 문서

아래 문서는 2026년 7월 20일에 URL 응답과 본문을 확인했습니다. API 버전·권한·제한은 바뀔 수 있으므로 배포 시점에 다시 확인하세요.