Andrew Perpetua และ Facebook Ads

Andrew Perpetua — เรียนรู้เกี่ยวกับ Facebook Ads

Andrew Perpetua เป็นผู้เชี่ยวชาญ Facebook Ads เคยทำงานที่ Meta ดูแล Ads Products มีประสบการณ์มากกว่า 10 ปี Performance Marketing แชร์ความรู้ผ่าน Blog และ Social Media

แนวคิดหลักของ Andrew Perpetua คือใช้ Data-driven Approach ทดสอบ Creative อย่างเป็นระบบ ให้ Algorithm ทำงาน ไม่ Over-optimize ด้วยมือ

Campaign Structure และ Setup

# === Facebook Ads Campaign Structure ===
# ใช้ Facebook Marketing API (Python SDK)
# pip install facebook-business

# campaign_structure.py
from dataclasses import dataclass, field
from typing import List, Dict, Optional
from enum import Enum

class CampaignObjective(Enum):
    CONVERSIONS = "OUTCOME_SALES"
    LEADS = "OUTCOME_LEADS"
    TRAFFIC = "OUTCOME_TRAFFIC"
    AWARENESS = "OUTCOME_AWARENESS"
    ENGAGEMENT = "OUTCOME_ENGAGEMENT"

class BidStrategy(Enum):
    LOWEST_COST = "LOWEST_COST_WITHOUT_CAP"
    COST_CAP = "COST_CAP"
    BID_CAP = "BID_CAP"
    ROAS_TARGET = "MINIMUM_ROAS"

@dataclass
class AdCreative:
    name: str
    headline: str
    description: str
    cta: str  # SHOP_NOW, LEARN_MORE, SIGN_UP
    image_url: Optional[str] = None
    video_url: Optional[str] = None
    format: str = "single_image"  # single_image, video, carousel

@dataclass
class AdSetConfig:
    name: str
    targeting: Dict
    budget_daily: float
    optimization_goal: str  # CONVERSIONS, LANDING_PAGE_VIEWS, LINK_CLICKS
    bid_strategy: BidStrategy = BidStrategy.LOWEST_COST
    creatives: List[AdCreative] = field(default_factory=list)

@dataclass
class CampaignConfig:
    name: str
    objective: CampaignObjective
    budget_daily: float
    use_cbo: bool = True  # Campaign Budget Optimization
    ad_sets: List[AdSetConfig] = field(default_factory=list)

class FacebookAdsManager:
    """Facebook Ads Campaign Manager"""

    def __init__(self, account_id, access_token):
        self.account_id = account_id
        self.token = access_token

    def create_campaign(self, config: CampaignConfig):
        """สร้าง Campaign"""
        # Facebook Marketing API call
        # campaign = AdAccount(account_id).create_campaign(params={
        #     'name': config.name,
        #     'objective': config.objective.value,
        #     'special_ad_categories': [],
        #     'campaign_budget_optimization': config.use_cbo,
        #     'daily_budget': int(config.budget_daily * 100),
        #     'bid_strategy': 'LOWEST_COST_WITHOUT_CAP',
        # })
        print(f"Campaign: {config.name}")
        print(f"  Objective: {config.objective.value}")
        print(f"  Budget: /day")
        print(f"  CBO: {config.use_cbo}")
        return config

    def create_targeting(self, countries, age_min=18, age_max=65,
                        genders=None, interests=None, lookalike=None):
        """สร้าง Targeting"""
        targeting = {
            "geo_locations": {"countries": countries},
            "age_min": age_min,
            "age_max": age_max,
        }
        if genders:
            targeting["genders"] = genders
        if interests:
            targeting["flexible_spec"] = [{"interests": interests}]
        if lookalike:
            targeting["custom_audiences"] = [{"id": lookalike}]
        return targeting

# ตัวอย่าง Campaign Structure
manager = FacebookAdsManager("act_123456", "token")

# Broad Targeting Campaign
broad = CampaignConfig(
    name="[CBO] Broad - Conversions",
    objective=CampaignObjective.CONVERSIONS,
    budget_daily=50.0,
    use_cbo=True,
    ad_sets=[
        AdSetConfig(
            name="Broad 25-55 TH",
            targeting={"countries": ["TH"], "age_min": 25, "age_max": 55},
            budget_daily=0,
            optimization_goal="CONVERSIONS",
            creatives=[
                AdCreative("Image Ad 1", "สินค้าลดราคา 50%", "ช้อปเลย", "SHOP_NOW",
                          format="single_image"),
                AdCreative("Video Ad 1", "รีวิวสินค้ายอดนิยม", "ดูเลย", "SHOP_NOW",
                          format="video"),
            ],
        ),
    ],
)

manager.create_campaign(broad)
print(f"  Ad Sets: {len(broad.ad_sets)}")
for adset in broad.ad_sets:
    print(f"    {adset.name}: {len(adset.creatives)} creatives")

Performance Optimization

# ads_optimizer.py — Facebook Ads Performance Optimization
import random
from dataclasses import dataclass
from typing import List, Dict
from datetime import datetime, timedelta

@dataclass
class AdMetrics:
    ad_name: str
    impressions: int
    clicks: int
    conversions: int
    spend: float
    revenue: float

    @property
    def ctr(self):
        return self.clicks / self.impressions * 100 if self.impressions > 0 else 0

    @property
    def cpc(self):
        return self.spend / self.clicks if self.clicks > 0 else 0

    @property
    def cpa(self):
        return self.spend / self.conversions if self.conversions > 0 else 0

    @property
    def roas(self):
        return self.revenue / self.spend if self.spend > 0 else 0

class AdsOptimizer:
    """Facebook Ads Optimization Engine"""

    def __init__(self):
        self.ads: List[AdMetrics] = []
        self.rules: List[Dict] = []

    def add_ad(self, metrics: AdMetrics):
        self.ads.append(metrics)

    def add_rule(self, name, metric, operator, threshold, action):
        self.rules.append({
            "name": name, "metric": metric,
            "operator": operator, "threshold": threshold,
            "action": action,
        })

    def evaluate_rules(self):
        """ประเมิน Optimization Rules"""
        print(f"\n{'='*55}")
        print(f"Ads Optimization Report")
        print(f"{'='*55}")

        actions = []
        for ad in self.ads:
            for rule in self.rules:
                value = getattr(ad, rule["metric"], None)
                if value is None:
                    continue

                triggered = False
                if rule["operator"] == ">" and value > rule["threshold"]:
                    triggered = True
                elif rule["operator"] == "<" and value < rule["threshold"]:
                    triggered = True

                if triggered:
                    actions.append({
                        "ad": ad.ad_name,
                        "rule": rule["name"],
                        "value": value,
                        "action": rule["action"],
                    })

        if actions:
            print(f"\n  Actions ({len(actions)}):")
            for a in actions:
                print(f"    {a['ad']}: {a['rule']} "
                      f"(value={a['value']:.2f}) -> {a['action']}")
        else:
            print(f"\n  No actions needed")

    def performance_report(self):
        """Performance Report"""
        print(f"\n  Ad Performance:")
        total_spend = sum(a.spend for a in self.ads)
        total_revenue = sum(a.revenue for a in self.ads)

        for ad in sorted(self.ads, key=lambda a: a.roas, reverse=True):
            print(f"    {ad.ad_name:<25} "
                  f"ROAS:{ad.roas:.1f}x "
                  f"CPA: "
                  f"CTR:{ad.ctr:.1f}% "
                  f"Conv:{ad.conversions}")

        print(f"\n  Total: Spend= "
              f"Revenue= "
              f"ROAS={total_revenue/total_spend:.1f}x" if total_spend > 0 else "")

# ตัวอย่าง
optimizer = AdsOptimizer()

ads = [
    AdMetrics("Image Ad - Product A", 50000, 1200, 45, 300, 2700),
    AdMetrics("Video Ad - Review", 80000, 2400, 72, 500, 5400),
    AdMetrics("Carousel - Collection", 30000, 600, 15, 200, 900),
    AdMetrics("UGC Video - Testimonial", 60000, 1800, 90, 400, 6300),
    AdMetrics("Static - Discount 50%", 40000, 800, 8, 250, 400),
]

for ad in ads:
    optimizer.add_ad(ad)

# Optimization Rules
optimizer.add_rule("High CPA", "cpa", ">", 20, "Pause or reduce budget")
optimizer.add_rule("Low ROAS", "roas", "<", 2.0, "Review creative/targeting")
optimizer.add_rule("Low CTR", "ctr", "<", 1.0, "Refresh creative")

optimizer.performance_report()
optimizer.evaluate_rules()

Creative Testing Framework

# creative_testing.py — A/B Testing Framework สำหรับ Ad Creatives
from dataclasses import dataclass
from typing import List
import random
import math

@dataclass
class CreativeVariant:
    name: str
    format: str  # image, video, carousel
    hook: str
    impressions: int = 0
    conversions: int = 0
    spend: float = 0

    @property
    def cvr(self):
        return self.conversions / self.impressions if self.impressions > 0 else 0

class CreativeTestingFramework:
    """Creative Testing Framework ตามแนวคิด Andrew Perpetua"""

    def __init__(self):
        self.variants: List[CreativeVariant] = []
        self.min_impressions = 1000  # Minimum สำหรับ Statistical Significance

    def add_variant(self, variant: CreativeVariant):
        self.variants.append(variant)

    def is_significant(self, v1: CreativeVariant, v2: CreativeVariant):
        """ตรวจสอบ Statistical Significance"""
        if v1.impressions < self.min_impressions or v2.impressions < self.min_impressions:
            return False, "Need more data"

        p1 = v1.cvr
        p2 = v2.cvr
        n1 = v1.impressions
        n2 = v2.impressions

        if p1 == 0 and p2 == 0:
            return False, "No conversions"

        p_pool = (v1.conversions + v2.conversions) / (n1 + n2)
        if p_pool == 0 or p_pool == 1:
            return False, "Cannot calculate"

        se = math.sqrt(p_pool * (1 - p_pool) * (1/n1 + 1/n2))
        if se == 0:
            return False, "SE is zero"

        z = abs(p1 - p2) / se
        return z > 1.96, f"z={z:.2f} (need >1.96)"

    def testing_report(self):
        """Creative Testing Report"""
        print(f"\n{'='*55}")
        print(f"Creative Testing Report")
        print(f"{'='*55}")

        # Sort by CVR
        sorted_variants = sorted(self.variants, key=lambda v: v.cvr, reverse=True)

        winner = sorted_variants[0] if sorted_variants else None

        for v in sorted_variants:
            is_winner = " *WINNER*" if v == winner else ""
            print(f"  {v.name:<30} "
                  f"CVR:{v.cvr*100:.2f}% "
                  f"Conv:{v.conversions} "
                  f"Imp:{v.impressions:,}{is_winner}")

        # Statistical Significance
        if len(sorted_variants) >= 2:
            sig, msg = self.is_significant(sorted_variants[0], sorted_variants[1])
            print(f"\n  Significance: {'YES' if sig else 'NO'} ({msg})")

        # Recommendations
        print(f"\n  Testing Framework (Andrew Perpetua):")
        print(f"    1. ทดสอบ 3-5 Creatives ต่อ Ad Set")
        print(f"    2. รอ 1,000+ Impressions ก่อนตัดสินใจ")
        print(f"    3. ดู CVR ไม่ใช่แค่ CTR")
        print(f"    4. Winner takes all — Scale winner, kill losers")
        print(f"    5. ทดสอบ Creative ใหม่ทุก 2 สัปดาห์")

# ตัวอย่าง
framework = CreativeTestingFramework()

variants = [
    CreativeVariant("UGC Video - Unboxing", "video", "ลองเปิดกล่อง!", 15000, 120, 300),
    CreativeVariant("Studio Photo - Product", "image", "สินค้าขายดี", 12000, 72, 240),
    CreativeVariant("Carousel - Features", "carousel", "5 เหตุผลที่ต้องมี", 10000, 50, 200),
    CreativeVariant("Meme Style - Humor", "image", "เมื่อเพื่อนเห็นของ", 8000, 40, 160),
    CreativeVariant("Before/After", "image", "ก่อน vs หลัง", 11000, 88, 220),
]

for v in variants:
    framework.add_variant(v)

framework.testing_report()

เคล็ดลับจาก Andrew Perpetua

Andrew Perpetua — เรียนรู้เกี่ยวกับ Facebook Ads
  • Broad Targeting: ให้ Algorithm หา Audience เอง อย่า Narrow มากเกินไป
  • CBO: ใช้ Campaign Budget Optimization ให้ Facebook จัดสรร Budget อัตโนมัติ
  • Creative First: Creative สำคัญที่สุด ทดสอบหลาย Format (Video, Image, UGC)
  • ROAS Focus: วัดผลด้วย ROAS ไม่ใช่แค่ CTR หรือ CPC
  • Scale Gradually: เพิ่ม Budget 20% ต่อสัปดาห์ อย่าเพิ่มทีเดียวมาก
  • Advantage+: ใช้ Advantage+ Shopping Campaigns สำหรับ E-commerce

Andrew Perpetua คือใคร

ผู้เชี่ยวชาญ Facebook Ads Digital Marketing เคยทำงานที่ Meta Product Marketing Manager ดูแล Ads Products ประสบการณ์ 10+ ปี Performance Marketing แชร์ความรู้ Blog Social Media