dlvr.itの無料プラン廃止を受け、WordPressサイト記事のRSSフィードをPython のfeedparserライブラリで解析、新着記事を抽出する仕組みを自前で構築します。
dlvr.it無料プラン廃止
IFFFTがTwitter連携を有償化したことから、WordPressの新記事をツイートする仕組みを、dlvr.itへ移行したのが昨年。 それも一年ちょっとでまた同じ憂き目に。
2024年8月にサービスプランの見直しと称した無料プランの廃止に伴い、9月から2週間のお試し期間の後、divr.itからツイートされることは本当に無くなりました。
これを受け、RSSフィードを読み込んで新着記事をツイートする仕組みをPythonで組んでみようと思います。なお、本記事ではRSSフィードから新着記事を取り出すところまで。
WordPress RSSフィード設定確認
開発を始める前に、RSSフィードを吐き出すWordPressのフィードに関する設定を確認。管理画面の左メニューの 設定 → 表示設定 を開き、フィードの各投稿に含める内容をデフォルトの 全文 ではなく、 抜粋 にしていることを確認します。
知らずに 全文 のままにしていたら以降の開発デバッグが難儀するほか、コピーサイト防止の点でも 抜粋 に留めるのが望ましいのだそう。
feedparserのインストール
PythonでRSSフィードを取り扱うのに必要なのが、feedparserライブラリです。
今回は、Oracle Cloud上のUbuntu 20.04ベースの仮想マシン上に構築。Python 3.8がインストールされていました。
|
1 2 3 4 5 6 7 8 9 10 |
~$ uname -a Linux ubnxc 5.15.0-1058-oracle #64~20.04.1-Ubuntu SMP Tue Apr 16 06:55:46 UTC 2024 aarch64 aarch64 aarch64 GNU/Linux ~$ lsb_release -a Distributor ID: Ubuntu Description: Ubuntu 20.04.6 LTS Release: 20.04 Codename: focal ~$ python3 --version Python 3.8.10 |
pip自身の更新の後、pipでfeedparserをインストールします。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
~$ python3 -m pip install --upgrade pip Requirement already satisfied: pip in ./.local/lib/python3.8/site-packages (24.0) Collecting pip Downloading pip-24.2-py3-none-any.whl.metadata (3.6 kB) Downloading pip-24.2-py3-none-any.whl (1.8 MB) Installing collected packages: pip Successfully installed pip-24.2 ~$ pip install feedparser Collecting feedparser Downloading feedparser-6.0.11-py3-none-any.whl.metadata (2.4 kB) Collecting sgmllib3k (from feedparser) Downloading sgmllib3k-1.0.0.tar.gz (5.8 kB) Preparing metadata (setup.py) ... done Downloading feedparser-6.0.11-py3-none-any.whl (81 kB) Building wheels for collected packages: sgmllib3k Successfully built sgmllib3k Installing collected packages: sgmllib3k, feedparser Successfully installed feedparser-6.0.11 sgmllib3k-1.0.0 |
feedparserのエラーハンドリング
早速、feedparserを使って自身のサイトのRSSフィードを読んでみたり、正常に取得できなかった場合のエラーハンドリングを試してみるも、何やら不思議な現象に見舞われます。
|
1 2 3 4 5 |
import feedparser feedurl = "https://servercan.net/blog/feed/" feed = feedparser.parse(feedurl) if feed.bozo == 1: print('Feed Error') |
パースの成否は .bozo と .bozo_exception に格納されており、 .bozo が 1 かどうかで成否判定可能とあるのですが、実際は以下の通り。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# feedurl = "https://servercasdadadsadasdnaa.net/blog/feed/" 【ありえないドメイン】 bozo : True bozo_exception : URLError(gaierror(-2, 'Name or service not known')) entries : なし len(entries) : 0 # feedurl = "https://servercan.net/blog/feehogehoge/" 【404ページ】 bozo : 1 bozo_exception : SAXParseException('undefined entity') entries : あり len(entries) : 0 # feedurl = "https://servercan.net/blog/feed/" 【正常】 bozo : True bozo_exception : CharacterEncodingOverride('document declared as us-ascii, but parsed as utf-8') entries : あり len(entries) : 10 |
結局、 .bozo によるハンドリングを諦め、フィードの中身が収められている .entries の長さを見ることにしました。
feedparserで得られるフィードエントリ
実際のコーディングを前に、feedparserで得られるRSSフィードエントリの書式を確認。
|
1 2 3 4 5 6 7 |
>>> pprint.pprint(feed.entries) [{'author': 'ServerCan',............}, {'author': 'ServerCan',............}, . . . {'author': 'ServerCan',............}] |
一つ一つの記事は辞書型で収録されており、 feed.entries はそのリスト型の集合体であることに留意しつつ、スクリプトを組み立てます。
動作フロー
今回作成するスクリプトの動作フローは、大まかに以下の通り。
- ローカルファイル有無・最終更新有無確認 (
os.path.isfile )
→ ファイル有れば最終更新情報読み取り ( pickle.load )
→ ファイル無ければ空っぽの最終更新情報変数定義 - RSSフィード読み込み (
feedparser )
→ 異常なら即バッチ終了 ( if feed.entries == 0 )
→ 初回実行モードでは、最初のエントリをファイル出力して終了( pickle.dump ) - フィードエントリでループ (
feed.entries )
→ エントリの日付( entry.published )と最終更新日付突き合わせ
→ 新しいエントリを別のリスト型変数へ格納(複数になる可能性も考慮) - ループ終了後、新しいエントリを収めたリスト型変数を精査
→ 中身が複数なら、順番をリバース(日付時刻による降順→昇順に)
→ 中身が空っぽなら、新しい記事は無しにつき、即バッチ終了 - 新エントリのリストでループ
→ ツイート(API利用するか、 twikit など)
→ ツイート成功なら、そのエントリをローカルファイルへ上書き( pickle.dump )
このうち本記事では第4項までを記すこととし、実際にツイートする仕組みはいくつかの選択肢より、別途考察しようと思います。
初回実行モード
まず、手元に最終確認記事の情報が無い初回実行モードでは、RSSフィードの最初のエントリを取り出し、ローカルファイルに書き込むだけで終了させます。
|
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 |
#!/usr/bin/env python3.8 # -*- coding: utf-8 -*- import feedparser, pickle, os, sys from datetime import datetime as dt ## VARs feedfile = 'lastfeed.bin' lastfeed = {} feedurl = "https://servercan.net/blog/feed/" dtformat = '%a, %d %b %Y %H:%M:%S %z' ## FUNCs def str2date(str): return dt.strptime(str, dtformat) def pickle_rb(file): with open(file, 'rb') as f: return pickle.load(f) def pickle_wb(file, obj): with open(file, 'wb') as f: pickle.dump(obj, f) ## LOAD LAST FEED FILE IF EXISTS print('batch start at '+dt.now().strftime('%Y-%m-%d %H:%M:%S')) if os.path.isfile(feedfile): print('feed file found, loading...') lastfeed = pickle_rb(feedfile) ## GET RSS FEED feed = feedparser.parse(feedurl) if feed.entries == 0: print('feedparser returns err, aborting...') sys.exit(0) ## INITIAL RUN : SAVE LATEST ENTRY ONLY if len(lastfeed) == 0: print('no feed file fould, creating new.') pickle_wb(feedfile, feed.entries[0]) print('feed file created, batch end.') sys.exit(0) |
更新チェックモード
次回以降は、feedparserで得られたRSSフィードのエントリ群と、ローカルファイルに格納された前回最新エントリの内容を一つ一つ突き合わせ、新しいものがあればそれを抜き出して記事タイトルとリンクを取得します。
|
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 |
## FEED LOOP lastdate = str2date(lastfeed.published) newentries = [] print('loop entries to check new') for idx,entry in enumerate(feed.entries): if lastdate >= str2date(entry.published): print(str(idx)+' '+entry.published+' : OLD') else: print(str(idx)+' '+entry.published+' : NEW') newentries.append(entry) ## SORT NEW ENTRIES IF MORE THAN 1 if len(newentries) > 1: print('more than 1 entries detected. entries list reversing...') newentries.reverse() elif len(newentries) == 0: print('new post NOT found, batch end.'); sys.exit(0) #DEBUG print('New Entries: '+str(len(newentries))) for idx,entry in enumerate(newentries): print(str(idx)+' '+entry.published) ## NEW ENTRIES ACTION for idx,entry in enumerate(newentries): #print(str(idx)+' '+entry.published) msg = '[Fun Scripting 2.0]\n' msg += entry.title + '\n\n' + entry.link print(msg) pickle_wb(feedfile, entry) print('batch end properly at '+dt.now().strftime('%Y-%m-%d %H:%M:%S')) |
実行結果例はこんな感じに。前回取得後、2つの新着記事が検出されました。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
0 Thu, 26 Sep 2024 04:34:05 +0000 : NEW 1 Sun, 22 Sep 2024 04:34:36 +0000 : NEW 2 Wed, 18 Sep 2024 06:00:37 +0000 : OLD 3 Sat, 14 Sep 2024 04:34:09 +0000 : OLD 4 Tue, 10 Sep 2024 04:34:34 +0000 : OLD 5 Fri, 06 Sep 2024 04:34:31 +0000 : OLD 6 Mon, 02 Sep 2024 04:34:34 +0000 : OLD 7 Thu, 29 Aug 2024 04:34:25 +0000 : OLD 8 Sun, 25 Aug 2024 04:34:45 +0000 : OLD 9 Wed, 21 Aug 2024 04:34:32 +0000 : OLD [Fun Scripting 2.0] Thinkpad X1 Carbon Gen3をWindows11へアップグレード https://servercan.net/blog/2024/09/..... [Fun Scripting 2.0] K-9 MailとThunderbirdにおけるHotmail,OutlookのOAuth2対応 https://servercan.net/blog/2024/09/..... |
次回は、この新着記事情報をツイートする仕組みの構築です。




