Clicky

GoogleスプレッドシートとGASでX自動投稿する方法|毎日1件ずつ予約投稿する手順

GAS(GoogleAppsScript)
GAS(GoogleAppsScript)
この記事は約21分で読めます。

※記事中に広告情報を含みます。

スキルを手に入れた時、人は強くなれる。
Youtubeでスキルアップを始める 電子書籍でスキルアップを始める

Xの投稿を毎日続けたいと思っていても、手動で投稿し続けるのは意外と大変です。投稿する内容は決まっているのに、忙しくて投稿を忘れてしまうこともあります。

そこで便利なのが、GoogleスプレッドシートとGoogle Apps Scriptを使ったX自動投稿です。スプレッドシートに投稿文を一覧で用意しておき、Google Apps Scriptで「予約済み」の投稿を1件ずつXへ投稿する仕組みを作ります。

この方法なら、投稿文の管理、投稿状況の確認、エラー確認までスプレッドシート上で行えます。

この記事では、初心者の方でも順番に設定できるように、X APIの準備からGASコードの貼り付け、定期実行の設定まで詳しく解説します。

X自動投稿の完成イメージ

今回作る仕組みは、とてもシンプルです。

Googleスプレッドシートに投稿文を用意します。

投稿したい文章のステータスを「予約済み」にしておくと、Google Apps Scriptがその行を読み取り、Xへ投稿します。

投稿が成功すると、ステータスが「投稿済み」に変わります。

エラーが出た場合は、ステータスが「エラー」になり、エラー内容も記録できます。

全体の流れは次の通りです。

手順内容
1スプレッドシートに投稿文を用意する
2X DeveloperでAPIキーを取得する
3Apps Scriptにコードを貼り付ける
4APIキーを設定する
5テスト投稿する
6時間主導型トリガーを設定する
7毎日自動でXへ投稿する

この仕組みを一度作っておけば、あとはスプレッドシートに投稿文を追加していくだけで運用できます。

事前に用意するもの

X自動投稿を作るには、次のものを用意します。

必要なもの用途
GoogleアカウントスプレッドシートとApps Scriptを使うため
Googleスプレッドシート投稿文を管理するため
X DeveloperアカウントX APIを使うため
X APIキーGASからXへ投稿するため

すでにスプレッドシートを作成している場合は、そのシートをそのまま使えます。

スプレッドシートの列を作る

まず、作成済みのGoogleスプレッドシートを開きます。

1行目に、次の見出しを作成してください。

カテゴリー
元ネタ
X投稿文
投稿ステータス
投稿日時
投稿ID
投稿URL
エラー内容

横に並べると、次のような形です。

カテゴリー元ネタX投稿文投稿ステータス投稿日時投稿ID投稿URLエラー内容

最低限必要なのは、「X投稿文」と「投稿ステータス」です。

ただし、運用しやすくするために、「投稿日時」「投稿ID」「投稿URL」「エラー内容」も作っておくことをおすすめします。

投稿ステータスの意味

投稿ステータスは、自動投稿を管理するための重要な列です。

ステータス意味
予約済みこれから投稿する文章
投稿中処理中の文章
投稿済み投稿が完了した文章
エラー投稿に失敗した文章

最初は、投稿したい行の「投稿ステータス」に「予約済み」と入力します。

GASが実行されると、予約済みの行を上から順番に探し、最初に見つかった1件だけを投稿します。

投稿が成功すると、その行は「投稿済み」に変わります。

そのため、次回実行時には次の「予約済み」の行が投稿されます。

スプレッドシートの入力例

たとえば、次のように入力します。

カテゴリー元ネタX投稿文投稿ステータス
WordPressログインURLの変更WordPressのログインURLを初期状態のままにしていませんか。セキュリティ対策として、ログインURLの変更は基本です。予約済み
SEO画像のAlt属性画像の代替テキストを空欄にしていませんか。Alt属性を設定すると、検索エンジンにも画像の意味が伝わりやすくなります。予約済み
高速化WebP画像サイト表示を速くしたいなら、画像の軽量化が重要です。WebP形式を使うと、画質を保ちながら容量を減らせます。予約済み

まずはテスト用として、1件だけ「予約済み」にしておくと安心です。

X DeveloperでAPIキーを取得する

次に、X DeveloperでAPIキーを取得します。

X APIを使って投稿するには、開発者用のアプリを作成し、認証情報を取得する必要があります。

取得する情報は次の4つです。

API Key
API Key Secret
Access Token
Access Token Secret

手順は次の通りです。

  1. X Developerにログインする
  2. Developer Portalを開く
  3. Projectを作成する
  4. Appを作成する
  5. App PermissionsをRead and Writeに変更する
  6. API KeyとAPI Key Secretを取得する
  7. Access TokenとAccess Token Secretを取得する

ここで重要なのは、App PermissionsをRead and Writeにすることです。

Readだけでは投稿できません。

また、権限を変更した後は、Access TokenとAccess Token Secretを再生成してください。

古いトークンのままだと、投稿権限が反映されない場合があります。

APIキーの取り扱いに注意する

APIキーやアクセストークンは、Xアカウントを操作するための重要な情報です。

外部に公開してはいけません。

次のような場所に書かないように注意してください。

避ける場所理由
ブログ記事内誰でも見られるため
SNS投稿不正利用される可能性があるため
共有ドキュメント閲覧権限の管理が難しいため
公開リポジトリ検索で見つかる可能性があるため

今回は、Apps Scriptのスクリプトプロパティに保存する形で管理します。

Apps Scriptを開く

作成済みのスプレッドシートを開いた状態で、上部メニューから次の順番で進みます。

拡張機能

Apps Script

Apps Scriptの画面が開いたら、最初から入っているコードを削除します。

その後、次のコードを貼り付けます。

GASコード全文

const SHEET_NAME = '';

const STATUS_RESERVED = '予約済み';
const STATUS_PROCESSING = '投稿中';
const STATUS_POSTED = '投稿済み';
const STATUS_ERROR = 'エラー';

function setXApiKeysOnce() {
PropertiesService.getScriptProperties().setProperties({
X_API_KEY: 'ここにAPI Keyを入力',
X_API_KEY_SECRET: 'ここにAPI Key Secretを入力',
X_ACCESS_TOKEN: 'ここにAccess Tokenを入力',
X_ACCESS_TOKEN_SECRET: 'ここにAccess Token Secretを入力',
X_USERNAME: 'ここにXのユーザー名を入力'
}, true);
}

function postNextReservedPostToX() {
const lock = LockService.getScriptLock();

if (!lock.tryLock(30000)) {
console.log('別の処理が実行中のため終了しました。');
return;
}

try {
const sheet = getTargetSheet_();
const values = sheet.getDataRange().getValues();

if (values.length < 2) {
console.log('投稿データがありません。');
return;
}

const headers = values[0].map(header => String(header).trim());
const col = getColumnIndexes_(headers);

for (let i = 1; i < values.length; i++) {
const row = values[i];
const status = String(row[col.status] || '').trim();

if (status !== STATUS_RESERVED) {
continue;
}

const rowNumber = i + 1;
const text = String(row[col.text] || '').trim();

if (!text) {
updateIfColumnExists_(sheet, rowNumber, col.status, STATUS_ERROR);
updateIfColumnExists_(sheet, rowNumber, col.error, 'X投稿文が空です。');
return;
}

updateIfColumnExists_(sheet, rowNumber, col.status, STATUS_PROCESSING);
SpreadsheetApp.flush();

try {
const result = createXPost_(text);
const postId = result.data && result.data.id ? result.data.id : '';

updateIfColumnExists_(sheet, rowNumber, col.status, STATUS_POSTED);
updateIfColumnExists_(sheet, rowNumber, col.postedAt, new Date());
updateIfColumnExists_(sheet, rowNumber, col.postId, postId);
updateIfColumnExists_(sheet, rowNumber, col.error, '');

const username = PropertiesService.getScriptProperties().getProperty('X_USERNAME');

if (username && postId && col.postUrl !== -1) {
const cleanUsername = username.replace(/^@/, '');
const postUrl = 'https://x.com/' + cleanUsername + '/status/' + postId;
updateIfColumnExists_(sheet, rowNumber, col.postUrl, postUrl);
}

console.log('投稿成功: ' + text);
return;

} catch (error) {
updateIfColumnExists_(sheet, rowNumber, col.status, STATUS_ERROR);
updateIfColumnExists_(sheet, rowNumber, col.error, String(error.message || error));
console.error(error);
return;
}
}

console.log('予約済みの投稿はありません。');

} finally {
lock.releaseLock();
}
}

function createXPost_(text) {
const url = 'https://api.x.com/2/tweets';

const payload = JSON.stringify({
text: text
});

const authorizationHeader = createOAuth1Header_('POST', url);

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

const statusCode = response.getResponseCode();
const responseText = response.getContentText();

if (statusCode < 200 || statusCode >= 300) {
throw new Error('X APIエラー: HTTP ' + statusCode + ' / ' + responseText);
}

return JSON.parse(responseText);
}

function createOAuth1Header_(method, url) {
const props = PropertiesService.getScriptProperties();

const apiKey = props.getProperty('X_API_KEY');
const apiKeySecret = props.getProperty('X_API_KEY_SECRET');
const accessToken = props.getProperty('X_ACCESS_TOKEN');
const accessTokenSecret = props.getProperty('X_ACCESS_TOKEN_SECRET');

if (!apiKey || !apiKeySecret || !accessToken || !accessTokenSecret) {
throw new Error('X APIキーが設定されていません。setXApiKeysOnceを実行してください。');
}

const oauthParams = {
oauth_consumer_key: apiKey,
oauth_nonce: createNonce_(),
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: Math.floor(Date.now() / 1000).toString(),
oauth_token: accessToken,
oauth_version: '1.0'
};

const parameterString = Object.keys(oauthParams)
.sort()
.map(key => encodeRfc3986_(key) + '=' + encodeRfc3986_(oauthParams[key]))
.join('&');

const signatureBaseString = [
method.toUpperCase(),
encodeRfc3986_(url),
encodeRfc3986_(parameterString)
].join('&');

const signingKey = encodeRfc3986_(apiKeySecret) + '&' + encodeRfc3986_(accessTokenSecret);

const signatureBytes = Utilities.computeHmacSignature(
Utilities.MacAlgorithm.HMAC_SHA_1,
signatureBaseString,
signingKey
);

const signature = Utilities.base64Encode(signatureBytes);

oauthParams.oauth_signature = signature;

const authorizationHeader = 'OAuth ' + Object.keys(oauthParams)
.sort()
.map(key => encodeRfc3986_(key) + '="' + encodeRfc3986_(oauthParams[key]) + '"')
.join(', ');

return authorizationHeader;
}

function getTargetSheet_() {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();

if (SHEET_NAME) {
const sheet = spreadsheet.getSheetByName(SHEET_NAME);

if (!sheet) {
throw new Error('指定したシートが見つかりません: ' + SHEET_NAME);
}

return sheet;
}

return spreadsheet.getSheets()[0];
}

function getColumnIndexes_(headers) {
const text = headers.indexOf('X投稿文');
const status = headers.indexOf('投稿ステータス');

if (text === -1) {
throw new Error('1行目に「X投稿文」という見出しが必要です。');
}

if (status === -1) {
throw new Error('1行目に「投稿ステータス」という見出しが必要です。');
}

return {
text: text,
status: status,
postedAt: headers.indexOf('投稿日時'),
postId: headers.indexOf('投稿ID'),
postUrl: headers.indexOf('投稿URL'),
error: headers.indexOf('エラー内容')
};
}

function updateIfColumnExists_(sheet, rowNumber, columnIndex, value) {
if (columnIndex === -1 || columnIndex === undefined || columnIndex === null) {
return;
}

sheet.getRange(rowNumber, columnIndex + 1).setValue(value);
}

function createNonce_() {
return Utilities.getUuid().replace(/-/g, '');
}

function encodeRfc3986_(value) {
return encodeURIComponent(value)
.replace(/[!'()]/g, function(character) {
return '%' + character.charCodeAt(0).toString(16).toUpperCase();
});
}

APIキーを設定する

コードを貼り付けたら、まず次の部分を自分の情報に変更します。

function setXApiKeysOnce() {
PropertiesService.getScriptProperties().setProperties({
X_API_KEY: 'ここにAPI Keyを入力',
X_API_KEY_SECRET: 'ここにAPI Key Secretを入力',
X_ACCESS_TOKEN: 'ここにAccess Tokenを入力',
X_ACCESS_TOKEN_SECRET: 'ここにAccess Token Secretを入力',
X_USERNAME: 'ここにXのユーザー名を入力'
}, true);
}

入力する内容は次の通りです。

項目入力する内容
X_API_KEYX Developerで取得したAPI Key
X_API_KEY_SECRETX Developerで取得したAPI Key Secret
X_ACCESS_TOKENX Developerで取得したAccess Token
X_ACCESS_TOKEN_SECRETX Developerで取得したAccess Token Secret
X_USERNAME自分のXユーザー名

X_USERNAMEは、投稿URLをスプレッドシートに記録するために使います。

たとえば、Xのユーザー名がexampleの場合は、次のように入力します。

X_USERNAME: 'example'

@は付けなくて大丈夫です。

setXApiKeysOnceを実行する

APIキーを入力したら、Apps Script画面上部の関数選択から次を選びます。

setXApiKeysOnce

その状態で「実行」をクリックします。

初回実行時は、Googleアカウントの承認画面が表示されます。

内容を確認して、許可してください。

実行が完了すると、APIキーがスクリプトプロパティに保存されます。

この作業は基本的に最初の1回だけで大丈夫です。

テスト投稿を用意する

次に、スプレッドシートにテスト投稿を1件用意します。

例として、次のように入力します。

カテゴリー元ネタX投稿文投稿ステータス
テストGAS投稿テストGoogle Apps ScriptからXへ自動投稿するテストです。予約済み

最初から本番用の長い文章で試すのではなく、短いテスト文で確認するのがおすすめです。

postNextReservedPostToXを実行する

Apps Script画面上部の関数選択から、次を選びます。

postNextReservedPostToX

その状態で「実行」をクリックします。

処理が成功すると、Xに投稿されます。

スプレッドシート側では、投稿ステータスが次のように変わります。

予約済み

投稿中

投稿済み

投稿日時、投稿ID、投稿URLの列を作っている場合は、それらも自動で記録されます。

投稿URLを記録する方法

投稿URLを記録したい場合は、スプレッドシートの1行目に次の見出しを作ってください。

投稿URL

さらに、APIキー設定時にX_USERNAMEを入力しておきます。

X_USERNAME: '自分のユーザー名'

投稿が成功すると、次のようなURLが自動で入ります。

https://x.com/ユーザー名/status/投稿ID

投稿URLがあると、後から投稿内容を確認しやすくなります。

定期実行トリガーを設定する

テスト投稿が成功したら、毎日自動で実行されるようにトリガーを設定します。

手順は次の通りです。

  1. Apps Script画面の左側にある「トリガー」をクリックする
  2. 右下の「トリガーを追加」をクリックする
  3. 実行する関数を「postNextReservedPostToX」にする
  4. イベントのソースを「時間主導型」にする
  5. 時間ベースのトリガーのタイプを「日付ベースのタイマー」にする
  6. 時刻を選択する
  7. 保存する

たとえば、毎朝投稿したい場合は、次のように設定します。

項目設定内容
実行する関数postNextReservedPostToX
イベントのソース時間主導型
タイマーの種類日付ベースのタイマー
時刻午前8時から9時

これで、毎日指定した時間帯にスクリプトが実行されます。

スプレッドシートを閉じていても、自動で動きます。

毎日1件ずつ投稿される仕組み

今回のコードでは、「予約済み」の投稿を上から順番に探します。

最初に見つかった1件だけを投稿します。

投稿が成功した行は「投稿済み」になるため、次回はその次の「予約済み」が投稿されます。

たとえば、30件の投稿文を用意しておけば、30日分の自動投稿リストとして使えます。

投稿文を追加したい場合は、スプレッドシートに新しい行を追加して、投稿ステータスを「予約済み」にするだけです。

エラーが出たときの確認方法

投稿に失敗した場合は、投稿ステータスが「エラー」になります。

エラー内容の列を作っている場合は、そこに原因が記録されます。

よくあるエラーは次の通りです。

エラー主な原因
401 UnauthorizedAPIキーやトークンが間違っている
403 Forbidden書き込み権限がない
429 Too Many RequestsAPIの利用制限に達している
400 Bad Request投稿本文やリクエスト内容に問題がある
予約済みがない投稿対象の行がない

特に多いのは、書き込み権限の不足です。

App PermissionsをRead and Writeに変更し、Access TokenとAccess Token Secretを再生成してください。

投稿されないときの確認ポイント

投稿されない場合は、次の点を確認してください。

確認項目内容
投稿ステータス「予約済み」と完全一致しているか
見出し「X投稿文」と「投稿ステータス」があるか
APIキー4つのキーが正しく入力されているか
権限App PermissionsがRead and Writeになっているか
トリガーpostNextReservedPostToXが設定されているか
投稿文空欄になっていないか

特に、投稿ステータスの表記ゆれに注意してください。

次のような表記は正しく判定されません。

予約
予約済
予約済み
予約済み

正しくは次の通りです。

予約済み

余計なスペースが入らないようにしてください。

投稿文の文字数を確認する

自動投稿では、投稿文が長すぎるとエラーになることがあります。

そのため、スプレッドシートに文字数確認用の列を作っておくと便利です。

X投稿文がC列にある場合、隣の列に次の関数を入れます。

=LEN(C2)

これで投稿文の文字数を確認できます。

投稿文は、短すぎても内容が薄くなり、長すぎても読みにくくなります。

目安としては、180文字から260文字程度にすると扱いやすいです。

投稿文を作るときのコツ

自動投稿用の文章は、ただ情報を並べるだけでは反応が取りにくくなります。

おすすめの構成は次の通りです。

順番内容
1読者の悩みを書く
2解決策を伝える
3具体例を入れる
4最後に一言でまとめる

たとえば、WordPressのTipsなら次のように書けます。

WordPressのログインURLを初期状態のままにしていませんか。/wp-adminのままだと、不正アクセスの標的になりやすくなります。SiteGuard WP PluginなどでログインURLを変更するだけでも、基本的なセキュリティ対策になります。

このように、「何に困るのか」「何をすればいいのか」が分かる投稿にすると、初心者にも伝わりやすくなります。

スプレッドシート管理のコツ

投稿リストは、カテゴリーごとに整理しておくと運用しやすくなります。

たとえば、次のように曜日ごとにテーマを決める方法があります。

曜日投稿テーマ
月曜日初期設定
火曜日セキュリティ
水曜日SEO
木曜日高速化
金曜日functions.php
土曜日プラグイン
日曜日運用改善

このようにテーマを分けると、発信内容に一貫性が出ます。

読者にも「このアカウントは何を発信しているのか」が伝わりやすくなります。

自動投稿を安全に運用するポイント

自動投稿は便利ですが、完全に放置するのはおすすめしません。

週に1回はスプレッドシートを確認し、次の点をチェックしましょう。

確認項目内容
投稿済み正常に投稿されているか
エラー失敗している行がないか
投稿文古い情報が含まれていないか
反応どの投稿が読まれているか
予約数残りの投稿文が足りているか

自動化の目的は、手抜きではなく、継続しやすい仕組みを作ることです。

投稿後の反応を見ながら、文章を改善していくことが大切です。

初心者におすすめの始め方

最初は、いきなり大量の投稿を用意する必要はありません。

おすすめの始め方は次の通りです。

  1. テスト投稿を1件作る
  2. 手動実行で投稿できるか確認する
  3. 予約投稿を5件作る
  4. 毎日1件のトリガーを設定する
  5. 1週間運用して確認する
  6. 問題なければ30件まで増やす

まずは小さく始めることが大切です。

最初から複雑にしすぎると、どこでエラーが出ているのか分かりにくくなります。

まとめ

GoogleスプレッドシートとGoogle Apps Scriptを使えば、Xの自動投稿を作ることができます。

必要な流れは次の通りです。

  1. スプレッドシートに投稿文を用意する
  2. 投稿ステータスを「予約済み」にする
  3. X DeveloperでAPIキーを取得する
  4. Apps Scriptにコードを貼り付ける
  5. APIキーを設定する
  6. テスト投稿する
  7. 時間主導型トリガーで毎日実行する

この仕組みを作れば、スプレッドシートに投稿文を追加するだけで、毎日1件ずつXへ自動投稿できます。

投稿文、投稿日時、投稿URL、エラー内容をスプレッドシートで管理できるため、初心者でも運用しやすい方法です。

まずはテスト投稿を1件成功させるところから始めて、少しずつ投稿リストを増やしていきましょう。

ホーム
掲載依頼
WordPress
スキルアップ
記事カテゴリ
お問い合わせ
Youtube