-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgenerate_toc_rss.py
More file actions
232 lines (193 loc) · 8.74 KB
/
generate_toc_rss.py
File metadata and controls
232 lines (193 loc) · 8.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
"""
RSSフィードを生成するスクリプト(目次リンクごと)
このスクリプトは、官報の目次リンクを受け取り、RSSフィードを生成します。
引数として目次のリンクがまとめられたJSON形式で受け取ります。
生成されたRSSフィードは、`feed_toc.xml`というファイルに保存されます。
"""
import ast
import sys
import json
from datetime import datetime, timezone
import logging
import hashlib
def make_guid(title, link, pubdate):
"""タイトル、リンク、日付からMD5ハッシュによるGUIDを生成する。
Args:
title (str): アイテムのタイトル
link (str): アイテムのリンクURL
pubdate (str): 公開日時
Returns:
str: MD5ハッシュ値(16進数文字列)
"""
# タイトル+リンク+日付を結合
raw = f"{title}{link}{pubdate}"
# UTF-8でバイト列に変換してMD5ハッシュ
md5_hash = hashlib.md5(raw.encode("utf-8")).hexdigest()
return md5_hash
def get_categories(toc_infos):
"""目次情報からカテゴリを抽出する関数
Args:
toc_infos (list): 目次情報のリスト
Returns:
list: カテゴリのリスト
"""
categories = []
for toc_item in toc_infos:
toc_item_value = toc_item["value"]
if isinstance(toc_item_value, str) and toc_item_value:
categories.append(toc_item_value)
return categories
def make_item(page_title, toc_info, categories=None):
"""PDF情報からRSSアイテムを生成し、JSONファイルに保存する関数
Args:
page_title (str): ページタイトル名
toc_infos (dict): 辞書型で、以下のキーを含む必要があります。
- "link_title": '入札公告'
- "url": '目次に該当するページのurl'}
categories (list, optional): カテゴリのリスト。デフォルトはNone。
"""
date = datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S GMT")
link_url = toc_info["url"]
toc_title = toc_info["link_title"]
logo_icon = "https://kanpo-viewer.com/logo.png"
# ハッシュ値作成
guid_value = make_guid(toc_title, link_url, date)
new_item = {
"title": toc_title,
"link": link_url,
"pub_date": date,
"author": "https://www.kanpo.go.jp",
"description": f"{page_title}が発行されました。\n{toc_title}についてはこちら:{link_url}",
"logo_icon": logo_icon,
"category": categories or [],
"guid_value": guid_value,
}
json_path = "rss_toc_data.json"
try:
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
logging.info(f"ファイル {json_path} が見つかりません。新しいファイルを作成します。")
data = []
except Exception as e:
logging.error(f"ファイル {json_path} の読み込み中にエラーが発生しました: {e}")
# 既存の同一リンクのアイテムを探す(リンクとタイトルで重複と確認)
existing_item = next(
(item for item in data if item["title"] == new_item["title"] and item["link"] == new_item["link"]), None
)
if not existing_item:
data.append(new_item)
logging.info(f"➕ 新しいRSSアイテムを追加しました: {new_item['title']}")
else:
logging.info(f"🔄 既存のRSSが存在するのでスキップします: {existing_item['title']}")
# 日付でソート
data = sorted(data, key=lambda x: datetime.strptime(x["pub_date"], "%a, %d %b %Y %H:%M:%S %Z"), reverse=True)
# RSSアイテムのテンプレート
item_template = """
<item>
<title><![CDATA[{title}]]></title>
<description><![CDATA[{description}]]></description>
<link>{link}</link>
<guid isPermaLink="false">{guid_value}</guid>
<pubDate>{pub_date}</pubDate>
<dc:creator>{author}</dc:creator>
<enclosure url="{logo_icon}" length="0" type="image/png"/>
{category_xml}
</item>
"""
# 古いアイテムを壊さないように、カテゴリが空の場合は古いテンプレートを使用
old_item_template = """
<item>
<title><![CDATA[{title}]]></title>
<description><![CDATA[{description}]]></description>
<link>{link}</link>
<guid isPermaLink="false">{guid_value}</guid>
<pubDate>{pub_date}</pubDate>
<dc:creator>{author}</dc:creator>
<enclosure url="{logo_icon}" length="0" type="image/png"/>
</item>
"""
items_xml = ""
for item in data:
# 既存データにguid_valueが存在しない場合は生成
if "guid_value" not in item or not item["guid_value"]:
item["guid_value"] = make_guid(item["title"], item["link"], item["pub_date"])
# カテゴリタグを整形してインデント付きで生成
category_xml = ""
if isinstance(item.get("category"), list):
for cat in item["category"]:
if category_xml == "":
category_xml += f"<category>{cat}</category>\n"
else:
category_xml += f"\t<category>{cat}</category>\n"
# 古いアイテムを壊さない様に。
if category_xml == "":
items_xml += old_item_template.format(**item)
else:
items_xml += item_template.format(**item, category_xml=category_xml)
rss_template = f"""<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title><![CDATA[ 官報RSS目次別(非公式)フィード ]]></title>
<link>https://www.kanpo.go.jp</link>
<description>これは官報の非公式更新通知RSS(目次別)です。基本的に毎日8:35分ごろに更新内容を確認して、RSSをプッシュします</description>
<generator>testkun08080</generator>
<lastBuildDate>{date}</lastBuildDate>
<atom:link href="https://kanpo-viewer.com/feed_toc.xml" rel="self" type="application/rss+xml"/>
<language>ja</language>
<image>
<url>{logo_icon}</url>
<title><![CDATA[ 官報RSS目次別(非公式)フィード ]]></title>
<link>https://www.kanpo.go.jp</link>
</image>
{items_xml}
</channel>
</rss>
"""
with open("feed_toc.xml", "w", encoding="utf-8") as f:
f.write(rss_template)
# kanpo-viewer用にコピー
try:
with open("kanpo-viewer/public/feed_toc.xml", "w", encoding="utf-8") as f:
f.write(rss_template)
logging.info("✅ kanpo-viewer/public/feed_toc.xml を更新しました")
except FileNotFoundError:
logging.warning("⚠️ kanpo-viewer/public/ ディレクトリが見つかりません")
with open(json_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
if __name__ == "__main__":
if len(sys.argv) <= 1:
logging.error("引数が不足しています。")
logging.error("目次情報をまとめたリストを引数としてわたす必要があります")
sys.exit(1)
try:
toc_list = ast.literal_eval(sys.argv[1]) # 文字列をリストに変換
except (ValueError, SyntaxError):
logging.error("引数が読み込めませんでした。")
sys.exit(1)
for item in toc_list:
page_title = item["page_title"]
toc_infos = item["toc_infos"]
for infos in toc_infos:
categories = []
for toc_item in infos:
toc_item_value = toc_item["value"]
if not toc_item_value:
logging.warning("目次の値が空です。スキップします。")
continue
# カテゴリを抽出 (リストの順番通りにh2,h3,h4,ulの順番を想定してカテゴリを抽出する)
# 文字列の場合はカテゴリとして扱う
if isinstance(toc_item_value, str):
categories.append(toc_item_value)
# リストの場合、各要素ごとにアイテムを作成
if isinstance(toc_item_value, list):
# アイテムリストが一つで、カテゴリがまだない場合はカテゴリを追加
if len(toc_item_value) == 1 and len(categories) == 0:
categories.append(toc_item_value[0]["link_title"])
for temp in toc_item_value:
make_item(page_title, temp, categories)
categories = [] # リストを初期化
logging.info("RSSフィードが更新されました")