387 lines
12 KiB
Python
387 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Facebook Ads CSV Bulk Upload Generator
|
|
Complete field mapping with validation for all campaign types
|
|
Author: Buba
|
|
Date: 2026-02-11
|
|
"""
|
|
|
|
import csv
|
|
import datetime
|
|
from typing import List, Dict, Optional
|
|
from enum import Enum
|
|
|
|
|
|
class CampaignObjective(Enum):
|
|
"""All supported Facebook campaign objectives"""
|
|
OUTCOME_TRAFFIC = "OUTCOME_TRAFFIC"
|
|
OUTCOME_SALES = "OUTCOME_SALES"
|
|
OUTCOME_LEADS = "OUTCOME_LEADS"
|
|
OUTCOME_ENGAGEMENT = "OUTCOME_ENGAGEMENT"
|
|
OUTCOME_APP_PROMOTION = "OUTCOME_APP_PROMOTION"
|
|
OUTCOME_AWARENESS = "OUTCOME_AWARENESS"
|
|
|
|
|
|
class OptimizationGoal(Enum):
|
|
"""Ad set optimization goals"""
|
|
LINK_CLICKS = "LINK_CLICKS"
|
|
LANDING_PAGE_VIEWS = "LANDING_PAGE_VIEWS"
|
|
IMPRESSIONS = "IMPRESSIONS"
|
|
REACH = "REACH"
|
|
OFFSITE_CONVERSIONS = "OFFSITE_CONVERSIONS"
|
|
THRUPLAY = "THRUPLAY"
|
|
POST_ENGAGEMENT = "POST_ENGAGEMENT"
|
|
|
|
|
|
class BillingEvent(Enum):
|
|
"""Billing event types"""
|
|
IMPRESSIONS = "IMPRESSIONS"
|
|
LINK_CLICKS = "LINK_CLICKS"
|
|
THRUPLAY = "THRUPLAY"
|
|
|
|
|
|
class CallToAction(Enum):
|
|
"""Call to action button types"""
|
|
LEARN_MORE = "LEARN_MORE"
|
|
SHOP_NOW = "SHOP_NOW"
|
|
SIGN_UP = "SIGN_UP"
|
|
DOWNLOAD = "DOWNLOAD"
|
|
GET_QUOTE = "GET_QUOTE"
|
|
CONTACT_US = "CONTACT_US"
|
|
BOOK_NOW = "BOOK_NOW"
|
|
APPLY_NOW = "APPLY_NOW"
|
|
NO_BUTTON = "NO_BUTTON"
|
|
|
|
|
|
class FBAdsCSVGenerator:
|
|
"""
|
|
Complete Facebook Ads CSV generator with all API fields
|
|
"""
|
|
|
|
# Complete field mapping
|
|
CAMPAIGN_FIELDS = [
|
|
"Campaign Name",
|
|
"Campaign Objective",
|
|
"Buying Type",
|
|
"Campaign Status",
|
|
"Campaign Budget Optimization",
|
|
"Campaign Budget",
|
|
"Campaign Bid Strategy",
|
|
"Campaign Spend Limit",
|
|
]
|
|
|
|
AD_SET_FIELDS = [
|
|
"Ad Set Name",
|
|
"Optimization Goal",
|
|
"Billing Event",
|
|
"Bid Strategy",
|
|
"Bid Amount",
|
|
"Daily Budget",
|
|
"Lifetime Budget",
|
|
"Start Date",
|
|
"End Date",
|
|
"Ad Set Status",
|
|
"Targeting Age Min",
|
|
"Targeting Age Max",
|
|
"Targeting Gender",
|
|
"Targeting Locations",
|
|
"Targeting Custom Audiences",
|
|
"Targeting Excluded Custom Audiences",
|
|
"Targeting Interests",
|
|
"Targeting Behaviors",
|
|
"Targeting Languages",
|
|
"Placements",
|
|
"Platform Positions",
|
|
"Optimization Window",
|
|
"Attribution Setting",
|
|
"Conversion Window",
|
|
"Promoted Object Pixel ID",
|
|
"Promoted Object Custom Event Type",
|
|
"Destination Type",
|
|
]
|
|
|
|
AD_FIELDS = [
|
|
"Ad Name",
|
|
"Ad Status",
|
|
"Body", # Primary text
|
|
"Title", # Headline
|
|
"Caption", # Link description
|
|
"Link", # Destination URL
|
|
"Call To Action",
|
|
"Image Hash", # For existing images
|
|
"Image File", # For new uploads
|
|
"Video ID", # For existing videos
|
|
"Video File", # For new uploads
|
|
"Website URL", # Can be different from Link
|
|
"Display Link", # vanity URL shown
|
|
"UTM Source",
|
|
"UTM Medium",
|
|
"UTM Campaign",
|
|
"UTM Term",
|
|
"UTM Content",
|
|
"Dynamic Creative",
|
|
"Additional Body 1",
|
|
"Additional Body 2",
|
|
"Additional Body 3",
|
|
"Additional Title 1",
|
|
"Additional Title 2",
|
|
"Additional Title 3",
|
|
"Additional Image 1",
|
|
"Additional Image 2",
|
|
"Use Page Post",
|
|
"Page Post ID",
|
|
"Instagram Actor ID",
|
|
"Facebook Page ID",
|
|
]
|
|
|
|
# Carousel specific
|
|
CAROUSEL_FIELDS = [
|
|
"Carousel Card 1 Title",
|
|
"Carousel Card 1 Body",
|
|
"Carousel Card 1 Link",
|
|
"Carousel Card 1 Image",
|
|
"Carousel Card 2 Title",
|
|
"Carousel Card 2 Body",
|
|
"Carousel Card 2 Link",
|
|
"Carousel Card 2 Image",
|
|
# ... up to 10 cards
|
|
]
|
|
|
|
# Multi-language DLO
|
|
MULTI_LANGUAGE_FIELDS = [
|
|
"Language 1 Code",
|
|
"Language 1 Body",
|
|
"Language 1 Title",
|
|
"Language 1 Link",
|
|
"Language 2 Code",
|
|
"Language 2 Body",
|
|
"Language 2 Title",
|
|
"Language 2 Link",
|
|
]
|
|
|
|
# Partnership ads
|
|
PARTNERSHIP_FIELDS = [
|
|
"Partnership Ad Code",
|
|
"Creator Account ID",
|
|
]
|
|
|
|
ALL_FIELDS = (
|
|
CAMPAIGN_FIELDS +
|
|
AD_SET_FIELDS +
|
|
AD_FIELDS +
|
|
CAROUSEL_FIELDS[:8] + # Include first 2 carousel cards
|
|
MULTI_LANGUAGE_FIELDS[:8] + # Include 2 languages
|
|
PARTNERSHIP_FIELDS
|
|
)
|
|
|
|
@staticmethod
|
|
def validate_date(date_str: str) -> bool:
|
|
"""Validate date format (YYYY-MM-DD)"""
|
|
try:
|
|
datetime.datetime.strptime(date_str, "%Y-%m-%d")
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
@staticmethod
|
|
def validate_budget(budget: str) -> bool:
|
|
"""Validate budget is a positive number"""
|
|
try:
|
|
return float(budget) > 0
|
|
except ValueError:
|
|
return False
|
|
|
|
@staticmethod
|
|
def validate_age(age: str) -> bool:
|
|
"""Validate age is between 13-65+"""
|
|
try:
|
|
age_int = int(age)
|
|
return 13 <= age_int <= 65
|
|
except ValueError:
|
|
return age == "65+"
|
|
|
|
def generate_traffic_campaign(
|
|
self,
|
|
campaign_name: str,
|
|
ad_set_name: str,
|
|
ad_name: str,
|
|
daily_budget: float,
|
|
target_url: str,
|
|
ad_copy: Dict[str, str],
|
|
image_file: str = None,
|
|
image_hash: str = None
|
|
) -> List[Dict]:
|
|
"""
|
|
Generate a complete traffic campaign CSV row
|
|
|
|
Args:
|
|
campaign_name: Name of the campaign
|
|
ad_set_name: Name of the ad set
|
|
ad_name: Name of the ad
|
|
daily_budget: Daily budget in dollars
|
|
target_url: Landing page URL
|
|
ad_copy: Dict with 'body', 'title', 'caption'
|
|
image_file: Filename for new image upload
|
|
image_hash: Hash for existing image in library
|
|
|
|
Returns:
|
|
List of row dictionaries ready for CSV
|
|
"""
|
|
|
|
row = {
|
|
# Campaign Level
|
|
"Campaign Name": campaign_name,
|
|
"Campaign Objective": CampaignObjective.OUTCOME_TRAFFIC.value,
|
|
"Buying Type": "AUCTION",
|
|
"Campaign Status": "ACTIVE",
|
|
"Campaign Budget Optimization": "FALSE",
|
|
"Campaign Budget": "",
|
|
"Campaign Bid Strategy": "LOWEST_COST_WITHOUT_CAP",
|
|
"Campaign Spend Limit": "",
|
|
|
|
# Ad Set Level
|
|
"Ad Set Name": ad_set_name,
|
|
"Optimization Goal": OptimizationGoal.LINK_CLICKS.value,
|
|
"Billing Event": BillingEvent.LINK_CLICKS.value,
|
|
"Bid Strategy": "LOWEST_COST_WITHOUT_CAP",
|
|
"Bid Amount": "",
|
|
"Daily Budget": str(daily_budget),
|
|
"Lifetime Budget": "",
|
|
"Start Date": datetime.datetime.now().strftime("%Y-%m-%d"),
|
|
"End Date": "",
|
|
"Ad Set Status": "ACTIVE",
|
|
"Targeting Age Min": "18",
|
|
"Targeting Age Max": "65+",
|
|
"Targeting Gender": "All",
|
|
"Targeting Locations": "United States",
|
|
"Targeting Custom Audiences": "",
|
|
"Targeting Excluded Custom Audiences": "",
|
|
"Targeting Interests": "",
|
|
"Targeting Behaviors": "",
|
|
"Targeting Languages": "en",
|
|
"Placements": "Automatic",
|
|
"Platform Positions": "Facebook,Instagram",
|
|
"Optimization Window": "1_DAY",
|
|
"Attribution Setting": "7_DAY_CLICK_1_DAY_VIEW",
|
|
"Conversion Window": "",
|
|
"Promoted Object Pixel ID": "",
|
|
"Promoted Object Custom Event Type": "",
|
|
"Destination Type": "WEBSITE",
|
|
|
|
# Ad Level
|
|
"Ad Name": ad_name,
|
|
"Ad Status": "ACTIVE",
|
|
"Body": ad_copy.get('body', ''),
|
|
"Title": ad_copy.get('title', ''),
|
|
"Caption": ad_copy.get('caption', ''),
|
|
"Link": target_url,
|
|
"Call To Action": CallToAction.LEARN_MORE.value,
|
|
"Image Hash": image_hash or "",
|
|
"Image File": image_file or "",
|
|
"Video ID": "",
|
|
"Video File": "",
|
|
"Website URL": target_url,
|
|
"Display Link": "",
|
|
"UTM Source": "facebook",
|
|
"UTM Medium": "paid_social",
|
|
"UTM Campaign": campaign_name.lower().replace(" ", "_"),
|
|
"UTM Term": "",
|
|
"UTM Content": ad_name.lower().replace(" ", "_"),
|
|
"Dynamic Creative": "FALSE",
|
|
"Additional Body 1": "",
|
|
"Additional Body 2": "",
|
|
"Additional Body 3": "",
|
|
"Additional Title 1": "",
|
|
"Additional Title 2": "",
|
|
"Additional Title 3": "",
|
|
"Additional Image 1": "",
|
|
"Additional Image 2": "",
|
|
"Use Page Post": "FALSE",
|
|
"Page Post ID": "",
|
|
"Instagram Actor ID": "",
|
|
"Facebook Page ID": "",
|
|
|
|
# Carousel (empty for single image)
|
|
"Carousel Card 1 Title": "",
|
|
"Carousel Card 1 Body": "",
|
|
"Carousel Card 1 Link": "",
|
|
"Carousel Card 1 Image": "",
|
|
"Carousel Card 2 Title": "",
|
|
"Carousel Card 2 Body": "",
|
|
"Carousel Card 2 Link": "",
|
|
"Carousel Card 2 Image": "",
|
|
|
|
# Multi-language (empty for single language)
|
|
"Language 1 Code": "",
|
|
"Language 1 Body": "",
|
|
"Language 1 Title": "",
|
|
"Language 1 Link": "",
|
|
"Language 2 Code": "",
|
|
"Language 2 Body": "",
|
|
"Language 2 Title": "",
|
|
"Language 2 Link": "",
|
|
|
|
# Partnership (empty)
|
|
"Partnership Ad Code": "",
|
|
"Creator Account ID": "",
|
|
}
|
|
|
|
return [row]
|
|
|
|
def write_csv(self, rows: List[Dict], filename: str):
|
|
"""Write rows to CSV file"""
|
|
with open(filename, 'w', newline='', encoding='utf-8') as f:
|
|
writer = csv.DictWriter(f, fieldnames=self.ALL_FIELDS)
|
|
writer.writeheader()
|
|
writer.writerows(rows)
|
|
print(f"✅ CSV generated: {filename}")
|
|
print(f" Rows: {len(rows)}")
|
|
print(f" Fields: {len(self.ALL_FIELDS)}")
|
|
|
|
|
|
# Example usage
|
|
if __name__ == "__main__":
|
|
generator = FBAdsCSVGenerator()
|
|
|
|
# Generate a sample campaign for Jake's brand
|
|
campaign_rows = generator.generate_traffic_campaign(
|
|
campaign_name="OpenClaw Launch Campaign",
|
|
ad_set_name="Cold Traffic - Tech Enthusiasts",
|
|
ad_name="OpenClaw Hero Ad v1",
|
|
daily_budget=50.00,
|
|
target_url="https://openclaw.com/?utm_source=facebook&utm_medium=paid_social",
|
|
ad_copy={
|
|
'body': "Meet OpenClaw: The AI assistant that actually gets stuff done. No prompts, no hand-holding. Just tell it what you want and watch it work. Join 10,000+ users automating their workflows.",
|
|
'title': "OpenClaw - AI That Actually Works",
|
|
'caption': "Start Your Free Trial Today"
|
|
},
|
|
image_file="openclaw_hero_1080x1080.jpg"
|
|
)
|
|
|
|
# Write to CSV
|
|
output_file = "facebook_ads_bulk_upload.csv"
|
|
generator.write_csv(campaign_rows, output_file)
|
|
|
|
print("\n📋 Next Steps:")
|
|
print("1. Upload your creative (openclaw_hero_1080x1080.jpg) to your computer")
|
|
print("2. Go to Meta Ads Manager")
|
|
print("3. Click the ⋮ menu > Import & Export > Import Ads")
|
|
print(f"4. Upload {output_file}")
|
|
print("5. Upload your creative file when prompted")
|
|
print("6. Review and publish!")
|
|
|
|
print("\n🎯 Field Validation Rules:")
|
|
print("- Campaign Status: ACTIVE or PAUSED")
|
|
print("- Dates: YYYY-MM-DD format")
|
|
print("- Budget: Positive numbers only (no $ symbol)")
|
|
print("- Age: 13-65 or '65+'")
|
|
print("- Gender: All, Male, or Female")
|
|
print("- Image Hash: Get from existing library or leave blank for new upload")
|
|
print("- UTM parameters: Auto-generated for tracking")
|
|
|
|
print("\n💡 Pro Tips:")
|
|
print("- For multiple ads: duplicate the row and change Ad Name + creative")
|
|
print("- For carousel: fill Carousel Card 1-10 fields")
|
|
print("- For DLO: fill Language 1-5 Code/Body/Title/Link fields")
|
|
print("- For dynamic creative: set Dynamic Creative=TRUE and fill Additional fields")
|