セキュリティニュース記事のRSSを取得してみた
はじめに
RSSでニュース記事をとってこれるのか気になったのでやってみました.
本記事では,ローカル環境で動作するシンプルな構成で,
- IPA
- JPCERT/CC
- JVN
の RSS を取得し,画面を横 3 分割して一覧表示するところを紹介する.
取得対象の RSS
今回利用する RSS は以下の 3 つ.
-
JPCERT/CC
https://www.jpcert.or.jp/rss/jpcert.rdf
システム構成
全体構成
-
フロントエンド
- HTML
- CSS
- JavaScript
-
バックエンド(ローカル用)
- Python(簡易 HTTP サーバ)
ブラウザの CORS 制限を回避するため,Python で RSS を中継するミニサーバを立てる構成にしている.
ディレクトリ構成
security-curation/
├─ index.html
├─ style.css
├─ app.js
└─ server.py
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Security News Curation</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>RSSで情報取得してみた</h1>
</header>
<main class="columns">
<section id="ipa" class="column">
<h2>IPA</h2>
<ul class="rss-list" id="ipa-list"></ul>
</section>
<section id="jpcert" class="column">
<h2>JPCERT/CC</h2>
<ul class="rss-list" id="jpcert-list"></ul>
</section>
<section id="jvn" class="column">
<h2>JVN</h2>
<ul class="rss-list" id="jvn-list"></ul>
</section>
</main>
<script src="app.js"></script>
</body>
</html>
style.css
body {
margin: 0;
font-family: "Segoe UI", sans-serif;
background: #f4f6f8;
}
header {
background: #111;
color: #fff;
padding: 12px 20px;
}
.columns {
display: flex;
height: calc(100vh - 60px);
}
.column {
flex: 1;
overflow-y: auto;
padding: 10px;
box-sizing: border-box;
}
.column h2 {
margin-top: 0;
padding-bottom: 6px;
border-bottom: 2px solid;
}
/* IPA */
#ipa {
background: #ffffff;
}
#ipa h2 {
color: #005bac;
border-color: #005bac;
}
/* JPCERT */
#jpcert {
background: #0f172a;
color: #e5e7eb;
}
#jpcert a {
color: #93c5fd;
}
#jpcert h2 {
border-color: #38bdf8;
}
/* JVN */
#jvn {
background: #fff7ed;
}
#jvn h2 {
color: #c2410c;
border-color: #fb923c;
}
.rss-list {
list-style: none;
padding: 0;
}
.rss-list li {
margin: 10px 0;
padding: 8px;
border-radius: 6px;
background: rgba(0,0,0,0.03);
}
.rss-list a {
text-decoration: none;
font-weight: bold;
display: block;
}
.rss-list span {
font-size: 0.85em;
opacity: 0.7;
}
app.js
async function loadRSS(url, listId) {
try {
const res = await fetch(url);
const text = await res.text();
const parser = new DOMParser();
const xml = parser.parseFromString(text, "text/xml");
const items = xml.querySelectorAll("item");
const list = document.getElementById(listId);
items.forEach(item => {
const title = item.querySelector("title")?.textContent ?? "";
const link = item.querySelector("link")?.textContent ?? "";
const date = item.querySelector("pubDate")?.textContent ?? "";
const li = document.createElement("li");
li.innerHTML = `
<a href="${link}" target="_blank">${title}</a>
<span>${date}</span>
`;
list.appendChild(li);
});
} catch (e) {
console.error("RSS load failed:", url, e);
}
}
loadRSS("/rss/ipa.rdf", "ipa-list");
loadRSS("/rss/jpcert.rdf", "jpcert-list");
loadRSS("/rss/jvn.rdf", "jvn-list");
server.py
import http.server
import socketserver
import requests
PORT = 8000
class Handler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path.startswith("/rss/"):
rss_map = {
"/rss/ipa.rdf": "https://www.ipa.go.jp/security/alert-rss.rdf",
"/rss/jpcert.rdf": "https://www.jpcert.or.jp/rss/jpcert.rdf",
"/rss/jvn.rdf": "https://jvn.jp/rss/jvn.rdf",
}
url = rss_map.get(self.path)
if not url:
self.send_error(404)
return
r = requests.get(url)
self.send_response(200)
self.send_header("Content-Type", "application/xml")
self.end_headers()
self.wfile.write(r.content)
else:
super().do_GET()
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print(f"Serving at http://localhost:{PORT}")
httpd.serve_forever()
起動方法
$ pip install requests
$ python3 server.py
http://localhost:8000
ブラウザで確認
ブラウザで以下にアクセスする.
http://localhost:8000
IPA,JPCERT/CC,JVN のセキュリティニュースが
画面を横に 3 分割したレイアウトで表示されれば成功である.
