Tableau REST API と Google Apps Script を使って Tableau Cloud のビューデータを Google Drive に自動連携してみた

Tableau REST API と Google Apps Script を使って Tableau Cloud のビューデータを Google Drive に自動連携してみた

2026.05.19

こんにちは、ikumiです。

Tableauのサブスクリプション機能では、通常PDFなどのイメージ画像しかメール配信ができません。しかし、Tableau Cloud で作成したダッシュボードビューのデータを定期的に CSV でエクスポートして共有したい、という場面があります。

今回は、 Tableau REST APIGoogle Apps Script(GAS) を使用して、Tableau Cloud の通常のシートビューのデータを API で取得し、そのまま Google Drive の指定フォルダに CSV を自動保存する仕組みを試してみました。

参考:Tableau REST API:query_view_data

技術的アプローチ

Google Apps Script(GAS)から Tableau REST API を呼び出す構成です。

処理の流れ:

  1. GAS から Tableau REST API にサインインしてトークンを取得
  2. 取得したトークンを使ってビューの CSV データを取得
  3. 取得した CSV を Google Drive の指定フォルダに保存

検証環境について

  • Tableau Cloud: 取得したいビューを作成済み
  • Tableau Cloud 権限: 対象ビューへのアクセス権があり、個人用アクセストークンを取得できる
  • Google アカウント: Google Drive・Google Apps Script が利用できること

事前準備

1. Tableau Cloud で PAT(パーソナルアクセストークン)を発行する

Tableau Cloud の右上アカウントメニューから マイアカウントの設定 を開き、パーソナルアクセストークン セクションでトークンを作成します。

tableau-restapi-gas-google-drive-export-no1
tableau-restapi-gas-google-drive-export-no2

表示される トークン名トークンシークレット をメモしておきます。シークレットはこの画面でしか確認できないため、必ずコピーして保管してください。

2. ビューの ID(LUID)を確認する

Tableau REST API ではビューを LUID(一意のID)で指定します。
※後述の GAS の findViewByName() ヘルパー関数を実行することで、ビュー名で絞り込んで LUID を確認できます。

3. Google Drive のフォルダ ID を確認する

CSV を保存したい Google Drive のフォルダを開き、URL の末尾の文字列をコピーします。

https://drive.google.com/drive/folders/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                                        ↑ この部分がフォルダID

4. Google Apps Script プロジェクトを作成する

Google Drive 上で 新規その他Google Apps Script を選択してプロジェクトを作成します。

tableau-restapi-gas-google-drive-export-no3

試してみた

1. GAS にコードを貼り付ける

Google Apps Script エディタの中身を全選択(Ctrl+A)して削除し、以下のコードを貼り付けます。冒頭の CONFIG の値を自身の環境に合わせて書き換えてください。

GASコードを展開する
// ===== 設定値(★要変更★の箇所を書き換えてください) =====
const CONFIG = {
  TABLEAU_URL: 'https://prod-apnortheast-a.online.tableau.com', // ★要変更★ Tableau Cloud の URL(末尾スラッシュなし)
  API_VERSION: '3.28',
  SITE_CONTENT_URL: 'your-site-name',  // ★要変更★ URL の /site/〇〇/ の〇〇
  PAT_NAME: 'your-pat-name',           // ★要変更★ PAT のトークン名
  PAT_SECRET: 'your-pat-secret',       // ★要変更★ PAT のトークンシークレット
  VIEW_ID: 'your-view-id',             // ★要変更★ ビューの LUID(後述の手順で確認)
  DRIVE_FOLDER_ID: 'your-folder-id',   // ★要変更★ Google Drive フォルダの ID
  FILE_NAME: 'tableau_export.csv',     // 保存するファイル名
};

// ===== メイン処理(これを実行する) =====
function exportTableauViewToGoogleDrive() {
  // Step 1: Tableau Cloud にサインイン
  const auth = signIn();
  if (!auth.token) {
    Logger.log('サインイン失敗。PAT_NAME・PAT_SECRET・SITE_CONTENT_URL を確認してください');
    return;
  }
  Logger.log('サインイン成功');

  // Step 2: ビューの CSV データを取得
  const blob = getViewData(auth.token, auth.siteId);
  Logger.log('CSV データ取得成功');

  // Step 3: Google Drive に保存
  saveToGoogleDrive(blob);
  Logger.log('Google Drive への保存完了!');
}

// Tableau Cloud にサインインしてトークンを取得する
function signIn() {
  const url = `${CONFIG.TABLEAU_URL}/api/${CONFIG.API_VERSION}/auth/signin`;
  const payload = `<tsRequest>
    <credentials personalAccessTokenName="${CONFIG.PAT_NAME}"
                 personalAccessTokenSecret="${CONFIG.PAT_SECRET}">
      <site contentUrl="${CONFIG.SITE_CONTENT_URL}" />
    </credentials>
  </tsRequest>`;

  const response = UrlFetchApp.fetch(url, {
    method: 'post',
    contentType: 'application/xml',
    payload: payload,
    muteHttpExceptions: true,
  });

  const text = response.getContentText();
  const tokenMatch = text.match(/token="([^"]+)"/);
  const siteIdMatch = text.match(/site id="([^"]+)"/);

  if (!tokenMatch || !siteIdMatch) {
    Logger.log('認証エラー: ' + text);
    return {};
  }

  return { token: tokenMatch[1], siteId: siteIdMatch[1] };
}

// ビューの CSV データを blob で取得する
function getViewData(token, siteId) {
  const url = `${CONFIG.TABLEAU_URL}/api/${CONFIG.API_VERSION}/sites/${siteId}/views/${CONFIG.VIEW_ID}/data`;

  const response = UrlFetchApp.fetch(url, {
    method: 'get',
    headers: { 'X-Tableau-Auth': token },
    muteHttpExceptions: true,
  });

  return response.getBlob(); // 文字化け防止のためバイナリのまま取得
}

// CSV データを Google Drive の指定フォルダに保存する
function saveToGoogleDrive(blob) {
  const folder = DriveApp.getFolderById(CONFIG.DRIVE_FOLDER_ID);

  // 同名ファイルが既にある場合はゴミ箱へ移動
  const existingFiles = folder.getFilesByName(CONFIG.FILE_NAME);
  while (existingFiles.hasNext()) {
    existingFiles.next().setTrashed(true);
  }

  blob.setName(CONFIG.FILE_NAME);
  folder.createFile(blob); // blob のまま保存することで文字化けを防ぐ
}

// ビューの LUID を名前で検索するヘルパー関数
function findViewByName() {
  const auth = signIn();
  const url = `${CONFIG.TABLEAU_URL}/api/${CONFIG.API_VERSION}/sites/${auth.siteId}/views?filter=name:eq:ビュー名`;
  // ↑ 「ビュー名」を実際のシート名に変えて実行してください

  const response = UrlFetchApp.fetch(url, {
    method: 'get',
    headers: { 'X-Tableau-Auth': auth.token },
  });

  Logger.log(response.getContentText());
}

2. ビューの LUID を確認する

findViewByName 関数のコメント内「ビュー名」を取得したいシート名に書き換えてから実行します。

tableau-restapi-gas-google-drive-export-no4

ログに XML 形式でビューの情報が出力されます。view id="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" の部分が LUID ですので、確認した LUID を CONFIGVIEW_ID に設定します。
tableau-restapi-gas-google-drive-export-no5

3. メイン関数を実行して動作確認する

exportTableauViewToGoogleDrive 関数を選択して実行します。初回実行時は Google Drive へのアクセス許可を求めるダイアログが表示されるので、許可してください。
実行後、ログに「Google Drive への保存完了!」と表示されれば問題ありません。

tableau-restapi-gas-google-drive-export-no6

Google Drive の指定フォルダに CSV ファイルが作成されており、中身が正しいデータになっていれば完了です。

▼ダッシュボード上のデータ
tableau-restapi-gas-google-drive-export-no7
▼出力結果
tableau-restapi-gas-google-drive-export-no8
tableau-restapi-gas-google-drive-export-no9

出力結果を確認すると、内容は正しいもののメジャーが縦持ちになっていてフォーマットが崩れていました・・。

おまけ. 出力結果のピボット

前段で起きていたフォーマット崩れの課題に対して、今回はGASでデータ整形をすることにしましたので、ビューデータ取得部分を以下とおりに修正することで、正しいフォーマットで出力することができました。

GASコードを展開する
// ===== ビューデータ取得 =====
function getViewData(token, siteId) {
  const url = `${CONFIG.TABLEAU_URL}/api/${CONFIG.API_VERSION}/sites/${siteId}/views/${CONFIG.VIEW_ID}/data`;

  const response = UrlFetchApp.fetch(url, {
    method: 'get',
    headers: { 'X-Tableau-Auth': token },
    muteHttpExceptions: true,
  });

  return response.getBlob();
}

// ===== メジャーを横持に変換する =====
function pivotMeasures(csvText) {
  const rows = Utilities.parseCsv(csvText);
  const headers = rows[0];

  const measureNameIdx = headers.findIndex(h => h.includes('メジャー ネーム'));
  const measureValueIdx = headers.findIndex(h => h.includes('メジャー バリュー'));

  // メジャー列が見つからない場合はそのまま返す
  if (measureNameIdx === -1 || measureValueIdx === -1) {
    Logger.log('メジャー列が見つからないため変換をスキップします');
    return csvText;
  }

  const keyIndices = headers.map((_, i) => i).filter(i => i !== measureNameIdx && i !== measureValueIdx);

  // ユニークなメジャー名を取得(出現順を保持)
  const measureNames = [...new Set(rows.slice(1).map(r => r[measureNameIdx]))];

  // キーごとにデータを集約
  const pivotMap = new Map();
  rows.slice(1).forEach(row => {
    const key = keyIndices.map(i => row[i]).join('\t');
    if (!pivotMap.has(key)) pivotMap.set(key, {});
    pivotMap.get(key)[row[measureNameIdx]] = row[measureValueIdx];
  });

  // 出力ヘッダー・行を組み立て
  const keyHeaders = keyIndices.map(i => headers[i]);
  const outputHeaders = [...keyHeaders, ...measureNames];

  const outputRows = [outputHeaders];
  pivotMap.forEach((measures, key) => {
    const keyValues = key.split('\t');
    const row = [...keyValues, ...measureNames.map(m => measures[m] || '')];
    outputRows.push(row);
  });

  return outputRows.map(r => r.map(v => `"${v}"`).join(',')).join('\n');
}

tableau-restapi-gas-google-drive-export-no10

最後に

Tableau REST API と Google Apps Script を組み合わせて、Tableau Cloud のビューデータを Google Drive に自動連携する仕組みを作ってみました。

GAS のコードは設定値(CONFIG)を書き換えるだけで動く形にしているため、API の知識がなくてもセットアップできると思います。また、トリガーを設定すれば毎日決まった時刻に自動実行されるため、定期的なデータ共有の手間を大幅に減らせます。


Tableauの導入支援はクラスメソッドにおまかせください

クラスメソッドでは、お客様のTableau導入時のオンボーディング(各種トレーニング、ナレッジ共有など導入ノウハウの提供)を行っております。データ分析環境の豊富な導入実績を持つクラスメソッドが、お客様のTableau導入を支援いたします。
Tableauの導入支援サービスを見る

この記事をシェアする

関連記事