0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RSS×Python×CustomTkinterでニュースダッシュボード作ってみた

0
Posted at

はじめに

 こんにちは。
 私は2026年6月中旬からBizCodeXというプログラミングスクールでPythonの学習を行っています。スクールで学習したことのアウトプットとして、ニュースダッシュボードを作成しました。なぜニュースダッシュボードにしたかというと、自分が興味のあるニュースだけを見たい、複数のサイトを見て回る手間をなくし効率化したいという思いがあったからです。
 最近、投資に興味があり株関係のニュースを入手したいと考えていたのですが、スクレイピング可能なサイトが見つけられず、また、APIで最新の情報が入手できずということでRSSを使用し、CustomTkinterでダッシュボードを作ることにしました。

使用した技術

・RSS(feedparser)
・CustomTkinter
・datetime
・Zoneinfo
・parsedate_to_datetime
・webbrowser
・slackweb (※slackに通知できるようにしたため)

アプリの紹介

キーワード入れる前の画面



キーワード検索後の画面


上部の枠にキーワードを入れて検索ボタンを押すと、下の枠に取得したニュースが表示されるようになっています。スクロールすることができ、ニュースタイトル、公開日、ニュース概要を乗せて、 [記事を開く]ボタンでリンク先のページに行くことができます。
slackボタンを押すと、最初から5つ目までをslackに送信する仕様になっています。

実装した機能とコード

ここでは実際に書いたコードとそのポイントを書いていきます。
(※ソースコード全文は記事の最後に掲載します。)

前回使用したキーワード保存・表示する

keyword_file='saved_keyword.txt'

keyword_entry=ctk.CTkEntry(
    card,
    width=300
)
keyword_entry.pack(side='left',padx=10)

#キーワード保存
    entered_keyword = keyword_entry.get()

    with open(keyword_file, 'w', encoding='utf-8') as f:
        f.write(entered_keyword)
    

キーワード検索に入力された情報を入手し、textファイルに保存しています。

#前回使用したキーワード読み込み
try:
    with open(keyword_file, 'r', encoding='utf-8') as f:
        saved_keyword = f.read()

    keyword_entry.insert(0, saved_keyword)

except FileNotFoundError:
    pass

textファイルを読み込み、アプリを開いたときにキーワード検索欄に表示します。


サイトによって日本時間にするモジュールを使いわける

news_published=entry.get('published','日付不明')
 
 # 日付を日本時間に変換して表示形式を整える
try:
    dt = parsedate_to_datetime(news_published)
except ValueError:
    dt = datetime.fromisoformat(news_published)
    jst=dt.astimezone(ZoneInfo('Asia/Tokyo'))
    date_text=jst.strftime('%Y/%m/%d %H:%M')

RSSによって日付の形式が異なるため、try-exceptで対応しています。
・parsedate_to_datetime()で変換できる形式
・ISO形式
の2通り。

キーワード入力の文字をカンマごとに区切る

#キーワードを','で区切ってリストに入れる      
keywords = []

for keyword in keyword_entry.get().split(','):
    if keyword.strip():
    keywords.append(keyword.strip().lower())
    

 まず、キーワードの空のリストを作って、for文でappendしていっています。
 この際、lower()を使うことによって、一度大文字を小文字に変換し、'Python'と大文字が入っていても'python'で検索をかけられるようにしています。
 また、strip()をいれることで、'Python 'と空白が入っていても空白を消し、検索できるようにしています。

工夫したこと

・RSSを複数登録できるようにしたこと

urls=['',
     '',
     ''
    ]

とどんどんRSSを登録することができます。


・検索とslack通知にボタンを分けて操作しやすくした
・[記事を開く]でリンク先に飛べるようにし、見やすくした

まとめ

ニュースボードを作ったことで、RSSの使用の仕方、CustomTkinterでのスクロール表示の実装、feedparserで取得したデータの扱い方を新たに学ぶことができました。また、Python基礎や関数、slackの自動通知など復習しながら取り組むことができました。
前回は、ChatGPTに頼ることが多かったですが、今回は自分で考えてコードを書くことが増えて、少しですが自分の成長を実感することができました。アウトプットを重ねることで、自分の引き出しがどんどん増えて強化されると気づくことができたため、今後も定期的にアウトプットの時間を取りたいと思います。


最後まで読んでいただきありがとうございました!

ソースコード全文はこちら
import customtkinter as ctk
import feedparser
import webbrowser
from email.utils import parsedate_to_datetime
from zoneinfo import ZoneInfo
import slackweb
from datetime import datetime


urls=['RSSのリンク']

keyword_file='⚪︎⚪︎.txt'

webhook_url='webhookのURL'



#アプリ設定====================================================================================================
ctk.set_appearance_mode('Light')
window=ctk.CTk()
window.title('ニュースダッシュボード')
window.geometry('1200x750')
window.configure(fg_color="#E4E4DF")

#検索窓========================================================================================================
card=ctk.CTkFrame(
    window,
    width=650,
    height=250,
    corner_radius=20,
    fg_color="white"
)
card.pack(fill='x',padx=100,pady=25)

#キーワード検索欄
keyword_label=ctk.CTkLabel(
    card,
    text=('🔍キーワード'),
    text_color='Black',
    font=('Arial', 16 )
)
keyword_label.pack(side='left',padx=60)

keyword_entry=ctk.CTkEntry(
    card,
    width=300
)
keyword_entry.pack(side='left',padx=10)

#前回使用したキーワード読み込み
try:
    with open(keyword_file, 'r', encoding='utf-8') as f:
        saved_keyword = f.read()

    keyword_entry.insert(0, saved_keyword)

except FileNotFoundError:
    pass


#ニュース表示窓================================================================================================
card2=ctk.CTkScrollableFrame(
    window,
    width=650,
    height=600,
    corner_radius=20,
    fg_color="white"
)
card2.pack(fill='x',padx=100,pady=25)


#ニュースを検索する
def search_news():

    #検索した結果を消す
    for widget in card2.winfo_children():
        widget.destroy()


    #キーワード保存
    entered_keyword = keyword_entry.get()

    with open(keyword_file, 'w', encoding='utf-8') as f:
        f.write(entered_keyword)
    
    #キーワードを','で区切ってリストに入れる      
    keywords = []

    for keyword in keyword_entry.get().split(','):
        if keyword.strip():
            keywords.append(keyword.strip().lower())
    
    global matched_news
    matched_news = []

    #RSSを1つずつ解析
    for url in urls:
        feed=feedparser.parse(url)

        #タイトル,日付,概要,リンクを取得
        for entry in feed.entries[:30]:
            news_title=entry.get('title','タイトルなし')
            news_published=entry.get('published','日付不明')
            summary=entry.get('summary','概要はありません')
            link_url=entry.get('link','')
            target_text=(news_title + summary).lower()

            #キーワードで絞り込み
            for keyword in keywords:
                if keyword in target_text:

                    news_card=ctk.CTkFrame(card2,fg_color=('#F8FAF8'))
                    news_card.pack(fill='x',padx=10,pady=5)

                    title=ctk.CTkLabel(
                        news_card,
                        text=(f'📰{news_title}'),
                        font=('Arial',14,'bold')
                    )
                    title.pack(anchor='w')

                    # 日付を日本時間に変換して表示形式を整える
                    try:
                        dt = parsedate_to_datetime(news_published)
                    except ValueError:
                        dt = datetime.fromisoformat(news_published)
                    jst=dt.astimezone(ZoneInfo('Asia/Tokyo'))
                    date_text=jst.strftime('%Y/%m/%d %H:%M')

                    news_day=ctk.CTkLabel(
                        news_card,
                        text=(f'📅{date_text}'),
                        text_color='Gray'
                    )
                    news_day.pack(anchor='w',padx=10,pady=5)

                    news_summary=ctk.CTkLabel(
                        news_card,
                        text=(summary),
                        wraplength=700,
                        justify='left'

                    )
                    news_summary.pack(anchor='w',padx=10,pady=5)
                    
                    if link_url:
                        news_link=ctk.CTkButton(
                            news_card,
                            text=('[記事を開く]'),
                            command=lambda link=link_url:webbrowser.open(link)
                        )
                        news_link.pack(anchor='w',pady=5)
                    
                        #検索したニュースをmatched_newsに入れる    
                        matched_news.append({
                            "title": news_title,
                            "link": link_url
                        })

                        break
#=============================================================================================================

#slackに通知する
def send_slack():
    #matched_newsに何もなかったら処理をやめる
    if not matched_news:
        return                        
    slack=slackweb.Slack(url=webhook_url)
    text = "【ニュース通知】\n\n"

    #5件まで通知する
    for entry in matched_news[:5]:
        text += f"📰 {entry['title']}\n"
        text += f"{entry['link']}\n\n"
                            
    slack.notify(text=text)


#slack通知ボタン
send_button=ctk.CTkButton(
    card,
    text=('slackに通知'),
    font=('Arial',16),
    command=send_slack,
    fg_color='#8D3ED9'
)
send_button.pack(side='right',padx=10)

#検索ボタン
search_button=ctk.CTkButton(
    card,
    text=('検索'),
    font=('Arial',16),
    command=search_news    
)
search_button.pack(side='right',padx=10)


window.mainloop()
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?