Facebook(Meta)広告のデータをスプレッドシートに出す方法|GASで毎朝書き出すコードと、購入数が二重になる落とし穴

「Facebook広告 スプレッドシート」で調べると、向きの違う3つの作業の話が同じ画面に並びます。広告の成果をシートへ出す話、リード獲得広告のリードをシートへ送る話、シートで管理している成約をMetaへ返す話です。どれを探しているかで、使う仕組みも必要な権限もまるで違います。
このうち広告の成果をシートへ出す作業は、Google Apps Script(GAS)で毎朝自動にできます。ただ、動かしてみると購入数が広告マネージャより多く出ることがあります。コードの書き間違いを疑いたくなるところですが、原因はMetaが返すデータの形の側にあります。3つの向きを切り分けたうえで、成果を書き出すコードと、数字が合わないときに確かめる順番までをまとめます。
Facebook広告とスプレッドシートをつなぐ3つの方向
広告の成果をシートに出すなら、Marketing APIから取る
毎日見る表に成果を出すなら、MetaのMarketing API(広告の数字を外から取り出す窓口)から取るのが基本です。表示回数・クリック・費用・購入といった数字をスプレッドシートに置く方法は、広告マネージャから書き出したファイルを貼るか、Marketing APIから取るかの2系統で、GASで自作するのも外部のコネクタを契約するのも、後者の取り方の違いにすぎません。
広告マネージャの「広告レポート」は .xlsx や .csv で書き出せるため、週に1回の報告なら貼り付けで足ります。負担になるのは、毎朝の確認にシートを使う場合や、複数の人が同じシートを見る場合です。貼る人が休むと表が止まり、貼り忘れた日だけ数字が抜けます。毎日見る表なら、人の手を通さずにAPIから取る作りに寄せるのが向いています。
| 向き | 動くデータ | 主な手段 |
|---|---|---|
| Meta → シート(成果) | 表示回数・クリック・費用・購入などの集計値 | 広告レポートの書き出し、GAS、外部のコネクタ |
| Meta → シート(リード) | リード獲得広告のフォームに入力された氏名・連絡先 | Meta Business SuiteのCRM連携、外部の自動化ツール |
| シート → Meta | 商談化・成約などのオフラインの成果 | コンバージョンAPI(スプレッドシートを接続する設定) |
リードやオフラインの成約は、レポートとは別の仕組みでつなぐ
リード獲得広告のリードをシートへ送る作業と、シートの成約をMetaへ返す作業は、成果の集計とは扱うデータも権限も違います。
リードは氏名や電話番号を含む個人情報です。シートに流す前に、誰がそのシートを開けるか、いつ消すかを決めておかないと、共有リンク1本で外に出ます。逆向きの成約の返送は、Metaのビジネスヘルプに、GoogleスプレッドシートをMetaに接続してコンバージョンAPIで送る設定の案内があり、CRMで管理しているリードがどこまで進んだかをMetaへ送れます。広告の配信の学習に効くのはこの返送のほうで、シートに数字を集める仕組みではありません。
ADminiのMeta広告連携なら、スクリプトを書かずに前日までの成果が毎朝表にそろいます。ADminiなら無料で始められます。無料で試してみる →
GASでFacebook広告の成果を毎朝シートに書き出す手順
準備は「アプリ」「システムユーザー」「ads_readの権限」の3つ
GASから広告の数字を取るには、Meta for Developersで作るアプリと、そのアプリを通して広告アカウントを読むためのアクセストークンが要ります。トークンは、ビジネス設定のシステムユーザー(人ではなく仕組みのために作るアカウント)で発行するのが長く止まらない作り方です。
流れは、アプリを作る、ビジネス設定でシステムユーザーを作って広告アカウントとアプリを割り当てる、ads_read(広告の数字を読むだけの権限)でトークンを発行する、の順です。Metaの開発者ドキュメントは、システムユーザーのアクセストークンは期限切れにならないと説明しています。個人のアカウントで発行した長期のトークンは約60日で切れるため、それで組むと2か月後のある朝に表が止まります。トークンはシートのセルに書かず、GASの「スクリプト プロパティ」に入れるのも忘れたくない点です。セルに書くと、シートを開ける人全員が広告アカウントを読める鍵を持つことになります。

直近7日を毎朝上書きするコードと、トリガーの設定
次のコードは、キャンペーン別・日別の成果を直近7日分取り直し、シートの同じ期間だけを書き換えます。前日の1行を足していく作りにしないのは、Metaの成果があとから増えるためです。スクリプト プロパティには、トークンを META_TOKEN、act_ から始まる広告アカウントIDを AD_ACCOUNT_ID という名前で入れておきます。
const API_VERSION = 'v26.0'; // Metaの変更履歴(changelog)で使える版を確かめる
const SHEET_NAME = 'meta_raw';
const DAYS = 7; // 毎回取り直す日数
const ACTION = 'omni_purchase'; // 広告マネージャの「購入」と突き合わせて決める
const HEADER = ['日付', 'キャンペーン', '表示回数', 'クリック', '費用', '購入', '購入の値'];
function updateMetaAds() {
const props = PropertiesService.getScriptProperties();
const token = props.getProperty('META_TOKEN');
const account = props.getProperty('AD_ACCOUNT_ID'); // act_から始まるID
const tz = Session.getScriptTimeZone();
const day = 24 * 60 * 60 * 1000;
const fmt = function (d) { return Utilities.formatDate(d, tz, 'yyyy-MM-dd'); };
const key = function (v) { return v instanceof Date ? fmt(v) : String(v); };
const pick = function (list) {
const hit = (list || []).filter(function (a) { return a.action_type === ACTION; })[0];
return hit ? Number(hit.value) : 0;
};
const since = fmt(new Date(Date.now() - DAYS * day));
const until = fmt(new Date(Date.now() - day));
let url = 'https://graph.facebook.com/' + API_VERSION + '/' + account + '/insights' +
'?level=campaign&time_increment=1&limit=500' +
'&fields=campaign_name,impressions,clicks,spend,actions,action_values' +
'&time_range=' + encodeURIComponent(JSON.stringify({ since: since, until: until })) +
'&access_token=' + token;
const fresh = [];
while (url) {
const res = JSON.parse(UrlFetchApp.fetch(url, { muteHttpExceptions: true }).getContentText());
if (res.error) throw new Error(res.error.message); // 失敗はトリガーの通知メールで気づける
res.data.forEach(function (r) {
fresh.push([r.date_start, r.campaign_name, Number(r.impressions || 0), Number(r.clicks || 0),
Number(r.spend || 0), pick(r.actions), pick(r.action_values)]);
});
url = res.paging && res.paging.next;
}
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME);
const last = sheet.getLastRow();
const kept = last > 1 ? sheet.getRange(2, 1, last - 1, HEADER.length).getValues().filter(function (row) {
return key(row[0]) < since || key(row[0]) > until;
}) : [];
const rows = kept.concat(fresh).sort(function (a, b) {
return key(a[0]) < key(b[0]) ? -1 : key(a[0]) > key(b[0]) ? 1 : 0;
});
sheet.clearContents();
sheet.getRange(1, 1, 1, HEADER.length).setValues([HEADER]);
if (rows.length) sheet.getRange(2, 1, rows.length, HEADER.length).setValues(rows);
}
シートには meta_raw という名前のタブを作っておきます。動いたら、GASの「トリガー」から時間主導型・日付ベースで毎朝1回(例: 午前5時〜6時)を設定します。見落としやすいのは日付の境目で、広告アカウントのタイムゾーンと、スクリプト(プロジェクトの設定)・スプレッドシート(ファイル→設定)のタイムゾーンをそろえておく必要があります。ずれていると、取り直す7日の端が1日ずれ、毎朝1日分だけ古い数字が残ります。トークン切れなどで7日より前に抜けた期間ができたときは、DAYS を一時的に広げて1回だけ実行すれば埋まります。Google広告で同じ作りを組む場合のコードは、Google広告スクリプトで直近14日を上書きする手順にあります。
購入数が広告マネージャと合わないときに確かめる3つのこと
actionsの中には、同じ購入が複数の種類で入っている
APIの成果は actions という一覧で返り、同じ購入が omni_purchase や offsite_conversion.fb_pixel_purchase など複数の種類で並びます。名前に purchase を含む種類をすべて足すと、1件の購入を2〜3回数えることになります。冒頭の「購入数が多すぎる」の正体は、たいていこれです。
次の表は、ある1日のキャンペーンの actions を再現用のサンプルデータで示したものです(実在のアカウントの数字ではありません)。広告マネージャの「購入」列は12件でも、purchase を含む種類を合計すると35件になります。
| action_type | value |
|---|---|
| omni_purchase | 12 |
| purchase | 12 |
| offsite_conversion.fb_pixel_purchase | 11 |
| purchase を含む種類の合計 | 35(広告マネージャの「購入」は12) |
どの種類が広告マネージャの「購入」と一致するかは、ピクセルだけで計測しているか、Conversions APIやアプリの購入も送っているかで変わり得ます。決め方は単純で、1日分だけ取って actions の中身をログに出し、広告マネージャで同じ日・同じアトリビューション設定の「購入」と一致する種類を1つ選んで、コードの ACTION に書きます。種類は足さずに1つ選び、選んだ理由を別のタブに書き残すと、担当が替わっても数字の意味が変わりません。
7日の取り直しで足りても、月の締めの数字は28日まで動く
Metaの開発者ドキュメントでは、成果の数字は15分ごとに更新され、計上から28日たつと変わらなくなるとされています。毎朝7日分を取り直す作りは日々の確認には十分ですが、28日より手前の数字は、まだ動く余地を残しています。
7日という幅は、Conversions APIで送る成果が発生から7日以内ならあとから届く仕様に合わせたものです。ふだんの差はこの幅で消えます。ところが月次報告では、前月末の数字を翌月の頭に締めるため、28日の手前で数字を固定することになります。「7日取り直していれば、締めた数字は広告マネージャといつまでも一致する」とは言えません。締めた日に前月分だけ取り直し、報告には何日時点の数字かを書き添えると、あとで食い違いを聞かれても説明がつきます。広告セットごとにアトリビューション設定が違う場合も、同じ「購入」に数え方の違う数字が混ざるので、先に設定をそろえておきます。

毎朝止まる原因は、トークン・実行時間・APIの版の3つ
自作の仕組みが止まるときの原因は、ほぼ次の3つに収まります。止まったことに気づけるよう、トリガーの失敗通知はメールで受け取る設定にしておきます。
- トークン:個人のトークンは約60日で切れる。システムユーザーのトークンでも、広告アカウントの割り当てを外すと読めなくなる
- 実行時間:GASは1回の実行が6分までで、広告単位・日別で長い期間を取ると届くことがある。期間を分けるか、キャンペーン単位にする
- APIの版:Metaは版ごとに提供期限を設けている。期限を過ぎた版を書いたままでは、同じ動きが続く保証はない。年に1〜2回は変更履歴で使っている版を確かめる
3つに共通するのは、書いた人が見張り続けないと止まる点です。ADmini(アドミニ)のMeta広告連携は、トークンの更新と毎日の取り直しを任せる選択肢です。広告アカウント、集計の単位(キャンペーン・広告セット・広告)、成果として数えるアクションを選んでつなぐと、1日1回の自動更新のたびに前日までの数字を取り直し、Google広告やLINEヤフー広告と同じ画面に並びます。数える種類を最初に1つ選ぶ点は、上のコードの ACTION と同じ考え方です。一方で、連携で取るのは表示回数・クリック・費用・コンバージョン・コンバージョン値で、リーチやフリークエンシー、動画の再生指標は取りません。これらを毎日見るなら、広告レポートの書き出しと併用することになります。
まとめ
「Facebook広告 スプレッドシート」の作業は、成果をシートへ出す、リードをシートへ送る、成約をMetaへ返す、の3つに分かれます。成果を出すならMarketing APIから取り、GASならシステムユーザーのトークンで直近7日を毎朝上書きします。購入数が多すぎるときは actions の種類を足していないか、月の締めでは28日の手前で固定していないかを確かめます。表をどう組むかは貼る・計算する・見せるの3シートに分けるテンプレートが、レポート全体の流れは広告レポートの作り方が参考になります。
自作の仕組みが壊れるときは、たいてい静かです。トークンが切れた朝も表は前日のまま残り、7日たってから数字の抜けに気づきます。購入の種類を足していた表は、2〜3倍の数字のまま月次報告に載り、翌月に広告マネージャと突き合わせた人が初めて首をかしげます。スクリプトで組むか、ADmini(アドミニ)のような連携に任せるかに関わらず、数える種類と取り直す期間を決めて書き残すのは人の仕事です。まず今週やることは、1日分だけ取って actions に並ぶ種類を確かめ、広告マネージャの「購入」と一致する種類を1つ決めることです。
ADminiのMeta広告連携なら、前日までの数字を毎朝取り直して他の媒体と1画面に並べられます。ADminiなら無料で始められます。無料で試してみる →
よくある質問
Facebook広告のデータをスプレッドシートに自動で出す方法はありますか?
あります。MetaのMarketing APIから成果を取り、Google Apps Script(GAS)でシートに書き込む方法と、外部のコネクタで転記する方法があります。GASで組む場合は、Meta for Developersのアプリと、ads_read の権限を持つアクセストークンが必要です。週に1回の報告だけなら、広告マネージャの「広告レポート」を .xlsx や .csv で書き出して貼る方法でも足ります。
GASでMeta広告のデータを取るとき、アクセストークンの期限はどうすればいいですか?
ビジネス設定のシステムユーザーでトークンを発行するのが確実です。Metaの開発者ドキュメントでは、システムユーザーのアクセストークンは期限切れにならないと説明されています。個人のアカウントで発行した長期のトークンは約60日で切れるため、定期実行には向きません。トークンはシートのセルではなく、GASのスクリプト プロパティに保存します。
APIで取ったMeta広告の購入数が広告マネージャより多いのはなぜですか?
actions の一覧に、同じ購入が omni_purchase や offsite_conversion.fb_pixel_purchase など複数の種類で入っているためです。purchase を含む種類をすべて足すと、1件の購入を2〜3回数えます。1日分を取って中身を確かめ、広告マネージャで同じ日・同じアトリビューション設定の「購入」と一致する種類を1つだけ使います。
リード獲得広告のリードをスプレッドシートに送ることはできますか?
できますが、広告の成果を集計する仕組みとは別の設定になります。Meta Business SuiteのCRM連携や外部の自動化ツールで、フォームに入力されたリードをシートへ送ります。リードは氏名や連絡先を含む個人情報のため、シートを開ける人と保存期間を決めてからつなぎます。
