Google AppsScriptでRSSフィードを解析し、情報を保存する仕組みを構築する
AppScript Automation | カテゴリー登録されたRSS feedを取得し、copyright(著作権表示)やpubData(公開日)、title(標題),description(説明)などを取得登録 |
AppScriptの作成
Webコンテンツ配信のフォーマットとしてRSSがあります。RSSのアドレスには記事のサマリーがまとめられており、こちらを取得することでRSS Readerの基本的の部分は構築できます。
RSSには複数のバージョンやAtomなどフォーマットの違いがあり、それぞれに対応するように記述して取得するようにします。
cookpadのRSS
function getRssMetadata(input) {
/*1. 入力の準備*/
const feedUrl = typeof input === "string" ? input : input.rss_url;
Logger.log("[INPUT] feedUrl: " + feedUrl);
/*
[INPUT] feedUrl: https://news.cookpad.com/feeds/news.rss
*/
try {
/*2. RSSフィードを取得し、XMLとして解析*/
const xml = UrlFetchApp.fetch(feedUrl).getContentText();
Logger.log("[FETCHED XML] " + xml.slice(0, 300));
/*
[FETCHED XML] <?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>クックパッドニュース</title>
<description>毎日の食卓を楽しくする「料理の知恵」メディア</description>
<link>https://news.cookpad.com/</link>
<language>ja</language>
<pubDate>Tue, 10 Jun 2025 14:
const doc = XmlService.parse(xml);
const root = doc.getRootElement();
Logger.log("[ROOT ELEMENT] " + root.getName());
*/
/*3. 結果オブジェクトの初期化*/
const result = {
rssUrl: feedUrl,
type: root.getName(),
title: '',
description: '',
copyright: '',
link: '',
pubDate: ''
};
/*4. RSS 2.0 の処理*/
if (root.getName() === "rss") {
Logger.log("[TYPE] RSS 2.0 detected");
const channel = root.getChild("channel");
result.title = channel.getChildText("title") || "";
result.description = channel.getChildText("description") || "";
result.link = channel.getChildText("link") || "";
result.copyright = channel.getChildText("copyright") || "";
result.pubDate = channel.getChildText("pubDate") || "";
/*5. Atom フィードの処理*/
} else if (root.getName() === "feed") {
Logger.log("[TYPE] Atom detected");
const ns = root.getNamespace();
result.title = root.getChild("title", ns)?.getText() || "";
result.description = root.getChild("subtitle", ns)?.getText() || "";
result.copyright = root.getChild("rights", ns)?.getText() || "";
result.pubDate = root.getChild("updated", ns)?.getText() || "";
const linkElem = root.getChildren("link", ns).find(link => {
const relAttr = link.getAttribute("rel");
return !relAttr || relAttr.getValue() === "alternate";
});
result.link = linkElem?.getAttribute("href")?.getValue() || "";
/*6. RSS 1.0(RDF)の処理*/
} else if (root.getName() === "RDF") {
Logger.log("[TYPE] RSS 1.0 / RDF detected");
// 全ての子要素から channel を見つける
const channel = root.getChildren().find(e => e.getName() === "channel");
if (channel) {
const ns = channel.getNamespace();
result.title = channel.getChild("title", ns)?.getText() || "";
result.description = channel.getChild("description", ns)?.getText() || "";
result.link = channel.getChild("link", ns)?.getText() || "";
} else {
Logger.log("[WARN] channel not found under RDF");
}
/*7. 不明な形式への対応*/
} else {
Logger.log("[TYPE] Unknown feed format");
}
Logger.log("[RESULT] " + JSON.stringify(result));
/*
[RESULT] {"rssUrl":"https://news.cookpad.com/feeds/news.rss","type":"rss","title":"クックパッドニュース","description":"毎日の食卓を楽しくする「料理の知恵」メディア","copyright":"Copyright© Cookpad Inc. All Rights Reserved","link":"https://news.cookpad.com/","pubDate":"Tue, 10 Jun 2025 14:00:00 +0900"}
*/
return result;
/* Appに返される情報 */
/*
{type=rss, description=毎日の食卓を楽しくする「料理の知恵」メディア, rssUrl=https://news.cookpad.com/feeds/news.rss, copyright=Copyright© Cookpad Inc. All Rights Reserved, title=クックパッドニュース, pubDate=Tue, 10 Jun 2025 14:00:00 +0900, link=https://news.cookpad.com/}
*/
/*8. エラーハンドリング*/
} catch (err) {
Logger.log("[ERROR] " + err.message);
return { error: err.message };
}
}
1. 入力の準備
const feedUrl = typeof input === "string" ? input : input.rss_url;
input
が文字列(URL)ならそのまま使用- オブジェクトなら
input.rss_url
を使用
2. RSSフィードを取得し、XMLとして解析
const xml = UrlFetchApp.fetch(feedUrl).getContentText();
const doc = XmlService.parse(xml);
const root = doc.getRootElement();
XmlService.parse
でXMLをパースroot
は<rss>
,<feed>
,<RDF>
などの最上位要素になります
3. 結果オブジェクトの初期化
const result = {
rssUrl: feedUrl,
type: root.getName(), // "rss", "feed", "RDF" など
title: '',
description: '',
copyright: '',
link: '',
pubDate: ''
};
- 抽出結果を格納するための箱
4. RSS 2.0 の処理
if (root.getName() === "rss") {
const channel = root.getChild("channel");
result.title = channel.getChildText("title");
...
}
<rss><channel>...</channel></rss>
構造のフィードに対応title
,description
,link
,pubDate
などを<channel>
から取得
5. Atom フィードの処理
else if (root.getName() === "feed") {
const ns = root.getNamespace();
result.title = root.getChild("title", ns)?.getText();
...
}
<feed>...</feed>
構造(Atom形式)に対応- 名前空間(
ns
)を考慮してtitle
,subtitle
,rights
,updated
を取得 <link>
要素の中からrel="alternate"
のものを探してhref
を取得
6. RSS 1.0(RDF)の処理
else if (root.getName() === "RDF") {
const channel = root.getChildren().find(e => e.getName() === "channel");
...
}
RDFベースのRSS 1.0に対応
<channel> 要素を探して、そこから情報を抽出
7. 不明な形式への対応
else {
Logger.log("[TYPE] Unknown feed format");
}
- 上記3種類(RSS 2.0 / Atom / RSS 1.0)に当てはまらない場合のアラート出力
8. エラーハンドリング
} catch (err) {
Logger.log("[ERROR] " + err.message);
return { error: err.message };
}
- ネットワークエラーやXML解析失敗時にエラーメッセージを返却
コーディングにはGPTなどのコード支援を活用するのがいいと思います。やりたいことを少しずつ記述していくと完成します。
これ以外にも本Appには、記事情報を定期的に取得するスクリプトや、古い記事を削除するスクリプトなど複数のスクリプトを実装していますが、本題とは少しずれるので割愛します。
気になる方がおられましたら、問い合わせからお願いします。