파이썬마케팅 자동화파이썬

파이썬으로 광고 캠페인 성과 진단기를 만들었습니다

· 2026-08-19 발행· 11분 읽기

목차

지난 글에서 마케터가 파이썬을 배워야 하는 이유를 이야기했어요.

반복 업무를 자동화하고, 흩어진 데이터를 직접 연결하고, 사람이 매번 하던 일을 재사용 가능한 시스템으로 바꿀 수 있다고요.

그런데 말만으로는 잘 와닿지 않죠. 그래서 그로스앤디 랩의 두 번째 실험에서는 설명보다 먼저 작동하는 도구를 만들어봤습니다.

광고 데이터를 입력하면 핵심 지표를 계산하고, 목표 달성 여부와 잘못된 데이터까지 알려주는 ‘광고 캠페인 성과 진단기’입니다.

먼저 직접 사용해보세요

목표 ROAS와 CPA를 정하고 캠페인별 광고비·노출·클릭·전환·매출을 입력하면 됩니다. 광고 캠페인 성과 진단기 사용하기

입력한 데이터는 서버로 전송되지 않아요. 지금 열어둔 브라우저 안에서만 계산됩니다. 예제 데이터로 실행하면 다음과 같은 결과가 나옵니다.

목표 ROAS: 300%
목표 CPA: 20,000원

[목표 달성] 여름 세일
CTR: 2.40%
CVR: 2.83%
CPC: 250원
CPA: 8,824원
ROAS: 350.00%

[개선 필요] 신규 고객 리타겟팅
CTR: 2.25%
CVR: 1.48%
CPC: 333원
CPA: 22,500원
ROAS: 233.33%

[목표 달성] 브랜드 검색
CTR: 4.17%
CVR: 2.80%
CPC: 160원
CPA: 5,714원
ROAS: 425.00%

[전체 요약]
총 광고비: 600,000원
총 매출: 1,980,000원
통합 CPA: 9,524원
통합 ROAS: 330.00%
종합 판정: 목표 달성

계산만 하는 것은 아니에요. 클릭이 노출보다 많거나, 전환이 클릭보다 많거나, 같은 캠페인명이 두 번 들어가면 오류로 분리합니다.

오류가 있는 캠페인을 합계에서 제외한 뒤 정상 데이터만 다시 계산하고요. 사람이 매번 수식을 넣고 데이터가 맞는지 확인하던 순서를, 같은 기준으로 반복 실행 가능한 프로그램으로 바꿨습니다.

이 도구를 만든 전체 Python 코드를 공개합니다

이번 글에서는 코드를 한 줄씩 설명하지 않겠습니다. 아직 모든 문법을 이해하지 못해도 괜찮아요. 먼저 “파이썬으로 이런 도구를 만들 수 있구나”를 보는 것이 이번 글의 목적이니까요.

아래가 광고 캠페인 성과 진단기의 전체 코드입니다.

import json
import sys


REQUIRED_FIELDS = [
    "campaign", "spend", "impressions", "clicks", "conversions", "revenue"
]


def is_number(value):
    return isinstance(value, (int, float)) and not isinstance(value, bool)


def validate_campaign(campaign, seen_names):
    errors = []
    missing = [field for field in REQUIRED_FIELDS if campaign.get(field) is None]
    if missing:
        errors.append(f"필수 항목 누락: {', '.join(missing)}")

    name = campaign.get("campaign")
    if not isinstance(name, str) or not name.strip():
        errors.append("campaign은 비어 있지 않은 문자열이어야 합니다.")
    else:
        normalized_name = name.strip().lower()
        if normalized_name in seen_names:
            errors.append(f"중복 캠페인명: {name.strip()}")
        seen_names.add(normalized_name)

    for field in ["spend", "revenue"]:
        value = campaign.get(field)
        if not is_number(value) or value < 0:
            errors.append(f"{field}는 0 이상의 숫자여야 합니다.")

    for field in ["impressions", "clicks", "conversions"]:
        value = campaign.get(field)
        if not isinstance(value, int) or isinstance(value, bool) or value < 0:
            errors.append(f"{field}는 0 이상의 정수여야 합니다.")

    if is_number(campaign.get("clicks")) and is_number(campaign.get("impressions")):
        if campaign["clicks"] > campaign["impressions"]:
            errors.append("clicks는 impressions보다 클 수 없습니다.")
    if is_number(campaign.get("conversions")) and is_number(campaign.get("clicks")):
        if campaign["conversions"] > campaign["clicks"]:
            errors.append("conversions는 clicks보다 클 수 없습니다.")
    return errors


def divide_or_none(numerator, denominator, multiplier=1):
    if denominator == 0:
        return None
    return numerator / denominator * multiplier


def calculate_metrics(campaign):
    return {
        "ctr": divide_or_none(campaign["clicks"], campaign["impressions"], 100),
        "cvr": divide_or_none(campaign["conversions"], campaign["clicks"], 100),
        "cpc": divide_or_none(campaign["spend"], campaign["clicks"]),
        "cpa": divide_or_none(campaign["spend"], campaign["conversions"]),
        "roas": divide_or_none(campaign["revenue"], campaign["spend"], 100),
    }


def evaluate(metrics, goals):
    if metrics["roas"] is None or metrics["cpa"] is None:
        return "판정 보류"
    checks = [
        metrics["roas"] >= goals["target_roas"],
        metrics["cpa"] <= goals["target_cpa"],
    ]
    if all(checks):
        return "목표 달성"
    if any(checks):
        return "부분 달성"
    return "개선 필요"


def diagnose(data):
    goals = data.get("goals")
    campaigns = data.get("campaigns")
    if not isinstance(goals, dict) or not isinstance(campaigns, list):
        raise ValueError("goals와 campaigns 입력 형식을 확인해주세요.")
    if not is_number(goals.get("target_roas")) or goals["target_roas"] < 0:
        raise ValueError("target_roas는 0 이상의 숫자여야 합니다.")
    if not is_number(goals.get("target_cpa")) or goals["target_cpa"] <= 0:
        raise ValueError("target_cpa는 0보다 큰 숫자여야 합니다.")

    results = []
    valid_campaigns = []
    seen_names = set()
    for campaign in campaigns:
        errors = validate_campaign(campaign, seen_names)
        if errors:
            results.append({
                "campaign": campaign.get("campaign", "이름 없음"),
                "status": "입력 오류",
                "errors": errors,
            })
            continue
        metrics = calculate_metrics(campaign)
        results.append({
            "campaign": campaign["campaign"],
            "status": evaluate(metrics, goals),
            "metrics": {key: None if value is None else round(value, 2)
                        for key, value in metrics.items()},
        })
        valid_campaigns.append(campaign)

    summary = None
    if valid_campaigns:
        totals = {
            field: sum(campaign[field] for campaign in valid_campaigns)
            for field in ["spend", "impressions", "clicks", "conversions", "revenue"]
        }
        metrics = calculate_metrics(totals)
        summary = {
            "totals": totals,
            "metrics": {key: None if value is None else round(value, 2)
                        for key, value in metrics.items()},
            "status": evaluate(metrics, goals),
        }
    return {"goals": goals, "results": results, "summary": summary}


def main():
    if len(sys.argv) != 2:
        print("사용법: python campaign-diagnostic.py 입력파일.json")
        return 1
    try:
        with open(sys.argv[1], "r", encoding="utf-8") as file:
            report = diagnose(json.load(file))
        output_path = "campaign-diagnostic-result.json"
        with open(output_path, "w", encoding="utf-8") as file:
            json.dump(report, file, ensure_ascii=False, indent=2)
        print(json.dumps(report, ensure_ascii=False, indent=2))
        print(f"\n결과 저장 완료: {output_path}")
        return 0
    except (OSError, json.JSONDecodeError, ValueError) as error:
        print(f"오류: {error}")
        return 1


if __name__ == "__main__":
    sys.exit(main())

코드를 직접 실행해보고 싶다면 아래 파일을 내려받으면 됩니다.

두 파일을 같은 폴더에 둔 뒤 터미널에서 다음 명령을 실행하세요.

python3 campaign_diagnostic.py sample_campaigns.json

실행이 끝나면 터미널에 진단 결과가 표시되고, 같은 폴더에 sample_campaigns_report.json 파일이 생성됩니다.

다음 글부터 코드를 하나씩 열어보겠습니다

처음부터 이 코드를 전부 이해할 필요는 없어요. 이 프로그램도 특별한 마법으로 만들어진 것이 아닙니다.

데이터를 담고, 반복하고, 조건을 확인하고, 계산을 함수로 나누고, 잘못된 입력을 처리하는 작은 규칙들이 이어진 결과예요.

파이썬이 마케터에게 주는 가장 큰 변화는 숫자를 대신 계산하는 데서 끝나지 않습니다. 그보다는 내가 일하는 기준을 반복 가능한 도구로 바꿀 수 있다는 데 있습니다.

다음 글부터는 이 코드를 작은 단위로 나눠보겠습니다. 어떤 부분이 파이썬의 자료구조인지, 조건문은 무엇을 판단하는지, 실무 데이터의 오류를 어떻게 막는지 하나씩 확인해볼게요.

그전에 도구를 먼저 사용해보세요. 직접 넣어본 데이터에서 예상과 다른 결과가 나온다면, 그 지점이 다음 개선의 또다른 시작점이 되겠죠. 내 광고 캠페인 진단해보기

다음 글: 광고 캠페인 성과 진단기를 만드는 전체 흐름

관련 키워드

파이썬마케팅 자동화광고 성과 분석ROAS 계산CPA 계산퍼포먼스 마케팅

앤디의 마케팅노트

마케팅을 시스템으로 만드는 과정을 매주 기록해요. 잘 된 것도, 안 된 것도 같이 봐요.

maily에서 구독하기