Google Ads API: การตั้งค่า การยืนยันตัวตน การอ่านและการเขียนข้อมูล
คู่มือทางเทคนิคสำหรับการตั้งค่าการเข้าถึง Google Ads แบบโปรแกรม — ตั้งแต่สถาปัตยกรรมไปจนถึงการเรียก API ครั้งแรก ครอบคลุมการกำหนดค่า MCC, OAuth 2.0, ระดับ Developer Token, ไลบรารี google-ads สำหรับ Python, การอ่านข้อมูลด้วย GAQL และรูปแบบการเขียนแบบ validate_only
สถาปัตยกรรม
จำเป็นต้องมีองค์ประกอบสามอย่างแยกกันก่อนที่การเรียก API ครั้งเดียวจะทำงานได้:
| องค์ประกอบ | วัตถุประสงค์ | ตัวอย่าง |
|---|---|---|
| **Manager Account (MCC)** | ออก Developer Token | `login_customer_id` |
| **Google Cloud Project** | OAuth 2.0 client สำหรับการยืนยันตัวตน | Client ID + Secret |
| **Google Ads Account** | เป้าหมายของการดำเนินการ API ทั้งหมด | `customer_id` |
องค์ประกอบเหล่านี้ไม่ใช่บัญชีเดียวกัน MCC สามารถเป็นเจ้าของ Developer Token ได้โดยไม่ต้องเป็นเจ้าของบัญชีเป้าหมาย — เพียงแค่ต้องเชื่อมโยงในฐานะผู้จัดการ Google Cloud Project สามารถอยู่ภายใต้ตัวตน Google ที่ต่างจาก MCC บัญชีเป้าหมายคือบัญชีผู้โฆษณาที่แคมเปญอาศัยอยู่
การแยกส่วนนี้มีความสำคัญเนื่องจาก API Center (ที่ซึ่ง Developer Token ถูกออกให้) มีให้ใช้งานเฉพาะภายใน Manager Accounts เท่านั้น บัญชี Google Ads แบบเดี่ยวไม่สามารถเข้าถึง API Center ได้ แม้ว่าจะมีผู้ใช้ระดับผู้ดูแลระบบก็ตาม
ส่วนหัวของคำขอ API จะประกอบรวมตัวตนทั้งสามนี้พร้อมกับ OAuth Bearer token:
developer-token: <from MCC API Center>
login-customer-id: <MCC numeric ID, no dashes>
customer-id: <target account numeric ID>
Authorization: Bearer *** 2.0 access token>
ข้อกำหนดเบื้องต้น
- Google Ads Manager Account ที่ตั้งค่าเสร็จสมบูรณ์แล้ว
- บัญชีโฆษณาเป้าหมายที่เชื่อมโยงกับ Manager Account และยอมรับการเชื่อมโยงแล้ว
- Google Cloud Project ที่เปิดใช้งาน Google Ads API แล้ว
- Python 3.9+ พร้อมไลบรารี
google-ads(pip install google-ads) - เบราว์เซอร์สำหรับขั้นตอน OAuth consent แบบครั้งเดียว
ขั้นตอนที่ 1: Manager Account และ Developer Token
API Center อยู่ภายใต้ Tools & Settings → Setup → API Center ภายใน Manager Account หากไม่พบ API Center แสดงว่าบัญชีนั้นไม่ใช่ Manager Account หรือการตั้งค่ายังไม่สมบูรณ์
จาก API Center ให้ขอ Developer Token ตั้งค่าประเภทบัญชีเป็น Advertiser (ไม่ใช่ Agency หรือ Third-Party) เมื่อ token นั้นมีไว้สำหรับบัญชีของคุณเอง Token จะถูกออกให้ทันทีในระดับ Test Access
Test Access อนุญาตให้:
ข้อผิดพลาดสำหรับการเขียนกับบัญชีจริงด้วย Test Access คือ RESOURCE_NOT_FOUND — ไม่ใช่ PERMISSION_DENIED ซึ่งอาจทำให้สับสนเนื่องจากทรัพยากรนั้นมีอยู่จริงและสามารถอ่านได้ ข้อผิดพลาดนี้หมายความว่า API ปฏิเสธที่จะดำเนินการ mutation ภายใต้ระดับการเข้าถึงปัจจุบัน
การสมัคร Basic Access
หากต้องการเขียนกับบัญชีจริง ให้สมัคร Basic Access จากหน้า API Center เดียวกัน แบบฟอร์มการสมัครประกอบด้วยคำถาม 12 ข้อ สำหรับการใช้งานภายในกับบัญชีของคุณเอง คำตอบที่เกี่ยวข้องคือ:
| คำถาม | คำตอบ |
|---|---|
| Q4: ความสัมพันธ์กับ Google Representative | No |
| Q6: Business Model | อธิบายกรณีการใช้งานของคุณเอง (e-commerce, lead gen, internal reporting) |
| Q8: ใครจะมีสิทธิ์เข้าถึง | Internal users — เฉพาะพนักงานเท่านั้น |
| Q9: เครื่องมือที่พัฒนาโดยบุคคลที่สาม | No (สคริปต์/เอเจนต์ของตัวเอง) |
| Q10: Conversion Tracking & Remarketing API | No (เว้นแต่คุณจะใช้งานจริง) |
| Q11: ประเภทแคมเปญ | Search, Performance Max, Shopping (ตามที่เกี่ยวข้อง) |
| Q12: ความสามารถ | Campaign Creation, Campaign Management, Reporting |
จำเป็นต้องมีเอกสารออกแบบ (PDF) สำหรับ Q7 ซึ่งควรอธิบายสถาปัตยกรรม API, ขั้นตอนการยืนยันตัวตน, กลยุทธ์การจำกัดอัตราการเรียก (rate-limiting), การจัดการข้อผิดพลาด และกรณีการใช้งานภายในเท่านั้น ให้เขียนตามข้อเท็จจริง — ทีมตรวจสอบ compliance จะประเมินความสอดคล้องกับกรณีการใช้งานที่แจ้งไว้
ระยะเวลาดำเนินการ ณ เดือนมิถุนายน 2026: ประมาณ 3 วันทำการ พร้อมหมายเหตุเกี่ยวกับปริมาณการสมัครที่สูงกว่าปกติ
ขั้นตอนที่ 2: OAuth 2.0
การตั้งค่า Google Cloud Project
ใน Google Cloud Console ภายใต้โปรเจกต์ที่เกี่ยวข้อง:
- เปิดใช้งาน Google Ads API (APIs & Services → Library)
- กำหนดค่า OAuth consent screen:
https://www.googleapis.com/auth/adwordsประเภท Desktop app ใช้ http://localhost เป็น redirect URI ซึ่งถูกต้อง — ขั้นตอน OAuth จะเปิดเบราว์เซอร์, Google จะเปลี่ยนเส้นทางไปยัง localhost พร้อม authorization code และ HTTP server ในเครื่องจะดักจับค่านั้น
การสร้าง Refresh Token
สคริปต์ Python ที่รัน HTTP server ในเครื่องบน 127.0.0.1:0 (พอร์ตสุ่ม), แสดง authorization URL, รอคำขอหนึ่งรายการ, ดึงพารามิเตอร์ code และแลกเปลี่ยนเป็น tokens:
from pathlib import Path
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
from google_auth_oauthlib.flow import Flow
class CallbackHandler(BaseHTTPRequestHandler):
code_value = None
def do_GET(self):
qs = parse_qs(urlparse(self.path).query)
CallbackHandler.code_value = (qs.get('code') or [None])[0]
self.send_response(200)
self.end_headers()
self.wfile.write(b'OAuth complete.')
def log_message(self, format, *args):
return
server = HTTPServer(('127.0.0.1', 0), CallbackHandler)
redirect_uri = f'http://127.0.0.1:{server.server_port}/'
flow = Flow.from_client_secrets_file(
'client_secret.json',
scopes=['https://www.googleapis.com/auth/adwords'],
redirect_uri=redirect_uri
)
auth_url, _ = flow.authorization_url(
access_type='offline',
prompt='consent'
)
print(auth_url)
server.handle_request()
flow.fetch_token(code=CallbackHandler.code_value)
Path('google_ads_token.json').write_text(flow.credentials.to_json())
พารามิเตอร์สองตัวที่มีความสำคัญ:
access_type='offline' — ส่งคืน refresh token ไม่ใช่แค่ access tokenprompt='consent' — บังคับให้แสดงหน้าจอ consent ใหม่แม้ว่าผู้ใช้จะเคยอนุญาตแล้วก็ตาม จำเป็นเพื่อรับ refresh token ใหม่หากมีการเปลี่ยนแปลง scopesrefresh token จะคงอยู่ถาวรเว้นแต่จะถูกเพิกถอน, ผู้ใช้เปลี่ยนรหัสผ่าน, หรือ token ไม่ถูกใช้งานเป็นเวลา 6 เดือน
ปัญหา Scope กับ Clients ที่มีอยู่เดิม
หาก OAuth client เคยได้รับอนุญาตสำหรับ scopes อื่นมาก่อน (Gmail, Drive, Calendar) Google จะส่งคืนผลรวมของ scopes เดิมและใหม่ ไลบรารี google-auth-oauthlib จะปฏิเสธความไม่ตรงกันของ scope นี้โดยค่าเริ่มต้น ให้ตั้งค่าตัวแปรสภาพแวดล้อมก่อนโหลด flow:
import os
os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1'
อีกทางเลือกหนึ่งคือละเว้น include_granted_scopes=true จาก authorization URL ซึ่งจะบอกให้ Google ขอเฉพาะ scope ที่ระบุอย่างชัดเจนเท่านั้น
ขั้นตอนที่ 3: `google-ads.yaml`
ไลบรารีอ่านการกำหนดค่าจากไฟล์ YAML:
developer_token: <value>
client_id: <value>
client_secret: <value>
refresh_token: <value>
login_customer_id: 1911599764
use_proto_plus: true
กฎความปลอดภัย:
0600 (เจ้าของอ่าน/เขียนเท่านั้น)login_customer_id คือ MCC ID ที่เป็นตัวเลขโดยไม่มีขีดคั่น มันบอก API ว่าจะใช้ Developer Token ของบัญชีผู้จัดการใด customer_id เป้าหมายจะถูกส่งตอนเรียกใช้ API ไม่ได้อยู่ในไฟล์กำหนดค่า
use_proto_plus: true เปิดใช้งานอินเทอร์เฟซ protobuf-plus ซึ่งจำเป็นสำหรับ API เวอร์ชันปัจจุบัน
ขั้นตอนที่ 4: การเริ่มต้น Python Client
from google.ads.googleads.client import GoogleAdsClient
client = GoogleAdsClient.load_from_storage(
'/path/to/google-ads.yaml',
version='v22'
)
gs = client.get_service('GoogleAdsService')
พารามิเตอร์ version เลือกเวอร์ชัน API อย่างชัดเจน ไลบรารี google-ads มาพร้อมกับหลายเวอร์ชันที่ติดตั้งแบบขนาน (v19 ถึง v22 ณ เดือนมิถุนายน 2026) ควรระบุเวอร์ชันเสมอ — ค่าเริ่มต้นอาจล้าหลังหรือไม่รองรับฟิลด์ใหม่กว่า
ตรวจสอบการเชื่อมต่อ:
cs = client.get_service('CustomerService')
customers = cs.list_accessible_customers()
ids = [r.split('/')[-1] for r in customers.resource_names]
# Example output: ['1305475941', '3977581086', '1911599764']
ขั้นตอนที่ 5: การดำเนินการอ่าน (GAQL)
Google Ads ใช้ GAQL (Google Ads Query Language) ซึ่งเป็นไวยากรณ์คล้าย SQL การดำเนินการอ่านทั้งหมดผ่าน GoogleAdsService.search_stream() ซึ่งหลีกเลี่ยงการแบ่งหน้า (pagination) และเป็นที่แนะนำมากกว่า search() สำหรับคำถามส่วนใหญ่
แคมเปญพร้อมเมตริกประสิทธิภาพ
SELECT
campaign.id,
campaign.name,
campaign.status,
campaign.advertising_channel_type,
campaign.serving_status,
campaign.bidding_strategy_type,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions,
metrics.conversions_value,
metrics.ctr,
metrics.average_cpc
FROM campaign
ORDER BY campaign.id
งบประมาณแคมเปญ
SELECT
campaign_budget.id,
campaign_budget.name,
campaign_budget.amount_micros,
campaign_budget.delivery_method,
campaign_budget.status,
campaign.id,
campaign.name
FROM campaign_budget
กลุ่มโฆษณา
SELECT
ad_group.id,
ad_group.name,
ad_group.status,
ad_group.cpc_bid_micros,
campaign.id,
campaign.name
FROM ad_group
ORDER BY campaign.id, ad_group.id
คีย์เวิร์ด (เรียงตาม Impressions)
SELECT
ad_group_criterion.keyword.text,
ad_group_criterion.keyword.match_type,
ad_group_criterion.status,
ad_group_criterion.criterion_id,
ad_group.id,
ad_group.name,
campaign.id,
campaign.name,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions
FROM keyword_view
WHERE ad_group_criterion.type = 'KEYWORD'
ORDER BY metrics.impressions DESC
LIMIT 100
โฆษณาพร้อมสถานะนโยบาย
SELECT
ad_group_ad.ad.id,
ad_group_ad.ad.name,
ad_group_ad.ad.type,
ad_group_ad.status,
ad_group_ad.policy_summary.approval_status,
ad_group.id,
ad_group.name,
campaign.id,
campaign.name
FROM ad_group_ad
Conversion Actions
SELECT
conversion_action.id,
conversion_action.name,
conversion_action.status,
conversion_action.type,
conversion_action.category,
conversion_action.include_in_conversions_metric,
conversion_action.counting_type
FROM conversion_action
รูปแบบการเรียกใช้ใน Python
def gaql_query(client, customer_id, query):
gs = client.get_service('GoogleAdsService')
results = []
for batch in gs.search_stream(customer_id=customer_id, query=query):
for row in batch.results:
results.append(row)
return results
ขั้นตอนที่ 6: การดำเนินการเขียนด้วย `validate_only`
API v22 ต้องการ request objects ไม่ใช่ keyword arguments ทุก mutation service (CampaignService, AdGroupService, AdGroupCriterionService, CampaignBudgetService) มีรูปแบบเดียวกัน:
from google.protobuf import field_mask_pb2
req = client.get_type('MutateCampaignsRequest')
req.customer_id = '1305475941'
req.validate_only = True
op = client.get_type('CampaignOperation')
op.create.name = 'Campaign Name'
op.create.status = client.enums.CampaignStatusEnum.PAUSED
op.create.advertising_channel_type = client.enums.AdvertisingChannelTypeEnum.SEARCH
op.create.campaign_budget = 'customers/1305475941/campaignBudgets/123456789'
op.create.manual_cpc.enhanced_cpc_enabled = False
req.operations.append(op) # .append(), not .add()
client.get_service('CampaignService').mutate_campaigns(request=req)
ความแตกต่างสำคัญจาก API เวอร์ชันเก่า:
validate_only เป็นฟิลด์บน request object ไม่ใช่ keyword argument ของเมธอด mutatereq.operations.append(op) ไม่ใช่ req.operations.add(op)'customers/1305475941/campaigns/22479990461') ไม่ใช่ค่าที่ส่งคืนจาก helper methodกระบวนการ Mutation สามขั้นตอน
เพื่อความปลอดภัยในการใช้งานจริง (production) การทำ mutation จะผ่านสามขั้นตอน:
ขั้นตอนที่ 1 — validate_only=True: API ตรวจสอบโครงสร้างคำขอ, ฟิลด์ที่จำเป็น และการอ้างอิงทรัพยากร ยังไม่มีการสร้างออบเจ็กต์ ควรทำขั้นตอนนี้ก่อนทุก mutation
ขั้นตอนที่ 2 — สร้างแบบหยุดชั่วคราว (Paused Creation): ตั้งค่า validate_only=False และสร้างออบเจ็กต์ด้วย status=PAUSED ตรวจสอบผลลัพธ์ด้วยคำถามอ่านก่อนดำเนินการต่อ
ขั้นตอนที่ 3 — เปิดใช้งานจริง (Live Enable): การเรียก API แยกต่างหากที่อัปเดตเฉพาะฟิลด์ status เป็น ENABLED การดำเนินการนี้ควรต้องได้รับการอนุมัติอย่างชัดเจน ไม่ควรรวมอยู่ในการสร้าง
# Gate 3: enable a previously created paused campaign
req = client.get_type('MutateCampaignsRequest')
req.customer_id = '1305475941'
req.validate_only = False
op = client.get_type('CampaignOperation')
op.update.resource_name = 'customers/1305475941/campaigns/22479990461'
op.update.status = client.enums.CampaignStatusEnum.ENABLED
op.update_mask.CopyFrom(field_mask_pb2.FieldMask(paths=['status']))
req.operations.append(op)
client.get_service('CampaignService').mutate_campaigns(request=req)
การอัปเดตงบประมาณ
req = client.get_type('MutateCampaignBudgetsRequest')
req.customer_id = '1305475941'
req.validate_only = True
op = client.get_type('CampaignBudgetOperation')
op.update.resource_name = 'customers/1305475941/campaignBudgets/123456789'
op.update.amount_micros = 1_000_000 # $1.00 per day
op.update_mask.CopyFrom(field_mask_pb2.FieldMask(paths=['amount_micros']))
req.operations.append(op)
client.get_service('CampaignBudgetService').mutate_campaign_budgets(request=req)
การอัปเดตราคาเสนอของกลุ่มโฆษณา
req = client.get_type('MutateAdGroupsRequest')
req.customer_id = '1305475941'
req.validate_only = True
op = client.get_type('AdGroupOperation')
op.update.resource_name = 'customers/1305475941/adGroups/165827073530'
op.update.cpc_bid_micros = 250_000 # $0.25
op.update_mask.CopyFrom(field_mask_pb2.FieldMask(paths=['cpc_bid_micros']))
req.operations.append(op)
client.get_service('AdGroupService').mutate_ad_groups(request=req)
การสร้างคีย์เวิร์ด
req = client.get_type('MutateAdGroupCriteriaRequest')
req.customer_id = '1305475941'
req.validate_only = True
op = client.get_type('AdGroupCriterionOperation')
op.create.ad_group = 'customers/1305475941/adGroups/165827073530'
op.create.status = client.enums.AdGroupCriterionStatusEnum.PAUSED
op.create.keyword.text = 'example keyword'
op.create.keyword.match_type = client.enums.KeywordMatchTypeEnum.EXACT
req.operations.append(op)
client.get_service('AdGroupCriterionService').mutate_ad_group_criteria(request=req)
การจัดการข้อผิดพลาด
from google.ads.googleads.errors import GoogleAdsException
try:
response = service.mutate_campaigns(request=req)
except GoogleAdsException as ex:
for error in ex.failure.errors:
print(f'{error.error_code}: {error.message}')
รหัสข้อผิดพลาดที่พบบ่อยและความหมาย:
| Error Code | สาเหตุ |
|---|---|
| `RESOURCE_NOT_FOUND` | Test Access token กับบัญชีจริง (เป็นไปตามที่คาด) หรือทรัพยากรไม่มีอยู่จริง |
| `REQUIRED` | ขาดฟิลด์บังคับ (เช่น bidding strategy ในการสร้างแคมเปญ) |
| `UNRECOGNIZED_FIELD` | ฟิลด์จาก API เวอร์ชันอื่นหรือชื่อฟิลด์ไม่ถูกต้อง |
| `INVALID_ARGUMENT` | ค่าฟิลด์ไม่ผ่านการตรวจสอบ (เช่น งบประมาณติดลบ) |
| `PERMISSION_DENIED` | ผู้ใช้ OAuth ไม่มีสิทธิ์เข้าถึงบัญชีเป้าหมาย |
ระดับการเข้าถึง API
การเข้าถึง Google Ads API เป็นแบบลำดับชั้น ไม่ใช่แบบมีหรือไม่มี:
| ระดับ | การออกให้ | อ่าน (Production) | เขียน (Production) | เขียน (Test Accounts) | จำนวน Operations ต่อวัน |
|---|---|---|---|---|---|
| **Test Access** | ทันทีจาก MCC API Center | ✅ | ❌ | ✅ | 15,000 |
| **Basic Access** | การสมัคร + การตรวจสอบ Compliance | ✅ | ✅ | ✅ | 15,000 |
| **Standard Access** | เกณฑ์การใช้จ่าย + การตรวจสอบ | ✅ | ✅ | ✅ | ไม่จำกัด |
Test Access เพียงพอสำหรับการพัฒนา: การยืนยันตัวตน, คำถาม GAQL, การทดสอบ validate_only และการอ่าน/เขียนเต็มรูปแบบกับบัญชีทดสอบ Google Ads การก้าวไปสู่ Basic Access ถูกควบคุมโดยการตรวจสอบ compliance ไม่ใช่โดยการใช้จ่าย
Standard Access ยกเลิกขีดจำกัดจำนวน operations ต่อวัน ซึ่งต้องมีประวัติการใช้จ่ายที่จัดการผ่าน token หรือการสมัครแยกต่างหาก
บัญชีทดสอบ
บัญชีทดสอบเป็นบัญชี Google Ads ฟรีที่ไม่มีการเรียกเก็บเงิน สามารถรับ mutation ใด ๆ ได้ วิธีการสร้าง: ใน MCC ไปที่ Accounts → + → Create new account → Test account เชื่อมโยงกับ MCC และใช้เป็น customer_id เป้าหมายในระหว่างการพัฒนาด้วย Test-Access
`validate_only` ในแต่ละระดับการเข้าถึง
validate_only=True ทำงานได้ในทุกระดับการเข้าถึง แต่ Test Access ยังคงปฏิเสธ mutation แบบ validate_only กับบัญชีจริงด้วย RESOURCE_NOT_FOUND นี่ไม่ใช่ข้อจำกัดของ validate_only — แต่เป็นการควบคุมการเข้าถึงแบบเดียวกับที่ใช้กับการดำเนินการเขียนทั้งหมด ไม่ว่าจะมี flag validate_only หรือไม่ก็ตาม
Conversion Tracking: การอ่านผ่าน API vs. สถานะการทำงานจริง
UI ของ Google Ads อาจแสดง "Set up conversion tracking" เป็นคำแนะนำแม้ว่าจะมี conversion action อยู่แล้วและเปิดใช้งานอยู่ API สามารถยืนยันการมีอยู่ของ conversion action (ประเภท WEBPAGE, หมวดหมู่ PURCHASE, สถานะ ENABLED, include_in_conversions_metric: true) แต่นี่เป็นเพียงการพิสูจน์ว่าออบเจ็กต์ conversion action มีอยู่จริง ไม่ได้พิสูจน์ว่าเว็บไซต์ส่ง event AW-.../label ที่สอดคล้องกัน, แท็ก Google ถูกโหลด, หรือมีสัญญาณ conversion ถูกรับแล้ว
เมื่อตรวจสอบ conversion tracking ให้แยกการตรวจสอบผ่าน API (metadata ของ conversion action) ออกจากการตรวจสอบผ่านเบราว์เซอร์ (แท็บ network, การโหลดแท็ก, การส่ง event) API เป็นแหล่งความจริงสำหรับการกำหนดค่า ส่วนเบราว์เซอร์เป็นแหล่งความจริงสำหรับการทำงาน
รูปแบบสำหรับ การระบุรายได้ฝั่ง backend นำมาใช้ที่นี่: หาก event conversion ฝั่ง client ไม่น่าเชื่อถือ ให้วางแผนสำหรับการอัปโหลด conversion แบบ server-side หรือ offline ผ่าน ConversionUploadService ของ Google Ads API เป็นทางสำรอง
การกำหนดเวอร์ชัน API
ไลบรารี google-ads สำหรับ Python มาพร้อมกับ API หลายเวอร์ชัน ณ เดือนมิถุนายน 2026 มี v19 ถึง v22 ให้ใช้งาน แต่ละเวอร์ชันเพิ่ม, เลิกใช้งาน (deprecate) หรือลบฟิลด์ GAQL และเมธอดของ service
กฎสำหรับการเลือกเวอร์ชัน:
load_from_storage(version='v22') ค่าเริ่มต้นอาจล้าสมัยsearch_stream มีให้ใช้งานตั้งแต่ v6+ และเป็นเส้นทางการอ่านที่แนะนำuse_proto_plus: true) จำเป็นสำหรับ v12+mutate_campaigns(request=req)) แทนที่รูปแบบ keyword-argument ใน v17+ความปลอดภัยในเครื่อง
ไฟล์ข้อมูลรับรองทั้งหมดต้องอยู่นอก version control:
~/.hermes/
google-ads.yaml # 0600 — dev token, OAuth client, refresh token
google_ads_token.json # 0600 — OAuth tokens, adwords scope
google_client_secret.json # 0600 — OAuth client ID + secret
สำหรับ repository ที่ทำงาน ให้เพิ่ม .gitignore:
google-ads.yaml
*_token*.json
*_secret*.json
สามารถ commit ไฟล์แม่แบบที่มีค่าตัวแทนเพื่อบันทึกโครงสร้างที่คาดหวัง:
# google-ads.yaml.template — commit this, fill locally
developer_token: INSERT_DEV_TOKEN
client_id: INSERT_CLIENT_ID
client_secret: INSERT_CLIENT_SECRET
refresh_token: INSERT_REFRESH_TOKEN
login_customer_id: INSERT_MCC_ID
use_proto_plus: true
ฟิลด์ที่จำเป็นสำหรับการดำเนินการทั่วไป
| การดำเนินการ | ฟิลด์ที่จำเป็น |
|---|---|
| Create Campaign (Search) | `name`, `status`, `advertising_channel_type`, `campaign_budget`, `manual_cpc` หรือ bidding strategy |
| Create Ad Group (Search) | `name`, `status`, `type_`, `cpc_bid_micros`, `campaign` |
| Create Keyword | `ad_group`, `status`, `keyword.text`, `keyword.match_type` |
| Update Campaign Status | `resource_name`, `status`, `update_mask` (paths: `['status']`) |
| Update Budget | `resource_name`, `amount_micros`, `update_mask` (paths: `['amount_micros']`) |
การขาดฟิลด์ที่จำเป็นจะส่งคืน REQUIRED พร้อมชื่อฟิลด์ การขาด update_mask ในการดำเนินการอัปเดตจะถูกเพิกเฉยอย่างเงียบ ๆ — ฟิลด์นั้นจะไม่ถูกอัปเดตและไม่มีข้อผิดพลาดส่งกลับ
สคริปต์ทดสอบฉบับสมบูรณ์
สคริปต์เดียวที่อ่านประเภทเอนทิตีทั้งหมดและทำการทดสอบการเขียนแบบ validate_only:
# google_ads_api_test.py — read all entities + validate_only write tests
import warnings
warnings.filterwarnings('ignore')
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
from google.protobuf import field_mask_pb2
CFG = '/path/to/google-ads.yaml'
CID = '1305475941'
c = GoogleAdsClient.load_from_storage(CFG, version='v22')
gs = c.get_service('GoogleAdsService')
def gaql(query, label):
print(f'\n=== {label} ===')
n = 0
for batch in gs.search_stream(customer_id=CID, query=query):
for row in batch.results:
n += 1
print(f'{n} rows')
# Read all entities
gaql("SELECT customer.id, customer.descriptive_name FROM customer WHERE customer.id = ...", "CUSTOMER")
gaql("SELECT campaign.id, campaign.name, campaign.status, ... FROM campaign", "CAMPAIGNS")
gaql("SELECT campaign_budget.id, campaign_budget.name, ... FROM campaign_budget", "BUDGETS")
gaql("SELECT ad_group.id, ad_group.name, ... FROM ad_group", "AD GROUPS")
gaql("SELECT ad_group_criterion.keyword.text, ... FROM keyword_view WHERE ...", "KEYWORDS")
gaql("SELECT ad_group_ad.ad.id, ... FROM ad_group_ad", "ADS")
gaql("SELECT conversion_action.id, ... FROM conversion_action", "CONVERSIONS")
# Write tests
def test_write(name, fn):
print(f'\n--- {name} ---')
try:
fn()
print('PASS')
except GoogleAdsException as e:
for err in e.failure.errors:
print(f'{err.error_code}: {err.message}')
def test_create_campaign():
req = c.get_type('MutateCampaignsRequest')
req.customer_id = CID
req.validate_only = True
op = c.get_type('CampaignOperation')
op.create.name = 'TEST_CAMPAIGN'
op.create.status = c.enums.CampaignStatusEnum.PAUSED
op.create.advertising_channel_type = c.enums.AdvertisingChannelTypeEnum.SEARCH
op.create.campaign_budget = f'customers/{CID}/campaignBudgets/123456789'
op.create.manual_cpc.enhanced_cpc_enabled = False
req.operations.append(op)
c.get_service('CampaignService').mutate_campaigns(request=req)
test_write('Create Campaign (validate_only)', test_create_campaign)
print('\nDone.')
เวอร์ชันที่ทำงานได้เต็มรูปแบบอยู่ที่ Google Ads API operations skill ใน advertising operations repository