GAS(Google Action Script)でWordPressの記事一覧を取得してみました。取得後、ツイートしようかなと思っています。取得方法だけこの記事で説明します。
他、いろいろとその周辺まわりのプチ覚書をかねています。
Contents
WordPressからTwitterでツイートする場合、どっち?IFTTTとGASの比較と違い!
WordPressのurlとタイトルをつぶやくことはIFTTTでもできます。IFTTTの場合は次のような感じです。
- 新規記事しかつぶやきできない(GASはプログラミングをかけばいろいろできる)
- XML-RPC API アクセス制限を解除する必要がある(GASの場合はREST API アクセス制限を解除する必要があります)
REST APIとスクレイピングの比較と違い、どっち!?
Webサイトの情報を取得する際、rest apiとスクレイピングという方法があります。
社内のページを頑張ってスクレイピングしてWebhookに投げるプログラム書いてたら先輩に「REST APIあるよ」って言われて泣きながら書き直した
— たっくん (@mikuta0407) July 19, 2022
スクレイピングよりapiを使おう勢が多いですね。WordPressならREST APIですね。
REST API:
- 特徴: REST APIは、Webアプリケーションやサービスのデータや機能を公開するためのプログラムインターフェースです。クライアントアプリケーションはHTTPリクエストを使用してAPIエンドポイントにアクセスし、データの取得や送信、処理などを行います。
- 使用例: クライアントアプリケーションやモバイルアプリケーションからデータを取得する、外部サービスとの連携、データの更新や処理など、Webアプリケーションやサービスを公開・利用する場合に使用されます。
スクレイピング:
- 特徴: スクレイピングは、WebページのHTML構造を解析し、必要なデータを取得する技術です。一般的には、Webサイトのコンテンツや情報を自動的に取得するために使用されます。Webページ上の特定の要素やデータを抽出することができます。勝手に人のサイトを抽出しちゃうと違法性などの問題もあるためご注意ください。
- 使用例: 特定のWebサイトからデータを取得して集計や分析を行う、情報の監視や更新の自動化、ウェブクローラーの作成など、Web上のデータを取得するために使用されます。
REST APIはWPでアプリケーションパスワードの生成とサーバーで解除が必要
REST APIはWPでアプリケーションパスワードの生成とサーバーで解除が必要です。そうしないとエラーがでます。
アプリケーションパスワードの作成は次からできます。生成されるものが半角スペースがあきますけどそのまま使うようです。
ユーザー > ユーザー一覧 > 名前 > アプリケーションパスワード
また、サーバーの設定も解除が必要です。
Exception: Request failed for https://xxx.com returned code 403. Truncated server response:
Exception: Request failed for https://xxx.com returned code 403. Truncated server response:
サーバーの設定を解除します。REST API アクセス制限サーバーの設定を解除します。
スターサーバーの例です。
サーバー管理ツール > WordPressセキュリティ設定 > 該当ドメインを選ぶ > 国外IPアクセス制限 > REST API アクセス制限 > 無効
ドメインがたくさんある場合はうっかりと他のドメインを選ばないようにしましょう。
gasでWordPressのurlとタイトルとカテゴリ名を取得する(サンプルコード)
最初のとっかかりですけど、作ってみました。usernameはログインするときの名前でOKです。
function fetchRandomWordPressPosts() {
// WordPressのREST APIエンドポイント、ユーザー名、アプリケーションパスワード
const apiUrl = 'https://xxx.net/wp-json/wp/v2';
const username = 'xxx';
const password = 'xxx';
const allPostsEndpoint = apiUrl + '/posts?_fields=id'; // 全記事の取得エンドポイント
const options = {
method: 'get',
headers: {
'Authorization': 'Basic ' + Utilities.base64Encode(username + ':' + password)
}
};
try {
const allPostsResponse = UrlFetchApp.fetch(allPostsEndpoint, options);
const allPostsData = JSON.parse(allPostsResponse.getContentText());
const totalPosts = allPostsData.length;
const randomPosts = getRandomIndexes(totalPosts, 10); // ランダムに10記事取得
for (let i = 0; i < randomPosts.length; i++) {
const postIndex = randomPosts[i];
const postId = allPostsData[postIndex].id;
const postEndpoint = apiUrl + '/posts/' + postId + '?_fields=title,link,categories'; // 個別記事の取得エンドポイント
const postResponse = UrlFetchApp.fetch(postEndpoint, options);
const postData = JSON.parse(postResponse.getContentText());
const postTitle = postData.title.rendered;
const postLink = postData.link;
const postCategories = postData.categories;
Logger.log('Post ID: ' + postId);
Logger.log('Title: ' + postTitle);
Logger.log('Link: ' + postLink);
Logger.log('Categories: ' + postCategories.join(', '));
Logger.log('---');
}
} catch (e) {
Logger.log('エラー: ' + e);
}
}
// ランダムなインデックスを生成するヘルパー関数
function getRandomIndexes(total, count) {
const indexes = [];
for (let i = 0; i < count; i++) {
const randomIndex = Math.floor(Math.random() * total);
indexes.push(randomIndex);
}
return indexes;
}
ただ、これだけではうまくいかず、かなり改造が必要なことがわかりました。
細かいことをいうとカテゴリ名はIDになってしまいますし…。ただ、もっと大枠の問題があります。
全記事を取得する場合は記事数の制限がある
- デフォルトは10件
- 最大で100件まで
で、100件以上は一工夫が必要そうです。
UrlFetchApp.fetchの取得サンプル
function getAllPosts() {
const { POSTS_TO_ADD, PER_PAGE } = getAddPostSettings();
const posts = [];
for (let page = 1; page <= Math.ceil(POSTS_TO_ADD / PER_PAGE); page++) {
const endpoint = `${WP_REST_API_ENDPOINT}?page=${page}&per_page=${PER_PAGE}&_fields=id,title,link,categories`;
const options = {
method: 'GET',
muteHttpExceptions: true,
};
const response = UrlFetchApp.fetch(endpoint, options);
const postsOnPage = JSON.parse(response.getContentText());
posts.push(...postsOnPage);
}
return posts;
}
ランダムに記事を取得する
ランダムでとりたいだけなんだけど、意外と考えることが多かったです。
WordPressのREST APIには、デフォルトでランダムな順序で記事を取得するためのクエリパラメータは存在しないようです。
WordPress側でカスタムエンドポイントを作成する方法は、クラアインととサーバーのやりとりがあり、実装が複雑になりそうです。
最初、記事数が多くなるとGASの6秒ルールにひっかかるため、記事を分割してGas側で定期実行をしようと検討しました。しかし、この方法は2回目取得時に重複記事ができてしまう問題がありました。
結局、全記事を取得する方法で実装しました。ただ、6秒ルールがあるため再帰的に実行するとまずそうです。処理を分散させるパッチ処理のような感じにしないとダメです。
${apiUrl}/posts?
と${apiUrl}?page
の違い
${apiUrl}/posts?
の形式:- この形式では、
posts
エンドポイントにアクセスし、全ての投稿を取得します。 - ページネーションのパラメータ(
page
やper_page
)を指定しない場合、デフォルトの値が適用され、一度に返される投稿数が制限されることがあります。
- この形式では、
${apiUrl}?page=
の形式:- この形式では、
apiUrl
に直接パラメータを付けてアクセスし、カスタムエンドポイントの形式を作成します。 - ページネーションのパラメータを明示的に指定することで、特定のページの投稿を取得することができます。
- この形式では、
ページネーション
記事一覧を取得するためにページネーションを使った方がよいですね。
- データの損失: APIがページネーションを使用してデータを提供する場合、ページネーションを無視して全てのデータを一度に取得しようとすると、APIが一度に返すことができるデータの上限に達した場合、全てのデータが返されず一部のデータが損失される可能性があります。
- パフォーマンスの問題: 一度に大量のデータを要求すると、APIサーバーに大きな負荷をかける可能性があります。これはAPIの応答時間を遅くするだけでなく、最悪の場合はAPIサーバーがダウンする可能性もあります。
- リソースの非効率的な使用: ページネーションを使用すると、要求と応答の両方のデータ量を管理し、一度に処理するデータ量を抑えることができます。これにより、メモリや帯域幅などのリソースを効率的に使用できます。
- レートリミットの違反: APIには通常、一定時間内に許可されるリクエストの数(レートリミット)が設定されています。一度に大量のデータを取得しようとすると、このレートリミットを超える可能性があります。これにより、APIからの一時的または恒久的なアクセス制限が課せられる可能性があります。
したがって、データが多く、ページネーションを提供するAPIを使用する場合、ページネーションを適切に使用することが重要です。
スプレッドシートからGASの関数を実行するメニューを追加する
スプレッドシートにメニューを追加できます。
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('カスタムメニュー')
.addItem('トリガーの設定', 'setTrigger')
.addToUi();
}
function setTrigger() {
// トリガーの設定処理
}
なお、addItem()は項目を動的に表示/非表示にする機能やグレーアウトする機能はなさそうです。
.addItem()ないで、三項演算子を使って制御するしかなさそうです。
.addItem(isOn ? 'オフ' : 'オン', 'toggleStatus')
GASはサーバーサイドですが、スプレッドシートを再度立ち上げると、グローバル変数は初期化されるため、前回の値は失われるようです。つまり、フラグ管理に一工夫いります。
データの永続性を確保するためには、外部のストレージを使用する必要があります。GASが標準で持っているPropertiesService
がよいのではないでしょうか。歯車アイコンから設定できるスクリプトIDのことです。
スクリプトIDが設定されていない場合、nullを返すのでtrueで分岐すればよさそうです。
PropertiesService は、Googleのアカウントごとに独立しているため、スプレッドシートのコピーを作成した場合、 PropertiesService は独立しています。コードやスプレッドシートに書くより、セキュリティ的にベターです。
ScriptPropertiesは非推奨
なお似たものとして、ScriptPropertiesクラスがあります。しかし、ScriptPropertiesは非推奨となっており、代替案としてはPropertiesServiceクラスを使用することが推奨されています。
Class ScriptProperties
非推奨。このクラスは非推奨のため、新しいスクリプトには使用しないでください。
https://developers.google.com/apps-script/reference/properties/script-properties?hl=ja
ご参考になれば幸いです。
コメント