WixStudioでVeloを使う Step 20 「ジョブ・スケジュールを使う」
はじめに
この記事は、Veloの記事を書き始め20記事目。改めて読んでみると、正直、自己満足な記事ばかりが並んでる。でも…5%くらいは誰かの役に立っていればといいと願う今日この頃。反省しつつ、今回も自己満足記事。
自身のサイトをWixStudioで作り替えてある。色々とウェブの技術を検証するときにこのサイトは実験台なる。だから、基本不安定。このサイトには、Zennで書いた記事を紹介するセクションが組み込んだ。

自身のサイトでZennの記事を紹介
スライダーを使っていて、記事がくるくると紹介されていく。ここで問題が発生する。スライドさせるコンテンツを用意するのがめんどくさい。難しいんじゃない。めんどくさい。Zennで記事を書いて、公開して、X(旧Twitter)でシェアして、自身のサイトにコンテンツを登録?。実際、手作業で3件登録して諦めた。時間が経ってから、なんとなくもう1件追加。更に時間が経ってから、もう1件追加した。そして、限界が来た。退屈すぎる。もう、自動化で。
目標
Zennで書いた記事の紹介セクションを自動で更新させる。
準備
必要なのはコレクションとVelo。とRSS。
コレクション
記事紹介のセクションは、スライドショーリピーターで実現してる。

スライドショーリピーター
スライドショーリピーターはコレクションと接続する事が出来る。コレクション(CMS)に登録されている内容をスライドで表示してくれる。

スライドショーリピーターをコレクション(CMS)と接続
目立つのはスライドショーリピーターだけど、重要なのはコレクションの方。コレクションを用意する。

Articlesコレクション
Articlesコレクションを作成する。コレクションIDも「Articles」。
必要なフィールドは、元々用意されている「Title」に加えて、「Slug」「Body」「URL」「Image」「PublishedAt」の6個。
| フィールド名 | フィールドキー | フィールドタイプ | 備考 |
|---|---|---|---|
| Title | title | テキスト | プライマリフィールドに指定 |
| Slug | slug | テキスト | |
| Body | body | テキスト | |
| URL | url | URL | |
| Image | image | 画像 | |
| PublishedAt | publishedAt | 日付 | 時間フィールドを含むにチェックする |
スライドショーリピーターに表示したい内容に合わせてフィールドとして用意しておけば良い。Slugフィールドは識別情報を格納しておくフィールド。Zennで記事識別子をslugと呼んでいる。
コレクションとスライドショーリピーターを接続させるには、スライドショーリピーターを選択した状態で要素設定パネル(右サイドバー)から操作する。

要素設定パネルからCMS接続
「+データセットを追加」を選択し、データセットを作成する。

データセットを作成
データセットが作成され、スライドショーリピーターとコレクションが接続できる。

コレクションと接続された状態
最後にスライドショーリピータに配置されている各要素についてもCMSの接続を行えば良い。
既にデータセットを作成したので、要素設定パネルの「CMS接続」を開いて「データセットの選択」と「コレクションのフィールドを選択」すれば良い。

データセットとフィールドを選択
RSS
RSSはサイトの更新情報などを配信するための仕組み。Zennでは記事の要約をRSSで配信されている。

Zennでは記事のRSSが配信される
RSSはXML形式で配信される。

Zennで配信されるRSS(https://zenn.dev/niibori/feed)
いろんなサイトでRSSは配信されているので好みのものをさがしURLを確認しておく。Zennで公開した僕の記事のRSSはhttps://zenn.dev/niibori/feedで取得出来る。
スクリプトを書く
スクリプトで書く内容は3つ。今回は全てバックエンドコードで作成する。
- RSSを取得する処理。
- RSSの内容を元に、コレクションに記事を登録する処理。
- 上記処理を自動で行う仕組みを用意。
RSSを取得する処理
URLを指定してRSSを取得する。このような処理はよく使う割りには、1から記述すると意外とめんどくさい。そして、世の中には「よく使う処理を汎用的に利用しやすい形でまとたモノ」が共有されている。この「よく使う…まとめたモノ」をパッケージって呼ぶ。Javascriptでパッケージを使う際にはnpmを使う事が多い。Veloでもコレが使える。
npmパッケージを利用する
Veloの「パッケージ・アプリ」、「+ npm からパッケージをインストール」を選択する。

Veloでnpmパッケージ
利用したいパッケージを検索して探す。今回は「rss-parser」を検索する。

rss-parser
使いたいパッケージを見つけたら「インストール」を選択する。

インストールを押す
インストールが完了するとnpmにインストール済のパッケージが表示される。

rss-parserがインストールされた
rssーparserを使ってスクリプトを書く
バックエンドコードとして作成する。スクリプト名はzenn.jsとした。
import Parser from "rss-parser"
export async function getFeeds(){
const parser = new Parser()
return parser.parseURL('https://zenn.dev/niibori/feed');
}
import Parser from "rss-parser"でrss-parserを利用する準備。関数としてgetFeeds()を定義する。処理内容はparserを用意して、RSS(https://zenn.dev/niibori/feed)を解析する。結果は呼び出しもとに返す。「Parser」は「解析する」という意味。rss-parserはRSSを解析する仕組み。const parser = new Parser()でRSSを解析する装置を用意し、parser.parseURL('https://zenn.dev/niibori/feed');で解析を実行する。returnでその結果を返す感じ。解析というのは「Javascriptから使いやすい状態に変換してくれる」くらいの認識で良い。https://zenn.dev/niibori/feedにアクセスするとわかるが、結果はXMLという形式で表現されている。この構造を細かく調べながら記事のタイトルとかURLを取り出すのは大変なので、そういった操作がしやすいようにしてくれる。
RSSの内容を元に、コレクションに記事を登録する処理
backend/zenn.jsのgetFeeds()を利用すれば、Zennの記事情報(RSS)が取得出来るようにした。コレを利用してArticlesコレクションにデータを登録する。これもバックエンドコードを新たに作成する。スクリプト名はarticles.jswとした。
import {getFeeds} from 'backend/zenn'
import wixData from 'wix-data'
export async function fetchArticlesFromZenn(){
const result = await getFeeds()
const feeds = result.items
const articleUrls = await wixData.query('Articles').descending('date').find()
.then( results => {
return results.items.map( item => item.url)
})
const newFeeds = feeds.filter( feed => {
return articleUrls.indexOf(feed.link) == -1}
)
Promise.allSettled(newFeeds.map( newFeed => {
const article = {}
article.title = newFeed.title
article.url = newFeed.link
article.slug = newFeed.guid
article.publishedAt = new Date(newFeed.isoDate)
article.body = newFeed.content
article.image = newFeed.enclosure.url
return wixData.save('Articles', article)
})
).then( results => {
console.log(results)
})
}
英語力が無いことが恥ずかしい。関数fetchArticlesFromZenn()を定義する。処理内容は大きく3つ。
- Articlesコレクションに登録されている記事の情報を取得する。
- 既に登録されている記事を多重登録しないように、登録すべき記事情報を選別する。
- Articlesに記事を登録する。
Articlesコレクションに登録されている記事の情報を取得する
Articlesコレクションから登録済の記事情報を取得している。
const articleUrls = await wixData.query('Articles').descending('date').find()
.then( results => {
return results.items.map( item => item.url)
})
さらに、results.items.map( item => item.url)で登録済記事の情報からURLの値だけを取り出し配列にする。これが結構ポイント。URLの配列はconst articleUrlsに格納される。
既に登録されている記事を多重登録しないように、登録すべき記事情報を選別する
RSSで取得した内容を全て登録すると、同じ記事が何度も登録されてしまう可能性がある。これを避けたい。
const result = await getFeeds()
const feeds = result.items
/* 中略 */
const newFeeds = feeds.filter( feed => {
return articleUrls.indexOf(feed.link) == -1
})
const result = await getFeeds()でRSSの結果を取得している。resultはRSSの解析結果。const feeds = result.itemsは解析結果から記事の情報を取り出している。記事の情報は1件とは限らない(配列で取得される)。このfeedsを元に全てArticlesに登録するとダメ。feedsからまだArticlesコレクションに登録されていない記事だけを探し出す。feeds.filter( feed => { return articleUrls.indexOf(feed.link) == -1 })で選別を実現している。feeds.filter()はフィルタリング処理。feeds.fileter( feed => {...})はfeedsの要素を1件ずつ取り出し、取り出された要素(feed)に対して選別の判定を行う。選別処理はarticleUrls.indexOf(feed.link) == -1。articleUrlsにfeed.linkが含まれるかどうか。feed.linkはRSSから取り出された記事情報のlink情報。含まれれば0以上の数値が返ってくる。含まれていなければ-1が返される。-1が返されればtrueとなり、登録すべき記事情報として判定される。feeds.filter()は結果を配列として返すのでconst newFeedsは登録すべき記事情報のみの配列になる。
参考:rss-parseで解析されたRSSの情報とスクリプト

RSSの記事(item)情報
XMLで表現されたRSSの情報。<item>~</item>までが1件分の記事情報。このブロックは複数個並ぶ。<item>~</item>の中には<title>~</title>や<link>~</link>等で括られて表現されている。これをrss-parserで解析した結果をスクリプトではresultとしている。複数の<item>~</item>はresult.itemsで取りだしfeeds。feedsから取りだした一つ一つ(<item>~</item>一つ分)をfeed。feed.titleやfeed.linkは<item>~</item>の中の<title>~</title>や<link>~</link>に括られた値を取り出せる。
Articlesに記事を登録する
newFeedsに格納された全ての記事情報に対して、Articlesコレクションへの登録処理を行う。
Promise.allSettled(newFeeds.map( newFeed => {
const article = {}
article.title = newFeed.title
article.url = newFeed.link
article.slug = newFeed.guid
article.publishedAt = new Date(newFeed.isoDate)
article.body = newFeed.content
article.image = newFeed.enclosure.url
return wixData.save('Articles', article)
})
).then( results => {
//console.log(results)
})
const article = {}で登録する為のオブジェクトを用意。article.title = newFeed.titleやarticle.publishedAt = new Date(newFeed.isoDate)で記事情報をオブジェクトの属性として設定。wixData.save('Articles', article)でArticlesコレクションへの登録を実施。これらの処理はPromise.allSettled(newFeeds.map( newFeed => {}))内で実施し全ての登録結果を取得出来るようにしている。
上記処理を自動で行う仕組みを用意
今まで関数を実行するときはボタンなどのイベントを使って呼び出していた。今回もそれは可能だけれど、ボタンを押さない限りは関数が呼び出されないと言うことになる。「自動で」という目標を達成できない。Wixでは、作成した関数を定期的に呼び出す仕組みがある。
バックエンドコードにファイルを作成し、設定を書けば完了出来る。作成するファイルはbackend/jobs.config。
{
"jobs":[
{
"functionLocation":"/articles.jsw",
"functionName":"fetchArticlesFromZenn",
"executionConfig":{
"cronExpression": "0 0 * * *"
}
}
]
}
| 設定項目 | 設定値 | 意味 |
|---|---|---|
"functionLocation" |
"/articles.jsw" |
バックエンドコードとして作成したスクリプト名 |
"functionName" |
"fetchArticlesFromZenn" |
functionLocationで指定したスクリプトに定義された関数名 |
"executionConfig" |
{"cronExpression": "0 0 * * *"} |
実行方法の指定 |
executionConfigの指定は慣れが必要。今回はcronExpression形式で指定している。"0 0 * * *"は先頭から順に「分」「時」「日」「月」「曜日」を空白区切りで列挙する。*はワイルドカード(要は、指定なし。何でもいい)。"0 0 * * *"は、「分」が0で「時」も0なので「0時0分」を指定している。他は未指定なので「毎日0時0分」になったら関数を呼び出すと言うことになる。"0 0 1 * *"のように指定すれば「毎月1日の0時0分」に実行するようなこともできる。
jobs.configで定期的な自動実行を実現出来るが、実行間隔は最低1時間以上空けなければならないことになっているので注意。
もう一点注意!日時はUTCで考えなければならない。日本時間(JST) はUTC+9(9時間進んでいる)ので、指定する時刻は9時間引いて考えなければならない。例えば日本時間(JST)で毎日0:00を指定する場合、UTCでは15:00を指定しなければならない。
動作確認
jobs.configを変更したら公開する。多分、これで反映される。
動作はバックグラウンドで行われるため確認しづらい。結果として取り込まれたコレクションがこんな感じ。

記事が取り込まれたコレクション
記事が取り込まれたので、ページにもコンテンツが反映される。

記事がスライドショーリピーターに反映される
上手くいかないときはログを確認した方が良い。ダッシュボードから「デベロッパーツール」の「ログ」、さらに「サイトイベント」を開くとログが見える。

ログ:サイトイベント
まとめ
他サイトのコンテンツを取り込んで表示する一例としてRSSを取り込んだ。npmを使って目的に合ったパッケージを取得出来ればスクリプトの記述量は結構減らせる。ただ、そのパッケージの使い方は別途調べなければならない。自分がやりたいと思うことの事例を探したりして、そこで使っているパッケージを確認するなどして選定する必要はある。また、Wixに組み込んでも上手くそれが使えない場合もあると思うので注意。バックエンドコードで利用すれば使えたりとか。その辺は、試行錯誤が必要な気がする。ジョブを使った処理の実行はすごく便利。定期的な単純作業はやっぱり自動化するべきだと思う。
つづき
WixStudioでVeloを使う Step 21 「会員情報(Member)を使い始める/サイト会員同士のメッセージ機能(?)を作成する」
Discussion