做外贸询盘的 Google Ads 账户,最常听到的抱怨是:询盘不少,能谈的没几个。 一半是同行、学生、骗子和机器人,剩下的一半里还有很多只要样品不下单的。
原因很简单:Google 只知道「有人提交了表单」,不知道哪个询盘是真客户。你告诉它「询盘越多越好」,它就会去找最容易提交表单的人——而这些人往往最不可能成交。
解决方法是把 CRM 里的有效询盘和成交数据回传给 Google(离线转化导入,或潜在客户的增强型转化),让智能出价去追求真正有价值的询盘。这件事说起来简单,做起来很多地方会出错:自动标记没开、微转化被设成主要转化、导入任务断了没人发现……
这个脚本是毕瑞出海原创的,专门把这些检查自动化。
检查哪些问题
1. 转化设置
| 检查项 | 问题 | 影响 |
|---|---|---|
| 自动标记 | 没有开启 | 没有 GCLID,离线导入完全无法匹配 |
| 微转化 | 浏览页面、点击按钮、下载等被设为「主要转化」 | 智能出价会去优化这些几乎没有价值的行为 |
| 询盘计数 | 询盘按「每次」计数 | 同一个人提交 3 次被算成 3 个询盘 |
| 询盘价值 | 询盘没有设置价值 | 无法用价值出价区分询盘和成交 |
| 重复统计 | 多个询盘类转化同时是主要转化 | 同一个表单被网站代码和 GA4 各算一次 |
| 增强型转化 | 「潜在客户的增强型转化」没有开启 | 只能靠 GCLID 匹配,匹配率低 |
| 转化窗口 | 有效询盘、成交的转化窗口太短 | B2B 成交周期长,窗口过短会漏掉成交 |
| 数据中断 | 主要转化最近 14 天突然没有数据 | 代码掉了或导入断了 |
| 已停用来源 | 来自 Universal Analytics 的转化 | UA 已停用,不会再有数据 |
2. 离线导入状态
读取 Google Ads 的离线导入诊断数据:每种上传方式和每个转化操作的上传总数、成功数、成功率、待处理数量、最后上传时间和报错类型。超过 7 天没有上传、或者状态为「需要处理」时会提醒。
3. 询盘质量漏斗
按广告系列统计:
| 广告系列 | 询盘 | 有效询盘 | 成交 | 有效率 | 每个有效询盘成本 |
|---|---|---|---|---|---|
| 搜索-美国 | 100 | 30 | 3 | 30% | 133 |
| PMax-询盘 | 150 | 5 | 0 | 3% | 300 |
只看询盘数,PMax 的询盘更多、更便宜;看有效询盘,PMax 的成本是搜索的两倍多。这就是很多 B2B 账户「开了 PMax 询盘暴涨、业务员却说没用」的原因。
脚本会标出有效率低于账户平均一半的系列,以及「询盘数 ÷ 点击数」异常高的系列(正常 B2B 表单的提交率通常在 2–10%,超过 30% 多半是机器人在刷表单)。
转化操作怎么识别
脚本按转化类别和名称识别每个转化操作的角色:
| 角色 | 识别规则 |
|---|---|
| 询盘 | 类别是「提交潜在客户表单 / 联系 / 索取报价」,或名称包含 lead、form、inquiry、quote、contact、whatsapp、email、询盘、表单 等 |
| 有效询盘 | 类别是「合格潜在客户」,或名称包含 qualified、MQL、SQL、valid、有效 |
| 成交 | 类别是「已转化的潜在客户」,或名称包含 closed、won、deal、order、成交 |
| 微转化 | 类别是网页浏览、互动、出站点击、下载等 |
如果你的转化操作命名不一样,在设置里改 LEAD_WORDS、QUALIFIED_WORDS、CLOSED_WORDS 即可。
脚本只读取数据,不会修改转化设置。
安装步骤(10 分钟)
- 登录 Google Ads,点击左侧「工具」→「批量操作」→「脚本」。
- 点击蓝色「+」按钮,选择「新建脚本」,起个容易认的名字。
- 删掉编辑器里默认的代码,把本文末尾的完整代码全部粘贴进去(也可以从 GitHub 下载)。
- 修改代码开头
CONFIG里的设置,至少填好EMAIL(接收提醒的邮箱)。 - 点击「授权」,用有这个账户权限的 Google 账号登录并允许访问。
- 点击「预览」。第一次运行会自动新建一个 Google 表格,在下方「日志」里找到以「★ 新建的表格」开头的一行,复制网址填到
SPREADSHEET_URL的两个引号中间,保存。 - 回到脚本列表,设置运行频率(见下方建议)。
预览模式下脚本也会正常写入表格,但不会修改账户里的任何设置。
建议运行频率:每天一次,离线导入出问题时第二天就能发现。
设置说明
| 设置 | 默认值 | 说明 |
|---|---|---|
| SPREADSHEET_URL | 空 | 报告表格网址,第一次运行后填入 |
| 空 | 发现严重问题时发邮件(每天最多一封) | |
| DAYS | 90 | 统计天数 |
| LEAD_WORDS / QUALIFIED_WORDS / CLOSED_WORDS | 内置词表 | 识别询盘、有效询盘、成交的名称关键词 |
| UPLOAD_STALE_DAYS | 7 | 离线导入多少天没上传就提醒 |
| SPAM_RATE | 0.3 | 询盘/点击超过多少算疑似机器人 |
外贸询盘账户的推荐转化设置
- 只把真正的询盘作为主要转化:表单提交、询价邮件、WhatsApp 点击(如果确实是询盘入口)。浏览页面、停留时长、点击电话号码这类只做「次要」转化。
- 询盘计数选「一次」。
- 在 CRM 里标记询盘质量:业务员每天把询盘分成有效 / 无效,成交后标记成交和金额。
- 把质量数据回传 Google:
– 方式一:离线转化导入——表单里隐藏一个 GCLID 字段,把它和询盘一起存进 CRM,有效询盘和成交时把 GCLID 上传给 Google。 – 方式二:潜在客户的增强型转化——在 Google Ads 里开启,表单提交时回传加密的邮箱;之后上传有效询盘和成交时用邮箱匹配,不需要保存 GCLID,匹配率通常更高。 – HubSpot、Salesforce、Zoho 等 CRM 有现成的 Google Ads 集成;其他 CRM 可以用 Zapier、Make 定时上传。
- 逐步切换出价目标:有效询盘每月能稳定积累 30 个以上后,把「有效询盘」设为主要转化(或者给有效询盘、成交设置更高的价值,用价值出价),询盘降为次要。
常见问题
「离线导入状态」是空的?
说明这个账户最近没有离线导入,或者导入不是用这个账户的 ID 上传的(例如在经理账户层面上传)。Google 只保留最近的导入诊断。
我的询盘都通过 WhatsApp 来,怎么回传质量?
WhatsApp 点击本身没法带回用户信息。比较可行的做法是:在 WhatsApp 跳转链接里带上 GCLID 或唯一编号(例如预填消息「询价编号 12345」),业务员在 CRM 里记录编号,成交后按编号找到 GCLID 上传。
机器人提交怎么防?
在表单加 reCAPTCHA 或隐藏的「蜜罐」字段;检查疑似机器人系列的展示位置(常见于 PMax 和展示广告的 App 流量,可以用 展示位置清理脚本);最根本的还是把出价目标换成「有效询盘」,机器人就不会再被奖励。
完整代码
代码也放在 GitHub 上,可以直接下载:offline-conversion-check.js。复制时点击代码框右上角的按钮,或者全选后复制。
/**
* 询盘质量与离线转化检查 v1.0(毕瑞出海中文版)
* ------------------------------------------------------------
* 作用:专门给外贸询盘(B2B)账户用。检查「Google 知道哪些询盘是真的」这件事有没有做好:
* 1. 转化设置:自动标记是否开启、主要转化里是否混进了浏览页面/点击按钮这类「微转化」、
* 询盘是否按「每次点击只算一次」计数、询盘有没有设置价值、增强型转化是否开启;
* 2. 离线转化导入(把 CRM 里的有效询盘、成交订单回传给 Google):最近有没有上传、成功率多少、有哪些报错;
* 3. 询盘质量漏斗:每个广告系列的询盘 → 有效询盘 → 成交,找出「询盘很多但几乎都是垃圾」的系列;
* 4. 疑似垃圾询盘:询盘数占点击数比例异常高的系列(机器人提交表单)。
*
* 毕瑞出海原创脚本。
*
* 教程:https://berightdigital.com/google-ads-offline-conversion-check-script/
* ------------------------------------------------------------
*/
// ======================= 设置(只需要改这里) =======================
var CONFIG = {
// Google 表格网址。留空 = 第一次运行时自动新建,网址打印在「日志」里,复制回这里。
SPREADSHEET_URL: '',
// 发现严重问题时发邮件(留空 = 不发)
EMAIL: '',
// 统计最近多少天
DAYS: 90,
// 名称包含这些词的转化操作算「询盘」(表单提交、邮件、WhatsApp 点击等),不区分大小写
LEAD_WORDS: ['lead', 'form', 'inquiry', 'enquiry', 'quote', 'contact', 'rfq', '询盘', '表单', 'whatsapp', 'email'],
// 名称包含这些词的转化操作算「有效询盘」
QUALIFIED_WORDS: ['qualified', 'mql', 'sql', 'valid', '有效'],
// 名称包含这些词的转化操作算「成交」
CLOSED_WORDS: ['closed', 'won', 'deal', 'order', 'customer', '成交', '下单'],
// 离线导入多少天没有上传就提醒
UPLOAD_STALE_DAYS: 7,
// 询盘数 ÷ 点击数超过这个比例,怀疑有机器人提交
SPAM_RATE: 0.3
};
// ======================= 设置结束,下面的代码一般不需要改 =======================
var VERSION = 'v1.0';
var TYPE_CN = { WEBPAGE: '网站代码', WEBPAGE_CODELESS: '网站(无代码)', UPLOAD_CLICKS: '离线导入(GCLID)', UPLOAD_CALLS: '电话导入', GOOGLE_ANALYTICS_4_CUSTOM: 'GA4 自定义事件',
GOOGLE_ANALYTICS_4_PURCHASE: 'GA4 购买', AD_CALL: '广告来电', WEBSITE_CALL: '网站来电', CLICK_TO_CALL: '点击致电', SALESFORCE: 'Salesforce', STORE_SALES: '门店销售',
FIREBASE_ANDROID_FIRST_OPEN: 'App 首次打开', SMART_CAMPAIGN_TRACKED_CALLS: '智能系列来电', UNIVERSAL_ANALYTICS_GOAL: 'UA 目标(已停用)', UNIVERSAL_ANALYTICS_TRANSACTION: 'UA 交易(已停用)' };
var CAT_CN = { SUBMIT_LEAD_FORM: '提交潜在客户表单', QUALIFIED_LEAD: '合格潜在客户', CONVERTED_LEAD: '已转化的潜在客户', CONTACT: '联系', PURCHASE: '购买', PAGE_VIEW: '网页浏览',
DEFAULT: '其他', IMPORTED_LEAD: '导入的潜在客户', PHONE_CALL_LEAD: '来电潜在客户', REQUEST_QUOTE: '索取报价', SIGNUP: '注册', ENGAGEMENT: '互动', OUTBOUND_CLICK: '出站点击',
GET_DIRECTIONS: '获取路线', BOOK_APPOINTMENT: '预约', ADD_TO_CART: '加入购物车', BEGIN_CHECKOUT: '开始结账', SUBSCRIBE_PAID: '付费订阅', DOWNLOAD: '下载' };
var MICRO_CATS = { PAGE_VIEW: 1, ENGAGEMENT: 1, OUTBOUND_CLICK: 1, DOWNLOAD: 1, ADD_TO_CART: 1, BEGIN_CHECKOUT: 1, GET_DIRECTIONS: 1 };
function main() {
var ss = openSpreadsheet('询盘与离线转化检查');
log('信息', '开始', '版本 ' + VERSION);
var A = [];
var acct = safe('账户设置', accountSettings) || {};
var actions = safe('转化操作', fetchActions) || [];
safe('转化设置检查', function () { checkActions(ss, actions, acct, A); });
safe('离线导入状态', function () { uploads(ss, actions, A); });
safe('询盘质量', function () { funnel(ss, actions, A); });
safe('行动清单', function () { writeActions(ss, A, '转化设置和离线导入都正常'); });
var hi = A.filter(function (a) { return a[0] === '高'; });
if (CONFIG.EMAIL && hi.length && stateGet(ss, 'mailed') !== today()) safe('邮件', function () {
if (!isPreview()) stateSet(ss, 'mailed', today());
mail('[询盘转化检查] ' + AdsApp.currentAccount().getName() + ':' + hi.length + ' 个严重问题', '转化跟踪或离线导入有需要马上处理的问题:',
[['优先级', '类别', '对象', '发现', '建议']].concat(hi.map(function (a) { return [a[0], a[1], a[2], a[3], a[5]]; })), ss);
});
safe('使用说明', function () {
writeGuide(ss, '询盘质量与离线转化检查', VERSION, [
['适用账户', '外贸询盘、B2B 获客账户;电商账户也可以用前两部分检查转化设置'],
['询盘识别', '转化类别为「提交潜在客户表单 / 联系 / 索取报价」,或名称包含:' + CONFIG.LEAD_WORDS.join('、')],
['有效询盘 / 成交', '类别为「合格潜在客户 / 已转化的潜在客户」,或名称包含:' + CONFIG.QUALIFIED_WORDS.join('、') + ' / ' + CONFIG.CLOSED_WORDS.join('、')],
['', ''],
['行动清单', '先看这里:会直接影响智能出价的设置问题和离线导入故障'],
['转化设置检查', '每个转化操作的类型、类别、是否主要转化、计数方式、价值、最近转化数和问题'],
['离线导入状态', '离线转化导入的上传次数、成功率、待处理数量和报错'],
['询盘质量', '每个广告系列的询盘、有效询盘、成交和转化率;找出垃圾询盘多的系列'],
['建议频率', '每天运行一次(离线导入出问题时能及时发现)']
]);
});
writeLog(ss);
orderSheets(ss, ['使用说明', '行动清单', '转化设置检查', '离线导入状态', '询盘质量', '运行日志']);
}
// ======================= 读取 =======================
function accountSettings() {
var r = tryQuery('自动标记', 'SELECT customer.auto_tagging_enabled, customer.conversion_tracking_setting.enhanced_conversions_for_leads_enabled, ' +
'customer.conversion_tracking_setting.accepted_customer_data_terms FROM customer') ||
tryQuery('自动标记', 'SELECT customer.auto_tagging_enabled FROM customer') || [];
var c = (r[0] || {}).customer || {}, ct = c.conversionTrackingSetting || {};
return { autoTag: c.autoTaggingEnabled, ecl: ct.enhancedConversionsForLeadsEnabled, terms: ct.acceptedCustomerDataTerms };
}
function lower(s) { return String(s || '').toLowerCase(); }
function hasAny(s, words) { s = lower(s); return words.some(function (w) { return s.indexOf(lower(w)) >= 0; }); }
function fetchActions() {
var full = 'SELECT conversion_action.id, conversion_action.name, conversion_action.type, conversion_action.category, conversion_action.status, ' +
'conversion_action.primary_for_goal, conversion_action.include_in_conversions_metric, conversion_action.counting_type, ' +
'conversion_action.value_settings.default_value, conversion_action.value_settings.always_use_default_value, ' +
'conversion_action.click_through_lookback_window_days, conversion_action.attribution_model_settings.attribution_model FROM conversion_action ' +
'WHERE conversion_action.status = "ENABLED"';
var rows = tryQuery('转化操作', full) || runQuery('SELECT conversion_action.id, conversion_action.name, conversion_action.type, conversion_action.category, conversion_action.status, ' +
'conversion_action.include_in_conversions_metric, conversion_action.counting_type FROM conversion_action WHERE conversion_action.status = "ENABLED"');
var list = rows.map(function (r) {
var c = r.conversionAction || {}, vs = c.valueSettings || {};
var a = { id: String(c.id), name: String(c.name || ''), type: String(c.type || ''), cat: String(c.category || ''),
primary: c.primaryForGoal === undefined ? c.includeInConversionsMetric : c.primaryForGoal, counting: String(c.countingType || ''),
defValue: num(vs.defaultValue), alwaysDefault: !!vs.alwaysUseDefaultValue, window: num(c.clickThroughLookbackWindowDays),
model: String((c.attributionModelSettings || {}).attributionModel || ''), recent: 0, base: 0, last: '', value: 0 };
a.role = /QUALIFIED_LEAD/.test(a.cat) || hasAny(a.name, CONFIG.QUALIFIED_WORDS) ? 'qualified'
: /CONVERTED_LEAD|PURCHASE/.test(a.cat) || hasAny(a.name, CONFIG.CLOSED_WORDS) ? 'closed'
: /SUBMIT_LEAD_FORM|CONTACT|REQUEST_QUOTE|IMPORTED_LEAD|PHONE_CALL_LEAD/.test(a.cat) || hasAny(a.name, CONFIG.LEAD_WORDS) ? 'lead'
: MICRO_CATS[a.cat] ? 'micro' : 'other';
a.offline = /UPLOAD/.test(a.type) || a.type === 'SALESFORCE';
return a;
});
// 最近的转化数(按转化操作)
var byName = {}; list.forEach(function (a) { byName[a.name] = a; });
(tryQuery('转化数', 'SELECT segments.conversion_action_name, segments.date, metrics.all_conversions, metrics.all_conversions_value FROM customer WHERE ' + range(CONFIG.DAYS)) || []).forEach(function (r) {
var a = byName[String(g(r, 'segments.conversionActionName'))]; if (!a) return;
var d = String(g(r, 'segments.date')), v = num(g(r, 'metrics.allConversions'));
if (d >= dayStr(-14)) a.recent += v; else a.base += v;
a.value += num(g(r, 'metrics.allConversionsValue'));
if (v > 0 && d > a.last) a.last = d;
});
return list;
}
// ======================= 1. 转化设置 =======================
function checkActions(ss, actions, acct, A) {
if (acct.autoTag === false) A.push(['高', '账户设置', '自动标记', '自动标记(Auto-tagging)没有开启', '', '离线转化导入依赖 GCLID,必须开启:管理员 → 账户设置 → 自动标记']);
if (acct.ecl === false) A.push(['中', '账户设置', '增强型转化(潜在客户)', '没有开启「潜在客户的增强型转化」', '', '开启后可以用表单里的邮箱/电话(加密后)回传成交,不完全依赖 GCLID,匹配率更高']);
var primaries = actions.filter(function (a) { return a.primary; });
var leads = actions.filter(function (a) { return a.role === 'lead'; });
actions.forEach(function (a) {
a.issues = [];
if (a.primary && a.role === 'micro') a.issues.push('微转化被设为主要转化:智能出价会去优化这种低价值行为');
if (a.role === 'lead' && a.counting === 'MANY_PER_CLICK') a.issues.push('询盘按「每次」计数:同一个人提交多次会被算成多个询盘,建议改为「一次」');
if (a.role === 'lead' && a.primary && !a.defValue) a.issues.push('询盘没有设置价值:用转化价值出价时无法区分询盘和成交的价值');
if (/UNIVERSAL_ANALYTICS/.test(a.type)) a.issues.push('来自已停用的 Universal Analytics,不会再有数据');
if (a.primary && a.base + a.recent === 0) a.issues.push('主要转化在最近 ' + CONFIG.DAYS + ' 天没有任何数据');
if (a.primary && a.base / Math.max(1, CONFIG.DAYS - 14) * 14 >= 5 && a.recent === 0) a.issues.push('最近 14 天突然没有转化(之前平均每两周 ' + Math.round(a.base / (CONFIG.DAYS - 14) * 14) + ' 次)');
if (a.window && a.window < 30 && (a.role === 'qualified' || a.role === 'closed')) a.issues.push('点击后转化窗口只有 ' + a.window + ' 天:B2B 成交周期长,建议设为 90 天');
});
if (primaries.filter(function (a) { return a.role === 'lead'; }).length > 1) A.push(['中', '转化设置', '主要转化', primaries.filter(function (a) { return a.role === 'lead'; }).length + ' 个询盘类转化同时是主要转化',
primaries.filter(function (a) { return a.role === 'lead'; }).map(function (a) { return a.name; }).join('、'), '确认没有重复统计(例如同一个表单既有网站代码又有 GA4 导入);重复的设为「次要」']);
var micro = primaries.filter(function (a) { return a.role === 'micro'; });
if (micro.length) A.push(['高', '转化设置', micro.map(function (a) { return a.name; }).join('、'), '浏览页面、点击按钮这类微转化被设为主要转化', '', '把它们改为「次要」转化,只让询盘、有效询盘、成交作为主要转化']);
if (leads.length && !actions.some(function (a) { return a.offline; })) A.push(['中', '离线导入', '—', '还没有离线转化导入', '', '把 CRM 里的有效询盘和成交回传给 Google(离线转化导入或潜在客户的增强型转化),智能出价才能分辨好询盘和垃圾询盘']);
actions.filter(function (a) { return a.issues.length && a.role !== 'micro'; }).forEach(function (a) {
A.push([/突然|没有任何数据|Universal/.test(a.issues.join()) && a.primary ? '高' : '中', '转化操作', a.name, a.issues[0], '最近 14 天 ' + r2(a.recent) + ',之前 ' + r2(a.base), a.issues.slice(1).join(';') || '见「转化设置检查」表']);
});
var ROLE_CN = { lead: '询盘', qualified: '有效询盘', closed: '成交', micro: '微转化', other: '其他' };
write(ss, '转化设置检查', ['转化操作', '识别为', '类型', '类别', '主要转化', '计数方式', '默认价值', '转化窗口(天)', '归因模型', '最近 14 天', '之前', '最后一次', '问题'],
actions.map(function (a) {
return [a.name, ROLE_CN[a.role], TYPE_CN[a.type] || a.type, CAT_CN[a.cat] || a.cat, a.primary ? '是' : '', a.counting === 'ONE_PER_CLICK' ? '一次' : a.counting === 'MANY_PER_CLICK' ? '每次' : a.counting,
a.defValue || '', a.window || '', a.model.replace('GOOGLE_ADS_', '').replace('_', ' '), a.recent, a.base, a.last, a.issues.join('\n')];
}), ['', '', '', '', '', '', FMT.money, FMT.int, '', FMT.dec, FMT.dec, '@', ''],
{ title: '自动标记:' + (acct.autoTag === false ? '未开启 ⚠' : acct.autoTag ? '已开启' : '未知') + ';潜在客户的增强型转化:' + (acct.ecl ? '已开启' : acct.ecl === false ? '未开启' : '未知'),
widths: [220, 80, 120, 120, 70], colors: { col: 12, fn: function (v) { return v ? '#fff1d6' : null; } } });
}
// ======================= 2. 离线导入 =======================
function alertText(al) {
return (al || []).map(function (x) {
var e = x.error || {}, code = values(e).filter(function (v) { return typeof v === 'string'; })[0] || JSON.stringify(e).slice(0, 60);
return code + (x.errorPercentage !== undefined ? '(' + Math.round(num(x.errorPercentage) * 100) + '%)' : '');
}).join(';');
}
function uploads(ss, actions, A) {
var rows = [];
var client = tryQuery('导入汇总', 'SELECT offline_conversion_upload_client_summary.client, offline_conversion_upload_client_summary.status, offline_conversion_upload_client_summary.total_event_count, ' +
'offline_conversion_upload_client_summary.successful_event_count, offline_conversion_upload_client_summary.success_rate, offline_conversion_upload_client_summary.pending_event_count, ' +
'offline_conversion_upload_client_summary.last_upload_date_time, offline_conversion_upload_client_summary.alerts FROM offline_conversion_upload_client_summary') || [];
client.forEach(function (r) {
var s = r.offlineConversionUploadClientSummary || {};
rows.push(['全部', String(s.client || ''), String(s.status || ''), num(s.totalEventCount), num(s.successfulEventCount), s.successRate === undefined ? '' : num(s.successRate),
num(s.pendingEventCount), String(s.lastUploadDateTime || '').slice(0, 16), alertText(s.alerts)]);
if (s.status === 'NEEDS_ATTENTION') A.push(['高', '离线导入', String(s.client || '导入'), '离线导入需要处理', '成功率 ' + pct(s.successRate) + ';' + alertText(s.alerts), '按报错类型修复(常见:GCLID 过期或无效、转化时间早于点击时间、时区格式错误、转化操作名称不对)']);
});
var per = tryQuery('按转化操作', 'SELECT offline_conversion_upload_conversion_action_summary.conversion_action_name, offline_conversion_upload_conversion_action_summary.client, ' +
'offline_conversion_upload_conversion_action_summary.status, offline_conversion_upload_conversion_action_summary.total_event_count, offline_conversion_upload_conversion_action_summary.successful_event_count, ' +
'offline_conversion_upload_conversion_action_summary.pending_event_count, offline_conversion_upload_conversion_action_summary.last_upload_date_time, offline_conversion_upload_conversion_action_summary.alerts ' +
'FROM offline_conversion_upload_conversion_action_summary') || [];
var seen = {};
per.forEach(function (r) {
var s = r.offlineConversionUploadConversionActionSummary || {}, name = String(s.conversionActionName || '');
seen[name] = 1;
var last = String(s.lastUploadDateTime || '').slice(0, 10);
rows.push([name, String(s.client || ''), String(s.status || ''), num(s.totalEventCount), num(s.successfulEventCount), div(num(s.successfulEventCount), num(s.totalEventCount)),
num(s.pendingEventCount), String(s.lastUploadDateTime || '').slice(0, 16), alertText(s.alerts)]);
if (last && last < dayStr(-CONFIG.UPLOAD_STALE_DAYS)) A.push(['高', '离线导入', name, '已经 ' + CONFIG.UPLOAD_STALE_DAYS + ' 天以上没有上传', '最后一次 ' + last, '检查 CRM 导出、Zapier/Make 自动化或定时上传任务是否中断']);
else if (s.status === 'NO_RECENT_UPLOADS') A.push(['中', '离线导入', name, '最近没有上传', '', '确认导入任务还在运行']);
});
// 有离线类型的转化操作,但诊断里没有记录
actions.filter(function (a) { return a.offline && !seen[a.name]; }).forEach(function (a) {
rows.push([a.name, '', '没有导入记录', '', '', '', '', a.last, '']);
if (a.recent === 0) A.push(['中', '离线导入', a.name, '离线转化操作最近 14 天没有转化', '最后一次转化 ' + (a.last || '无'), '确认是否还在上传;不再使用的请移除,避免干扰']);
});
write(ss, '离线导入状态', ['转化操作', '上传方式', '状态', '上传总数', '成功', '成功率', '待处理', '最后上传', '报错'], rows,
['', '', '', FMT.int, FMT.int, FMT.pct, FMT.int, '@', ''], { empty: '没有离线转化导入记录(Google 只保留最近的导入诊断)',
colors: { col: 2, fn: function (v) { return v === 'NEEDS_ATTENTION' ? '#fde2e1' : v === 'EXCELLENT' ? '#e3f4e8' : v ? '#fff1d6' : null; } } });
}
// ======================= 3. 询盘质量漏斗 =======================
function funnel(ss, actions, A) {
var role = {}; actions.forEach(function (a) { role[a.name] = a.role; });
var rows = tryQuery('按系列的转化', 'SELECT campaign.name, segments.conversion_action_name, metrics.all_conversions, metrics.all_conversions_value FROM campaign WHERE ' + range(CONFIG.DAYS)) || [];
var clicks = {};
(tryQuery('点击', 'SELECT campaign.name, metrics.clicks, metrics.cost_micros FROM campaign WHERE ' + range(CONFIG.DAYS) + ' AND metrics.clicks > 0') || []).forEach(function (r) {
var c = clicks[g(r, 'campaign.name')] || (clicks[g(r, 'campaign.name')] = { clicks: 0, cost: 0 });
c.clicks += num(g(r, 'metrics.clicks')); c.cost += money(g(r, 'metrics.costMicros'));
});
var map = {};
rows.forEach(function (r) {
var c = String(g(r, 'campaign.name')), rl = role[String(g(r, 'segments.conversionActionName'))];
if (!rl || rl === 'micro' || rl === 'other') return;
var x = map[c] || (map[c] = { lead: 0, qualified: 0, closed: 0, value: 0 });
x[rl] += num(g(r, 'metrics.allConversions'));
if (rl === 'closed') x.value += num(g(r, 'metrics.allConversionsValue'));
});
var hasQ = values(map).some(function (x) { return x.qualified > 0 || x.closed > 0; });
var tot = { lead: 0, qualified: 0 }; values(map).forEach(function (x) { tot.lead += x.lead; tot.qualified += x.qualified; });
var avgQ = div(tot.qualified, tot.lead);
var data = Object.keys(map).map(function (c) {
var x = map[c], k = clicks[c] || { clicks: 0, cost: 0 };
var qr = div(x.qualified, x.lead), cr = div(x.closed, x.lead), spam = div(x.lead, k.clicks);
var note = [];
if (k.clicks >= 30 && spam !== '' && spam >= CONFIG.SPAM_RATE) note.push('询盘/点击 ' + pct(spam) + ',疑似机器人提交');
if (hasQ && x.lead >= 10 && avgQ && qr !== '' && qr < avgQ * 0.5) note.push('有效询盘比例只有账户平均的 ' + pct(qr / avgQ));
return [c, k.clicks, k.cost, x.lead, x.qualified, x.closed, x.value, div(k.cost, x.lead), div(k.cost, x.qualified), div(k.cost, x.closed), qr, cr, spam, note.join(';')];
}).sort(function (a, b) { return b[2] - a[2]; });
write(ss, '询盘质量', ['广告系列', '点击', '费用', '询盘', '有效询盘', '成交', '成交金额', '每个询盘成本', '每个有效询盘成本', '每单成交成本', '有效率', '成交率', '询盘/点击', '问题'],
data, ['', FMT.int, FMT.money, FMT.dec, FMT.dec, FMT.dec, FMT.money, FMT.money, FMT.money, FMT.money, FMT.pct, FMT.pct, FMT.pct, ''],
{ title: hasQ ? '账户平均有效率 ' + pct(avgQ) : '还没有「有效询盘 / 成交」数据:需要先设置离线转化导入,才能看到询盘质量', empty: '没有询盘数据',
colors: { col: 13, fn: function (v) { return v ? '#fde2e1' : null; } } });
data.filter(function (r) { return r[13]; }).slice(0, 10).forEach(function (r) {
A.push([/机器人/.test(r[13]) ? '高' : '中', '询盘质量', r[0], r[13], '询盘 ' + r2(r[3]) + ',有效 ' + r2(r[4]) + ',费用 ' + r2(r[2]),
/机器人/.test(r[13]) ? '在表单加验证(reCAPTCHA、隐藏字段);检查这个系列的展示位置和搜索字词;把「有效询盘」设为主要转化,让出价不再追求垃圾询盘' : '检查这个系列的搜索字词和地区;考虑用「有效询盘」作为出价目标']);
});
if (!hasQ && tot.lead > 0) A.push(['中', '询盘质量', '全部系列', '只统计了询盘数量,没有询盘质量数据', '最近 ' + CONFIG.DAYS + ' 天 ' + r2(tot.lead) + ' 个询盘',
'在 CRM 里标记有效询盘和成交,通过离线转化导入回传 Google,并把出价目标从「询盘」逐步切换到「有效询盘」']);
}
// ======================= 通用工具函数(毕瑞出海脚本库共用) =======================
var RUN_LOG = [];
var STARTED = new Date();
var FMT = { money: '#,##0.00', int: '#,##0', pct: '0.0%', dec: '#,##0.00', ratio: '0.00' };
var HEAD_BG = '#173a52';
function openSpreadsheet(title) {
if (CONFIG.SPREADSHEET_URL) return SpreadsheetApp.openByUrl(CONFIG.SPREADSHEET_URL);
var ss = SpreadsheetApp.create(title + ' - ' + AdsApp.currentAccount().getName());
log('提示', '表格', '已新建表格,请把这个网址填到 SPREADSHEET_URL:' + ss.getUrl());
Logger.log('★ 新建的表格:' + ss.getUrl());
return ss;
}
function runQuery(q) {
var out = [], it = AdsApp.search(q);
while (it.hasNext()) out.push(it.next());
return out;
}
// 查询失败时返回 null,并把原因写进日志(不让一个查询拖垮整个脚本)
function tryQuery(step, q) {
try { return runQuery(q); }
catch (e) { log('提示', step, '查询失败:' + (e && e.message ? e.message : e)); return null; }
}
function safe(step, fn) {
try { return fn(); }
catch (e) { log('错误', step, (e && e.message) ? e.message : String(e)); return null; }
}
function log(level, step, msg) {
RUN_LOG.push([new Date(), level, step, String(msg)]);
Logger.log(level + ' | ' + step + ' | ' + msg);
}
function timeLeft() { return 28 * 60 * 1000 - (new Date() - STARTED); }
function isPreview() { try { return AdsApp.getExecutionInfo().isPreview(); } catch (e) { return false; } }
function tz() { return AdsApp.currentAccount().getTimeZone(); }
function today() { return Utilities.formatDate(new Date(), tz(), 'yyyy-MM-dd'); }
function dayStr(offsetDays) { return Utilities.formatDate(new Date(Date.now() + offsetDays * 864e5), tz(), 'yyyy-MM-dd'); }
// 截至昨天的 N 天
function range(days) { return ' segments.date BETWEEN "' + dayStr(-Number(days)) + '" AND "' + dayStr(-1) + '" '; }
function between(from, to) { return ' segments.date BETWEEN "' + from + '" AND "' + to + '" '; }
function g(obj, path) {
var parts = path.split('.'), cur = obj;
for (var i = 0; i < parts.length; i++) { if (cur === null || cur === undefined) return ''; cur = cur[parts[i]]; }
return cur === undefined || cur === null ? '' : cur;
}
function num(v) { var x = Number(v); return isNaN(x) ? 0 : x; }
function money(micros) { return num(micros) / 1e6; }
function div(a, b) { return b ? a / b : ''; }
function r2(x) { return x === '' || x === null || x === undefined ? '—' : Math.round(num(x) * 100) / 100; }
function pct(x) { return x === '' || x === null || x === undefined ? '—' : Math.round(num(x) * 1000) / 10 + '%'; }
function values(o) { return Object.keys(o).map(function (k) { return o[k]; }); }
function clean(s) { return String(s || '').replace(/["\\]/g, ''); }
function nameFilter(field) {
var inc = clean(CONFIG.CAMPAIGN_NAME_CONTAINS), exc = clean(CONFIG.CAMPAIGN_NAME_EXCLUDES);
field = field || 'campaign.name';
return (inc ? ' AND ' + field + ' LIKE "%' + inc.replace(/%/g, '') + '%"' : '') +
(exc ? ' AND ' + field + ' NOT LIKE "%' + exc.replace(/%/g, '') + '%"' : '');
}
function metricsOf(r) {
return { impr: num(g(r, 'metrics.impressions')), clicks: num(g(r, 'metrics.clicks')), cost: money(g(r, 'metrics.costMicros')),
conv: num(g(r, 'metrics.conversions')), value: num(g(r, 'metrics.conversionsValue')) };
}
function zero(o) { o.impr = 0; o.clicks = 0; o.cost = 0; o.conv = 0; o.value = 0; return o; }
function addMetrics(t, m) { t.impr += m.impr; t.clicks += m.clicks; t.cost += m.cost; t.conv += m.conv; t.value += m.value; return t; }
function derived(m) {
m.ctr = div(m.clicks, m.impr); m.cpc = div(m.cost, m.clicks); m.cpa = div(m.cost, m.conv);
m.roas = div(m.value, m.cost); m.cvr = div(m.conv, m.clicks); return m;
}
var METRIC_HEAD = ['展示', '点击', '点击率', '费用', '平均点击费用', '转化', '转化价值', '每次转化费用', 'ROAS'];
var METRIC_FMT = [FMT.int, FMT.int, FMT.pct, FMT.money, FMT.money, FMT.dec, FMT.money, FMT.money, FMT.ratio];
function metricCells(m) { derived(m); return [m.impr, m.clicks, m.ctr, m.cost, m.cpc, m.conv, m.value, m.cpa, m.roas]; }
// 以 = + - @ 开头的文字会被表格当成公式,前面加 ' 保持为文字
function cleanRow(row) {
return row.map(function (v) {
if (typeof v === 'string' && /^[=+\-@]/.test(v)) return "'" + v;
if (typeof v === 'number' && !isFinite(v)) return '';
return v === undefined || v === null ? '' : v;
});
}
// 写一张表:清空后写表头和数据,先设格式再写值
function write(ss, name, header, data, formats, opt) {
opt = opt || {};
var sh = ss.getSheetByName(name) || ss.insertSheet(name);
sh.clear();
var r0 = 1;
if (opt.title) { sh.getRange(1, 1).setValue(opt.title).setFontWeight('bold'); r0 = 2; }
var max = opt.max || 20000;
if (data.length > max) { log('提示', name, '共 ' + data.length + ' 行,只写入前 ' + max + ' 行'); data = data.slice(0, max); }
var body = data.length ? data.map(cleanRow) : [header.map(function (_, i) { return i === 0 ? (opt.empty || '(没有数据)') : ''; })];
if (formats && data.length) formats.forEach(function (f, i) { if (f) sh.getRange(r0 + 1, i + 1, data.length, 1).setNumberFormat(f); });
sh.getRange(r0, 1, 1, header.length).setValues([header]).setFontWeight('bold').setBackground(HEAD_BG).setFontColor('#ffffff');
sh.getRange(r0 + 1, 1, body.length, header.length).setValues(body);
sh.setFrozenRows(r0);
if (opt.widths) opt.widths.forEach(function (w, i) { if (w) sh.setColumnWidth(i + 1, w); });
if (opt.wrap && data.length) sh.getRange(r0 + 1, 1, data.length, header.length).setWrap(true).setVerticalAlignment('top');
if (opt.colors && data.length) {
var col = opt.colors.col;
sh.getRange(r0 + 1, col + 1, data.length, 1).setBackgrounds(data.map(function (r) { return [opt.colors.fn(r[col], r)]; }));
}
return sh;
}
function levelColor(v) { return v === '高' ? '#fde2e1' : v === '中' ? '#fff1d6' : v === '低' ? '#eef1f5' : null; }
// 行动清单:[优先级, 类别, 对象, 发现, 数据, 建议]
function writeActions(ss, A, emptyText) {
var rank = { '高': 0, '中': 1, '低': 2 };
A.sort(function (a, b) { return (rank[a[0]] === undefined ? 3 : rank[a[0]]) - (rank[b[0]] === undefined ? 3 : rank[b[0]]); });
if (!A.length) A.push(['—', '—', '—', emptyText || '没有发现需要处理的问题', '', '继续定期运行']);
return write(ss, '行动清单', ['优先级', '类别', '对象', '发现', '数据', '建议'], A, null,
{ wrap: true, widths: [70, 90, 300, 240, 260, 440], colors: { col: 0, fn: levelColor } });
}
function ensureSheet(ss, name, header) {
var sh = ss.getSheetByName(name);
if (!sh) {
sh = ss.insertSheet(name);
sh.getRange(1, 1, 1, header.length).setValues([header]).setFontWeight('bold').setBackground(HEAD_BG).setFontColor('#ffffff');
sh.setFrozenRows(1);
}
return sh;
}
// 历史记录表:同一天重复运行时覆盖当天,再追加到末尾;keepDays 天以前的记录删除
function upsertDay(sh, date, rows, keepDays) {
var last = sh.getLastRow();
if (last > 1) {
var v = sh.getRange(2, 1, last - 1, 1).getValues();
var first = -1, count = 0;
for (var i = v.length - 1; i >= 0; i--) { if (dateStr(v[i][0]) === date) { first = i; count++; } else if (count) break; }
if (count) sh.deleteRows(first + 2, count);
}
if (rows.length) {
var start = sh.getLastRow() + 1;
sh.getRange(start, 1, rows.length, 1).setNumberFormat('@');
sh.getRange(start, 1, rows.length, rows[0].length).setValues(rows.map(cleanRow));
}
if (keepDays && sh.getLastRow() > 1) {
var cutoff = Utilities.formatDate(new Date(Date.now() - keepDays * 864e5), tz(), 'yyyy-MM-dd');
var d = sh.getRange(2, 1, sh.getLastRow() - 1, 1).getValues(), n = 0;
while (n < d.length && dateStr(d[n][0]) < cutoff) n++;
if (n) sh.deleteRows(2, n);
}
}
function dateStr(v) {
if (v instanceof Date) return Utilities.formatDate(v, tz(), 'yyyy-MM-dd');
return String(v || '').slice(0, 10);
}
// 隐藏的状态表:保存键值,用于去重提醒、断点续跑等
function stateGet(ss, key) {
var sh = ss.getSheetByName('_状态'); if (!sh || sh.getLastRow() < 1) return '';
var v = sh.getRange(1, 1, sh.getLastRow(), 2).getValues();
for (var i = 0; i < v.length; i++) if (v[i][0] === key) return String(v[i][1]);
return '';
}
function stateSet(ss, key, val) {
var sh = ss.getSheetByName('_状态') || ss.insertSheet('_状态');
var n = sh.getLastRow(), v = n ? sh.getRange(1, 1, n, 1).getValues() : [];
for (var i = 0; i < v.length; i++) if (v[i][0] === key) { sh.getRange(i + 1, 2).setNumberFormat('@').setValue(String(val)); sh.hideSheet(); return; }
sh.getRange(n + 1, 1, 1, 2).setNumberFormat('@').setValues([[key, String(val)]]);
sh.hideSheet();
}
function writeGuide(ss, title, version, rows) {
var acct = AdsApp.currentAccount();
var all = [[title + '(' + version + ')', ''],
['账户', acct.getName() + '(' + acct.getCustomerId() + ')'],
['运行时间', Utilities.formatDate(STARTED, tz(), 'yyyy-MM-dd HH:mm') + (isPreview() ? '(预览模式)' : '')],
['', '']].concat(rows).concat([['', ''], ['脚本库', 'https://berightdigital.com/google-ads-scripts/']]);
var sh = ss.getSheetByName('使用说明') || ss.insertSheet('使用说明');
sh.clear();
sh.getRange(1, 1, all.length, 2).setValues(all.map(cleanRow));
sh.getRange(1, 1).setFontSize(14).setFontWeight('bold');
sh.setColumnWidth(1, 220); sh.setColumnWidth(2, 720);
sh.getRange(1, 2, all.length, 1).setWrap(true);
}
function writeLog(ss) {
log('信息', '完成', '用时 ' + Math.round((new Date() - STARTED) / 1000) + ' 秒');
var sh = ss.getSheetByName('运行日志') || ss.insertSheet('运行日志');
sh.clear();
var rows = [['时间', '级别', '步骤', '内容']].concat(RUN_LOG.map(cleanRow));
sh.getRange(1, 1, rows.length, 4).setValues(rows);
sh.getRange(1, 1, 1, 4).setFontWeight('bold');
sh.setColumnWidth(4, 700);
}
function orderSheets(ss, order) {
order.forEach(function (n, i) {
var sh = ss.getSheetByName(n);
if (sh) { ss.setActiveSheet(sh); ss.moveActiveSheet(i + 1); }
});
ss.getSheets().forEach(function (s) { if (/^_/.test(s.getName())) s.hideSheet(); });
['Sheet1', '工作表1', 'Sheet 1'].forEach(function (n) {
var sh = ss.getSheetByName(n);
if (sh && ss.getSheets().length > 1 && sh.getLastRow() === 0) ss.deleteSheet(sh);
});
var first = ss.getSheetByName(order[1] || order[0]);
if (first) ss.setActiveSheet(first);
}
// 发 HTML 邮件:rows 为二维数组(第一行是表头)
function mail(subject, intro, rows, ss) {
if (!CONFIG.EMAIL) return;
var h = function (s) { return String(s === undefined || s === null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); };
var table = rows && rows.length > 1 ? '<table cellpadding="6" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;font-size:13px">' +
rows.map(function (r, i) {
return '<tr style="' + (i === 0 ? 'background:' + HEAD_BG + ';color:#fff' : 'border-top:1px solid #ddd;vertical-align:top') + '">' +
r.map(function (c, j) { var bg = i > 0 && j === 0 ? levelColor(c) : null; return (i === 0 ? '<th>' : '<td' + (bg ? ' style="background:' + bg + '"' : '') + '>') + h(typeof c === 'number' ? r2(c) : c) + (i === 0 ? '</th>' : '</td>'); }).join('') + '</tr>';
}).join('') + '</table>' : '';
var html = '<div style="font-family:Arial,sans-serif;font-size:13px;color:#222"><p>' + h(intro).replace(/\n/g, '<br>') + '</p>' + table +
(ss ? '<p><a href="' + ss.getUrl() + '">打开完整报告</a></p>' : '') + '</div>';
var plain = intro + '\n\n' + (rows || []).slice(1).map(function (r) { return r.join(' | '); }).join('\n') + (ss ? '\n\n完整报告:' + ss.getUrl() : '');
MailApp.sendEmail({ to: CONFIG.EMAIL, subject: subject, body: plain, htmlBody: html });
log('信息', '邮件', '已发送到 ' + CONFIG.EMAIL);
}
搭配使用的脚本
- 目标 CPA 与盈亏平衡脚本:按询盘成交率和订单金额算出每个询盘最多能花多少钱。
- 账户异常监控脚本:转化操作突然没数据时每小时提醒。
- 国家地区与语言表现分析脚本:垃圾询盘往往集中在某些国家。
- 更多脚本见 Google Ads 脚本库。
毕瑞出海BerightDigital微信扫码关注,视频和文章同步更新