Навык Claude, Который Пишет Мои Объявления Google Ads
Я создал навык Claude, который напрямую запрашивает API Google Ads через GAQL, находит адаптивные поисковые объявления со слабым CTR или высоким CPA и переписывает заголовки и описания в пределах точных лимитов символов Google (30 и 90). Новое объявление создаётся в статусе ПАУЗА — я сам проверяю и включаю его. Главное здесь: лимиты символов Google — это реальное ограничение, а не сам текст. Модель, которая неправильно считает символы, выдаёт объявления, которые Google молча обрезает или отклоняет.
Каждую среду. 28 400+ читателей. Никакой воды.
✓ Проверьте почту — нажмите ссылку подтверждения, чтобы завершить подписку.
✓ Вы подписаны!
✓ Вы уже в списке.
[Взгляд оператора] У меня уже есть навык Claude, который управляет моими объявлениями в Facebook — эта разработка здесь. Google Ads оказался сложнее в продолжении, не потому что API хуже, а потому что у адаптивных поисковых объявлений есть жёсткие лимиты символов, которых нет у Facebook: 30 символов на заголовок, 90 на описание. Переписанный текст, который хорошо читается, но отклоняется или обрезается при показе, — это не переписанный текст, это баг. Большая часть работы над этим навыком ушла на то, чтобы заставить модель соблюдать это ограничение, а не доверять, что она сделает это сама.
Table of contents
Open Table of contents
- Почему Google Ads требовал другой сборки, чем Facebook
- Получение доступа к API (часть, которую все недооценивают)
- Структура файлов навыка
- Основной код на TypeScript
- Как я использую это каждый день
- Чего валидатор не улавливает
- Инструменты, которые я использую для этого
- Часто задаваемые вопросы
- Вывод оператора
Почему Google Ads требовал другой сборки, чем Facebook
Навык для Facebook читает данные о производительности, переписывает текст и публикует новый набор объявлений. Google Ads делает те же три задачи, но два момента отличаются:
- Язык запросов — GAQL, а не REST-фильтры. API Google Ads v18 работает на Google Ads Query Language — в формате SQL, только для чтения, созданном именно для такой выгрузки отчётов.
- У адаптивных поисковых объявлений жёсткие лимиты. До 15 заголовков (по 30 символов каждый) и 4 описаний (по 90 символов каждое) на объявление. Google не предупреждает мягко — он либо отклоняет актив при отправке, либо, что хуже, показывает обрезанную версию, которая выглядит неправильно для реального клиента.
Пункт 2 — это то, что ломает наивные реализации. Попросите модель «написать более яркий заголовок», и она с радостью вернёт 34 символа. Google Ads отклонит этот актив или отрендерит его обрезанным посреди слова. Я узнаю об этом только когда вижу живое объявление, которое заканчивается на «Забронируйте визи—».
Получение доступа к API (часть, которую все недооценивают)
Три вещи, и третья стоит вам недели, если не спланировать заранее:
- Проект Google Cloud с учётными данными OAuth2 — client ID, client secret и refresh token с правом доступа
https://www.googleapis.com/auth/adwords. Сгенерируйте refresh token один раз через OAuth playground или короткий локальный скрипт; после этого это сохранённые учётные данные, а не процесс входа. - Developer token, запрошенный из вашего управляющего аккаунта (MCC) Google Ads в разделе Администрирование → Центр API.
- Уровень доступа. Каждый новый developer token начинается с доступа Test Account — он работает только с тестовыми аккаунтами Google Ads, а не с вашим реальным. Вы запрашиваете Basic Access (до 15 000 операций в день, этого достаточно) через форму, которую Google проверяет вручную. Отведите несколько рабочих дней на одобрение, прежде чем планировать запуск на реальном аккаунте.
Сохраните GOOGLE_ADS_CLIENT_ID, GOOGLE_ADS_CLIENT_SECRET, GOOGLE_ADS_REFRESH_TOKEN, GOOGLE_ADS_DEVELOPER_TOKEN и GOOGLE_ADS_CUSTOMER_ID (10-значный ID аккаунта без дефисов) в вашем .env. Если вы управляете аккаунтом через MCC, вам также нужен GOOGLE_ADS_LOGIN_CUSTOMER_ID с ID управляющего аккаунта — без этого запросы отклоняются с ошибкой прав доступа, и сообщение об ошибке не говорит, почему.
Структура файлов навыка
.claude/skills/google-ads/
SKILL.md ← инструкции, которые читает Claude
index.ts ← реальная реализация инструмента
limits.ts ← проверка лимитов символовSKILL.md:
# Google Ads Manager Skill
Use this skill when the user says "check my Google Ads", "run ads
report", or "rewrite underperforming RSAs". Never run this without
explicit user instruction — it touches live ad spend.
## What it can do
- Pull campaign and ad group performance via GAQL (last 7 or 30 days)
- Flag responsive search ads with CTR < 3% or CPA above the stored
target as underperformers
- Rewrite headlines (≤30 chars) and descriptions (≤90 chars) for
flagged ads in Ale's voice
- Create new ad variations as PAUSED — never enabled automatically
## What it will NOT do
- Change budgets or bids without explicit confirmation
- Enable a paused ad
- Submit any headline or description that fails the character checkПоследнюю строку в «What it will NOT do» я добавил после того, как в первую неделю навык опубликовал заголовок на 32 символа. Собственный подсчёт модели был неверен на два символа — достаточно близко, чтобы пройти при беглом взгляде, и достаточно далеко, чтобы актив отклонили. С тех пор всё проходит через строгий валидатор, а не через самоотчёт модели.
Основной код на TypeScript
// .claude/skills/google-ads/index.ts
import { GoogleAdsApi } from "google-ads-api";
import Anthropic from "@anthropic-ai/sdk";
const client = new GoogleAdsApi({
client_id: process.env.GOOGLE_ADS_CLIENT_ID!,
client_secret: process.env.GOOGLE_ADS_CLIENT_SECRET!,
developer_token: process.env.GOOGLE_ADS_DEVELOPER_TOKEN!,
});
const customer = client.Customer({
customer_id: process.env.GOOGLE_ADS_CUSTOMER_ID!,
login_customer_id: process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID,
refresh_token: process.env.GOOGLE_ADS_REFRESH_TOKEN!,
});
interface AdPerformance {
adGroupAdId: string;
campaignName: string;
headlines: string[];
descriptions: string[];
impressions: number;
clicks: number;
costMicros: number;
conversions: number;
ctr: number;
cpa: number;
}
async function getAdPerformance(days = 7): Promise<AdPerformance[]> {
const rows = await customer.query(`
SELECT
ad_group_ad.ad.id,
campaign.name,
ad_group_ad.ad.responsive_search_ad.headlines,
ad_group_ad.ad.responsive_search_ad.descriptions,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions
FROM ad_group_ad
WHERE segments.date DURING LAST_${days === 30 ? "30" : "7"}_DAYS
AND ad_group_ad.status = 'ENABLED'
`);
return rows.map((r: any) => {
const impressions = Number(r.metrics.impressions ?? 0);
const clicks = Number(r.metrics.clicks ?? 0);
const cost = Number(r.metrics.cost_micros ?? 0) / 1_000_000;
const conversions = Number(r.metrics.conversions ?? 0);
return {
adGroupAdId: r.ad_group_ad.ad.id,
campaignName: r.campaign.name,
headlines: (r.ad_group_ad.ad.responsive_search_ad?.headlines ?? []).map((h: any) => h.text),
descriptions: (r.ad_group_ad.ad.responsive_search_ad?.descriptions ?? []).map((d: any) => d.text),
impressions,
clicks,
costMicros: Number(r.metrics.cost_micros ?? 0),
conversions,
ctr: impressions > 0 ? (clicks / impressions) * 100 : 0,
cpa: conversions > 0 ? cost / conversions : Infinity,
};
});
}
const HEADLINE_MAX = 30;
const DESCRIPTION_MAX = 90;
function validateAsset(text: string, max: number): boolean {
return text.length <= max;
}
async function rewriteRsa(
ad: AdPerformance,
targetCpa: number,
): Promise<{ headlines: string[]; descriptions: string[] }> {
const anthropic = new Anthropic();
const msg = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{
role: "user",
content: `Rewrite this Google Ads responsive search ad in Alejandro Rioja's voice: direct, operator-focused, specific. It's underperforming — CTR ${ad.ctr.toFixed(2)}%, CPA $${ad.cpa.toFixed(2)} vs a $${targetCpa} target.
Current headlines: ${JSON.stringify(ad.headlines)}
Current descriptions: ${JSON.stringify(ad.descriptions)}
Rules, non-negotiable:
- Each headline: 30 characters MAXIMUM, including spaces and punctuation
- Each description: 90 characters MAXIMUM, including spaces and punctuation
- Return 5 headlines and 4 descriptions
- Return JSON only: {"headlines": ["...", ...], "descriptions": ["...", ...]}`,
}],
});
const text = (msg.content[0] as any).text.replace(/```json\n?/, "").replace(/```/, "").trim();
const parsed = JSON.parse(text);
const headlines = parsed.headlines.filter((h: string) => validateAsset(h, HEADLINE_MAX));
const descriptions = parsed.descriptions.filter((d: string) => validateAsset(d, DESCRIPTION_MAX));
if (headlines.length < 3 || descriptions.length < 2) {
throw new Error("Model returned too many over-limit assets — rerun or revise manually.");
}
return { headlines, descriptions };
}Каждый заголовок и описание проверяются на соответствие лимиту в коде, после генерации, прежде чем что-либо коснётся API. Переписанный модель текст — это черновик. Валидатор — это фильтр.
Как я использую это каждый день
> проверь мой Google Ads за последние 7 днейClaude выполняет запрос GAQL, отмечает всё, что ниже 3% CTR или выше моей сохранённой цели по CPA, и спрашивает перед переписыванием. Я подтверждаю. Он генерирует новые заголовки и описания, пропускает их через валидатор символов, отбрасывает то, что не проходит, и создаёт новое объявление в статусе PAUSED через операцию mutate API Google Ads. Я сравниваю новый текст со старым прямо в Google Ads, включаю те, которые действительно стал бы использовать, и оставляю остальное на паузе.
Тот же потолок, что и у версии для Facebook: примерно 20 минут вместо целого дня в интерфейсе, потому что механическая часть — извлечение данных, составление текста в рамках жёсткого ограничения, подготовка изменения — это именно то, что модель должна делать вместо меня.
Чего валидатор не улавливает
Подсчёт символов — механический процесс. Релевантность — нет. Заголовок может быть на 28 символов, пройти все проверки и всё равно оказаться неправильным сообщением для поискового запроса, к которому он привязан. Навык не может определить, связана ли низкая эффективность группы объявлений с плохим предложением, несоответствием с посадочной страницей или действительно слабым текстом — он может только сказать мне, что текст слаб по сравнению с объявлениями рядом. Если у всех объявлений в кампании низкий CTR, это проблема таргетинга или посадочной страницы, и если дать навыку больше циклов переписывания для неправильно подобранного поискового намерения, это даст только более послушный, но столь же игнорируемый текст.
Я сам читаю вывод GAQL перед тем, как одобрить пакет переписываний, так же как делаю со стороны Facebook. Навык убирает набор текста и подсчёт символов. Он не убирает суждение о том, что на самом деле сломано.
Инструменты, которые я использую для этого
Claude — то, на чём работают оба рекламных навыка, и для Facebook, и этот, они разделяют один и тот же паттерн переписать-и-проверить, просто против другого API и более жёсткого бюджета символов.
Google Ads API (npm-пакет google-ads-api) — обёртка на TypeScript вокруг необработанного gRPC/REST API. Она обрабатывает обновление OAuth и построение запросов GAQL, чтобы не собирать protobuf вручную.
Часто задаваемые вопросы
Мне нужен Basic Access, или подойдёт доступ Test Account?
Доступ Test Account работает только с тестовыми аккаунтами Google Ads — полезен для разработки, бесполезен для реального аккаунта с реальными расходами. Вам нужен Basic Access (запрашивается в Центре API вашего управляющего аккаунта), прежде чем этот навык сможет коснуться живой кампании. Одобрение — это ручная проверка, а не мгновенная, так что запрашивайте его заранее, а не в ту неделю, когда хотите запустить.
Почему 5 заголовков и 4 описания, а не максимумы 15 и 4?
Google разрешает до 15 заголовков, но и Ad Strength, и реальные тесты показывают убывающую отдачу после 8–10 действительно разных подходов — большинство аккаунтов заканчивают с несколькими почти дублирующими заголовками, которые просто заполняют счёт. Я ограничиваю вывод модели пятью новыми заголовками на пакет переписывания, чтобы рассматривать действительно разные варианты, а не отсеивать девять вариаций одной и той же фразы.
Что происходит, если переписанный модель текст не проходит проверку символов при каждой попытке?
Валидатор выбрасывает ошибку, а не молча обрезает или отправляет актив с превышением лимита. На практике это означает, что я перезапускаю промпт один раз, и если он проваливается два раза, я пишу этот конкретный заголовок вручную. Чаще это случается с описаниями — 90 символов — более узкое пространство для полноценного предложения, чем кажется.
Также по теме: для отчётности только для чтения по GA4, Search Console, Bing, Ahrefs и Core Web Vitals в одном коннекторе Claude вместо создания отдельного навыка под каждую платформу, я использую SiteWide MCP — та же идея подключения Claude к реальным маркетинговым данным, но с более широким охватом, чем одна рекламная платформа.
Вывод оператора
Если вы пишете варианты адаптивных поисковых объявлений вручную и сами считаете символы, это именно та механическая, основанная на правилах задача, которую должна взять на себя модель. Часть, требующая реального суждения — проблема в тексте или в таргетинге — остаётся за вами. Создайте навык, дайте ему писать и проверять текст по лимитам Google, а диагностику оставьте себе.
Каждую среду. 28 400+ читателей. Никакой воды.
✓ Проверьте почту — нажмите ссылку подтверждения, чтобы завершить подписку.
✓ Вы подписаны!
✓ Вы уже в списке.
Похожие статьи
Запланированные задачи в Claude Cowork: гид
Запланированные задачи в Claude Cowork превращают разовый промпт в повторяющуюся работу, которая запускается сама — без кода и сервера. Вот как её настроить.
AI AgentsУслуги на базе ИИ-агентов, которые агентство продаст в 2026
Шесть автоматизаций на ИИ-агентах из моего собственного бизнеса, переупакованные в пункты услуг, которые агентство может очертить, оценить и продать.
AI AgentsИИ-агенты для SaaS: что автоматизировать первым
Фреймворк уровней и ROI для решений об ИИ-агентах, применённый к SaaS-процессам, которые действительно стоит автоматизировать в первую очередь.
Получайте ИИ-руководство на почту
Каждую среду. 28 400+ читателей. Никакой воды.
Проверьте почту.
Мы отправили письмо для подтверждения — нажмите на ссылку, чтобы завершить подписку. Проверьте папку «Спам», если не видите его в течение минуты.
Вы подписаны.
Добро пожаловать — следующий выпуск скоро придёт на вашу почту.
Вы уже в списке — ждите выпуск каждую среду.