【GAS×Twitter】Webページのスクレイピング結果を自動投稿する

Google Apps Script

本記事では、Google Apps Script(GAS)を使用して、Webページのスクレイピング結果をTwitterに自動ツイートする方法を解説します。

当ブログでは、最新記事を投稿したときにツイッターでお知らせをしているんですが、この作業、けっこう忘れがちなんですよね・・・。何か楽にできる方法はないかと考えた結果、GASでスクレイピングしてトリガーで流せば良いのでは?と思ったので実際に作ってみようと思います。できあがりはこんなかんじ↓

最新ニュースを拾ってツイートしたい、Webサイトを更新したらお知らせしたいなど、スクレイピング⇒自動投稿のセットは活躍する場面が多いと思うので、是非ご活用いただけたらと思います。

事前準備

GASからTwitterの投稿を行うためには、Twitter・GASそれぞれの設定が必要です。以下の記事にて詳しく解説しています。

手順が多いのと、プログラムを動かす必要があるので初心者の方はちょっと抵抗があるかもしれませんが、順番に実施していただくことで問題なくできるかと思います。

うまくいかない、記事読んだけどやり方が分からないという場合はお気軽にコメントください!

GASでWebサイトをスクレイピング

「スクレイピング」というのは、Webサイトの情報を収集して分析・加工する技術のことです。

GASでWebサイトのスクレイピングを行うには、「Parserライブラリ」という外部ライブラリをインストールする必要があります。インストール手順は以下記事をご参照ください。

GASでHTMLを取得する

HTML取得のコードは以下のようになります。

UrlFetchApp.fetch(URL).getContentText(“[文字コード]”);
※文字コード:headタグ内の <meta charset=”[文字コード]”> にあわせる

例えば、今回は本ブログのトップページを取得するコードは以下のようになります。

const URL = 'https://prtn-life.com/';

function autoTweet() {
  let res = UrlFetchApp.fetch(URL).getContentText("utf-8");
}

resに格納される値は、HTMLソース全てです。ここから必要な部分を取り出します。

Parserライブラリで特定の部分のみ抽出する

ライブラリのインストール

GASで取得したHTMLを操作するには、「Parserライブラリ」を使用します。使用にはインストールが必要です。GASエディタ>ライブラリから、以下を指定してインストールしてください。

1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw

詳しい手順はこちらの記事にて解説しています。

Parserライブラリの使い方

HTMLの中から特定の部分のみを抽出するには、以下のように記載します。

◆条件に一致する全ての要素を取得する場合(戻り値:リスト)
Parser.data(HTML文字列).from(‘開始文字列’).to(‘終了文字列’).iterate();

◆条件に一致する最初の要素を取得する場合(戻り値:文字列)
Parser.data(HTML文字列).from(‘開始文字列’).to(‘終了文字列’).build();

例えば、以下のHTMLのうちタイトルを取得したい場合は以下のようになります。

 <head>
  <title>これはタイトルです!</title>
 </head>
let title = Parser.data(res).from('<title>').to('</title>').build();

開始・終了文字列の指定方法

対象のサイトから、どのように記事タイトルとリンクを指定するかを確認します。

先ほどの例では、開始タグ(<title>)と終了タグ(</title>)が明確に存在していたので、それを指定して取得することができました。しかし、実際にブログ記事のリンクを取得しようとすると、<a href=~と続いているし、終わりもどこを指定すれば良いのやら・・・!

このような場合は、開始位置と終了位置が特定できる文字列を指定すればOKです。必ずしもHTMLのタグを指定しなければならないのではなく、取り出したい部分を一意に特定できれば問題ありません。

今回は最新記事のみ取得できれば良いので、どちらも.build()を使います。

ツイート文面の作成

取得した内容をもとに、自動投稿する内容を作成します。

ここで気を付けたいのは、Twitter APIでは過去のツイート内容と一言一句同じツイートはできないということです。今回の例のように、ブログの記事紹介のツイートだと、過去に同じタイトルを紹介していたらエラーになってしまいます。過去の同内容のツイートを削除するという方法もありますが、ちょっと面倒ですよね。

そこで、ツイート内容が一意になるよう何かしらの文字を追加してあげるようにします。今回の例では、実行時の日時を取得し、それをツイート内容に付与するようにしました。文字数を節約したい場合は’yyMMdd’にするなど適宜変更してみてください。

  let today = new Date();
  let todayStr = Utilities.formatDate(today, 'JST', 'yyyy-MM-dd');

それっぽいかんじに文面を作って、以下のような形になりました。

取得した文字列をツイートする

ブログの記事タイトルとリンクが取得できたので、これをツイートします。こちらの記事にあるソースコードをベースに実装していきます。

まず、先ほど作成した関数をソースコードの任意の場所に追加します。

次に、ツイート処理(sendTweet())を変更します。現状のソースコードでは、固定の文字列をツイートするようになっています。

上記のうち、固定の文字の部分に先ほど作成した関数を入れてあげます。

これで完了です。

ツイートしてみる

実行する関数が「sendTweet」になっていることを確認して、「実行」を押します。実行結果にはツイートID、ツイートしたテキストが出力されます。

Twitterに正しくツイートされていることを確認できました。

トリガーによる自動投稿の設定

GASで自動投稿を行うには、「トリガー」機能を使います。左側メニューにある時計のアイコンを押下します。

「トリガーを追加」を押して、トリガーを設定します。このとき、実行する関数を「sendTweet」に設定するのを忘れずに。

実行する間隔は、〇時間おき、毎日〇時、毎週〇曜日など選択できます。詳しくは以下記事にて解説しています。

コピペ用ソースコード

以下にコピペ用のソースコードを載せておきます。(といっても、以前の記事で載せたソースコードに今回の処理を追加しただけですが)

以下をGASのエディタに貼り付けて実行してください。OAuth、Parserライブラリの追加と、Twitter APIの初回認証が完了していないと実行できませんのでご注意ください。

const CLIENT_ID = 'XXX'
const CLIENT_SECRET = 'XXX'

function main() {
  const service = getService();
  if (service.hasAccess()) {
    Logger.log("Already authorized");
  } else {
    const authorizationUrl = service.getAuthorizationUrl();
    Logger.log('Open the following URL and re-run the script: %s', authorizationUrl);
  }
}

function autoTweet() {

  const URL = 'https://prtn-life.com/';
  let today = new Date();
  let todayStr = Utilities.formatDate(today, 'JST', 'yyyy-MM-dd');

  // HTTPレスポンスを文字列で取得
  let res = UrlFetchApp.fetch(URL).getContentText("utf-8");

  // 店舗ごとに配列の要素に追加
  let latestArticle = Parser.data(res).from('<div class="new-list-box block-box">').to('</div>').build();

  let title = Parser.data(latestArticle).from('title="').to('"').build();
  let link = Parser.data(latestArticle).from('<a href="').to('" class=').build();

  let msg =  '【ブログ更新のお知らせ】\n\n' + title + '\n\n' + '詳細は以下をチェック♪' + '\n\n' + link + '\n' + todayStr;
  
  return msg;
}

function getService() {
  pkceChallengeVerifier();
  const userProps = PropertiesService.getUserProperties();
  const scriptProps = PropertiesService.getScriptProperties();
  return OAuth2.createService('twitter')
    .setAuthorizationBaseUrl('https://twitter.com/i/oauth2/authorize')
    .setTokenUrl('https://api.twitter.com/2/oauth2/token?code_verifier=' + userProps.getProperty("code_verifier"))
    .setClientId(CLIENT_ID)
    .setClientSecret(CLIENT_SECRET)
    .setCallbackFunction('authCallback')
    .setPropertyStore(userProps)
    .setScope('users.read tweet.read tweet.write offline.access')
    .setParam('response_type', 'code')
    .setParam('code_challenge_method', 'S256')
    .setParam('code_challenge', userProps.getProperty("code_challenge"))
    .setTokenHeaders({
      'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET),
      'Content-Type': 'application/x-www-form-urlencoded'
    })
}

function authCallback(request) {
  const service = getService();
  const authorized = service.handleCallback(request);
  if (authorized) {
    return HtmlService.createHtmlOutput('Success!');
  } else {
    return HtmlService.createHtmlOutput('Denied.');
  }
}

function pkceChallengeVerifier() {
  var userProps = PropertiesService.getUserProperties();
  if (!userProps.getProperty("code_verifier")) {
    var verifier = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";

    for (var i = 0; i < 128; i++) {
      verifier += possible.charAt(Math.floor(Math.random() * possible.length));
    }

    var sha256Hash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, verifier)

    var challenge = Utilities.base64Encode(sha256Hash)
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '')
    userProps.setProperty("code_verifier", verifier)
    userProps.setProperty("code_challenge", challenge)
  }
}

function logRedirectUri() {
  var service = getService();
  Logger.log(service.getRedirectUri());
}

function sendTweet() {
  var payload = {
    text: autoTweet()
  }

  var service = getService();
  if (service.hasAccess()) {
    var url = `https://api.twitter.com/2/tweets`;
    var response = UrlFetchApp.fetch(url, {
      method: 'POST',
      'contentType': 'application/json',
      headers: {
        Authorization: 'Bearer ' + service.getAccessToken()
      },
      muteHttpExceptions: true,
      payload: JSON.stringify(payload)
    });
    var result = JSON.parse(response.getContentText());
    Logger.log(JSON.stringify(result, null, 2));
  } else {
    var authorizationUrl = service.getAuthorizationUrl();
    Logger.log('Open the following URL and re-run the script: %s', authorizationUrl);
  }
}

まとめ

Google Apps Script(GAS)を使用して、Webページのスクレイピング結果をTwitterに自動ツイートする方法を紹介しました。

スプレッドシートにこれまでのブログ記事を一覧にしておき、一覧にない場合のみツイートしたり、一覧からランダムに過去記事をツイートしても良いなと思いました。スプレッドシートを使えば英単語だったり格言だったりを呟くbotも無料で作れますね!アフィリエイトなんかも捗るのではないでしょうか。

当ブログでは、Google Apps Script(GAS)を使って、LINEやTwitterなどと連携した様々なアイディアを発信しています。私自身が、「こういうのがあったらいいなー」「自動化させて楽したいなー」と思ったことをネタにしているので、けっこう実用的なものもあるかと思います。是非参考にしてみてください!

¥2,860 (2024/10/03 10:14時点 | Amazon調べ)
chaso

文系出身、数字が苦手な平凡主婦。塾講師、大手企業SE、不動産事務、Webライター、結婚後はパートタイムでエンジニアをしています。機械音痴だけど効率化や自動化をこよなく愛しています!お仕事の依頼・ご相談は問い合わせよりお願いいたします♪

chasoをフォローする

コメント

タイトルとURLをコピーしました