はじめに
RSSやAtomフィードは、ニュースサイトやブログの更新情報を配信する標準的な仕組みです。専用のRSSリーダーを使うのが一般的ですが、普段使っているプリザンターでフィード情報を管理できれば、既存のフィルタ・ソート・通知機能をそのまま活用できて便利です。
今回は、バックグラウンドサーバスクリプトで外部のRSS/Atomフィードを定期取得し、items.Upsertで記事テーブルにデータを自動登録する仕組みを作ってみます。
処理の全体像
バックグラウンドサーバスクリプトがスケジュール実行されると、設定した各フィードURLに対してHTTP GETでXMLを取得します。取得したXMLから記事の情報(タイトル・リンク・概要・公開日時)を正規表現で抽出し、items.UpsertでプリザンターのテーブルにUpsertします。リンクURLをキーにしているため、同じ記事が重複登録されることはありません。
前提条件
Script.json
バックグラウンドサーバスクリプトを有効にするために、App_Data/Parameters/Script.jsonのBackgroundServerScriptをtrueに設定します。
{
"ServerScript": true,
"BackgroundServerScript": true
}
記事格納用テーブルの作成
取得した記事を格納する期限付きテーブルを作成します。以下のカラム構成にします。
| カラム | 用途 | 説明 |
|---|---|---|
| タイトル | 記事タイトル | フィードの<title>を格納 |
| 内容 | 記事概要 | フィードの<description>や<summary>を格納 |
| 分類A | リンクURL | 記事のURLを格納。Upsertのキーとして使用 |
| 分類B | フィード名 | どのフィードから取得したかを識別 |
| 期限付き | 公開日時 | フィードの<pubDate>や<published>を格納 |
カラムの種類は環境に合わせて変更してください。ここでは分類A・分類Bを使用していますが、分類C以降でも構いません。
テーブルを作成したら、サイトIDを控えておきます。後述のスクリプトに設定します。
パラメータファイルの変更後はプリザンターの再起動が必要です。パラメータ再読み込み機能を使う場合は、特権ユーザでログインして実行してください。
XMLパース関数
プリザンターのサーバスクリプトはJavaScript(V8 / ClearScript)エンジンで動作しますが、DOMParserのようなブラウザAPIは使用できません。そのため、XMLのパースには正規表現を使います。
RSSとAtomではタグ名が異なるため、両方に対応するパース関数を用意します。
/**
* XMLテキストからタグの値を取得する
*/
function getTagValue(xml, tagName) {
var pattern = new RegExp('<' + tagName + '[^>]*>([\\s\\S]*?)</' + tagName + '>');
var match = xml.match(pattern);
return match ? match[1].replace(/<!\[CDATA\[|\]\]>/g, '').trim() : '';
}
/**
* XMLテキストからhref属性を取得する(Atom用)
*/
function getHref(xml, tagName) {
var pattern = new RegExp('<' + tagName + '[^>]*href="([^"]*)"');
var match = xml.match(pattern);
return match ? match[1] : '';
}
/**
* RSS/Atom XMLをパースして記事の配列を返す
*/
function parseEntries(xml) {
var entries = [];
if (xml.indexOf('<feed') !== -1) {
// Atom フィード
var entryBlocks = xml.split(/<entry[\s>]/);
for (var i = 1; i < entryBlocks.length; i++) {
var block = entryBlocks[i];
entries.push({
title: getTagValue(block, 'title'),
link: getHref(block, 'link'),
summary: getTagValue(block, 'summary')
|| getTagValue(block, 'content'),
pubDate: getTagValue(block, 'published')
|| getTagValue(block, 'updated')
});
}
} else {
// RSS 2.0 フィード
var itemBlocks = xml.split(/<item[\s>]/);
for (var j = 1; j < itemBlocks.length; j++) {
var itemBlock = itemBlocks[j];
entries.push({
title: getTagValue(itemBlock, 'title'),
link: getTagValue(itemBlock, 'link'),
summary: getTagValue(itemBlock, 'description'),
pubDate: getTagValue(itemBlock, 'pubDate')
});
}
}
return entries;
}
getTagValueはXMLタグの中身を取得する汎用関数です。CDATA セクション(<![CDATA[...]]>)にも対応しています。getHrefはAtomの<link href="...">から URL を取得します。
parseEntriesはフィードの形式を自動判定します。<feedタグが含まれていればAtom形式、それ以外はRSS 2.0形式として処理します。
この正規表現ベースのパーサは一般的なRSS 2.0/Atomフィードに対応しますが、名前空間付きタグ(例: <dc:creator>)や複雑な構造には対応していません。対象フィードに合わせて必要に応じてカスタマイズしてください。
バックグラウンドサーバスクリプト
登録手順
- 特権ユーザでログイン
- 「テナント管理」→「サーバスクリプト」タブを開く
- 「新規作成」をクリック
- 以下を設定して「追加」→「更新」
| 項目 | 設定値 |
|---|---|
| タイトル | RSSフィード取得 |
| 条件 | バックグラウンドサーバスクリプト |
| スケジュール | 毎時(任意) |
| 無効 | チェックなし |
| サーバスクリプト | 下記参照 |
スクリプト
// --- 設定 ---
var targetSiteId = 12345; // 記事格納テーブルのサイトID
var feeds = [
{ name: 'Qiita - Pleasanter', url: 'https://qiita.com/tags/pleasanter/feed' },
{ name: 'Qiita - プリザンター', url: 'https://qiita.com/tags/プリザンター/feed' }
];
// --- 設定ここまで ---
feeds.forEach(function (feed) {
try {
// フィードを取得
httpClient.ResponseHeaders.Clear();
httpClient.RequestUri = feed.url;
var response = httpClient.Get();
if (!httpClient.IsSuccess) {
context.Log('フィード取得に失敗: ' + feed.name + ' (' + feed.url + ')');
return;
}
// XMLをパースして記事を抽出
var entries = parseEntries(response);
if (entries.length === 0) {
context.Log('記事なし: ' + feed.name);
return;
}
var upsertCount = 0;
entries.forEach(function (entry) {
try {
if (!entry.link) return;
var data = {
Keys: ['ClassA'],
Title: entry.title || '(無題)',
Body: entry.summary || '',
ClassA: entry.link,
ClassB: feed.name
};
// 公開日時を期限付きにセット
if (entry.pubDate) {
var d = new Date(entry.pubDate);
if (!isNaN(d.getTime())) {
data.DateA = d.toISOString();
}
}
var result = items.Upsert(targetSiteId, JSON.stringify(data));
if (result) {
upsertCount++;
}
} catch (entryError) {
context.Log('エントリ処理エラー: ' + entry.link + ' - ' + entryError.message);
}
});
context.Log(
'フィード処理完了: ' + feed.name
+ ', 取得件数=' + entries.length
+ ', Upsert件数=' + upsertCount
);
} catch (feedError) {
context.Log('フィード処理エラー: ' + feed.name + ' - ' + feedError.message);
}
});
// --- パース関数 ---
function getTagValue(xml, tagName) {
var pattern = new RegExp(
'<' + tagName + '[^>]*>([\\s\\S]*?)</' + tagName + '>'
);
var match = xml.match(pattern);
return match ? match[1].replace(/<!\[CDATA\[|\]\]>/g, '').trim() : '';
}
function getHref(xml, tagName) {
var pattern = new RegExp('<' + tagName + '[^>]*href="([^"]*)"');
var match = xml.match(pattern);
return match ? match[1] : '';
}
function parseEntries(xml) {
var entries = [];
if (xml.indexOf('<feed') !== -1) {
var entryBlocks = xml.split(/<entry[\s>]/);
for (var i = 1; i < entryBlocks.length; i++) {
var block = entryBlocks[i];
entries.push({
title: getTagValue(block, 'title'),
link: getHref(block, 'link'),
summary: getTagValue(block, 'summary')
|| getTagValue(block, 'content'),
pubDate: getTagValue(block, 'published')
|| getTagValue(block, 'updated')
});
}
} else {
var itemBlocks = xml.split(/<item[\s>]/);
for (var j = 1; j < itemBlocks.length; j++) {
var itemBlock = itemBlocks[j];
entries.push({
title: getTagValue(itemBlock, 'title'),
link: getTagValue(itemBlock, 'link'),
summary: getTagValue(itemBlock, 'description'),
pubDate: getTagValue(itemBlock, 'pubDate')
});
}
}
return entries;
}
スクリプトの解説
スクリプト先頭の設定セクションでフィードの情報を指定します。
| 設定項目 | 説明 |
|---|---|
targetSiteId |
記事を格納するテーブルのサイトID |
feeds |
取得するフィードの配列。nameはフィード名(分類Bに格納)、urlはフィードのURL |
処理の流れは以下のとおりです。
-
feeds配列を順に処理する -
httpClient.Get()でフィードのXMLを取得する -
parseEntriesでXMLをパースして記事エントリの配列を取得する - 各エントリについて
items.Upsertを呼び出す-
Keys: ['ClassA']でリンクURL(分類A)をキーに指定しているため、同じURLの記事は更新、新しいURLの記事は新規作成される
-
- 処理結果を
context.Logで出力する
httpClient.ResponseHeaders.Clear() はリクエストの前に必ず呼び出してください。詳しくは「プリザンターのサーバスクリプトでhttpClientを使うときのお約束」を参照してください。
フィードの追加
フィードを追加するには、設定セクションのfeeds配列にオブジェクトを追加するだけです。
var feeds = [
{ name: 'Qiita - Pleasanter', url: 'https://qiita.com/tags/pleasanter/feed' },
{ name: 'Qiita - プリザンター', url: 'https://qiita.com/tags/プリザンター/feed' },
{ name: 'Zenn - Pleasanter', url: 'https://zenn.dev/topics/pleasanter/feed' }
];
プリザンターのログ機能を活用すれば、フィード取得の成否やUpsert件数を確認できます。スケジュール実行のログは「テナント管理」→「サーバスクリプト」から確認可能です。
新着記事の通知
items.Upsertでレコードが作成・更新されると、テーブルに設定された通知が発動します。つまり、記事格納テーブルに通知設定を追加するだけで、新着記事をメールやチャットで受け取れます。
通知設定の例
記事格納テーブルの「テーブルの管理」→「通知」タブで通知を追加します。
| 項目 | 設定値 |
|---|---|
| 通知種別 | メール / HttpClient など |
| 条件 | 作成後 |
| アドレス | 通知先のメールアドレスやWebhook URL |
「作成後」を条件にすると、新しい記事が初めて登録されたときだけ通知されます。同じ記事が更新された場合も通知したい場合は「更新後」も追加してください。
通知の詳細な設定方法については「テーブルの管理:通知」を参照してください。
カスタマイズ例
HTMLタグの除去
フィードの概要(<description>や<summary>)にHTMLタグが含まれている場合、テーブルにそのまま格納すると読みにくくなります。以下のようにHTMLタグを除去できます。
function stripHtml(html) {
return html.replace(/<[^>]*>/g, '');
}
Upsert時のデータ構築でBodyに適用します。
data.Body = stripHtml(entry.summary || '');
古い記事の自動削除
フィードを長期運用すると記事が蓄積されていきます。一定期間を過ぎた記事を自動削除したい場合は、同じバックグラウンドサーバスクリプト内でAPIを使って古いレコードを削除する処理を追加できます。
// --- 古い記事の削除(90日以上前) ---
var maxDays = 90;
var cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - maxDays);
httpClient.ResponseHeaders.Clear();
httpClient.RequestUri = context.ApplicationPath + 'api/items/' + targetSiteId + '/get';
httpClient.Content = JSON.stringify({
ApiKey: 'YOUR_API_KEY',
View: {
ColumnFilterHash: {
DateA: '["' + cutoffDate.toISOString() + ',"]'
}
}
});
httpClient.MediaType = 'application/json';
var oldResponse = httpClient.Post();
if (httpClient.IsSuccess) {
var oldResult = JSON.parse(oldResponse);
var oldRecords = oldResult.Response.Data || [];
oldRecords.forEach(function (record) {
var recordId = record.IssueId;
if (!recordId) return;
httpClient.ResponseHeaders.Clear();
httpClient.RequestUri = context.ApplicationPath
+ 'api/items/' + recordId + '/delete';
httpClient.Content = JSON.stringify({ ApiKey: 'YOUR_API_KEY' });
httpClient.MediaType = 'application/json';
httpClient.Post();
});
if (oldRecords.length > 0) {
context.Log('古い記事を削除: ' + oldRecords.length + '件');
}
}
古い記事の削除にはAPIキーが必要です。対象テーブルの削除権限を持つユーザのAPIキーを作成しておきます。
まとめ
バックグラウンドサーバスクリプトとitems.Upsertを組み合わせて、プリザンターをRSSリーダーにする仕組みを作成しました。
- バックグラウンドサーバスクリプトでフィードの定期取得を自動化した
-
httpClient.Get()で外部のRSS/AtomフィードのXMLを取得し、正規表現でパースした -
items.UpsertでリンクURLをキーにして重複なくレコードを登録・更新した - RSS 2.0とAtomの両方のフィード形式に対応した
- フィードの追加は設定配列にURLを追加するだけで対応できる
-
Script.jsonのBackgroundServerScript: trueが前提条件