何もわからない

よくわからん

Discord BotにNHKトップニュースをとってきてもらえるようにした話

はじめに

身内で使ってるDiscordサーバに最近(1週間ほど前)Botを追加しました.
以下は現在Pythonで実装されているBotの機能です.
1. DiscordにBotがログインする(最低限の機能).
2. ボイスチャネルの人数が0から1になったとき,「〇〇(初めに入室した人)が通話を開始しました」と指定のテキストチャネルに送る.また,最後の一人が抜けたとき,「〇〇(最後に出た人)が通話を終了しました」と指定のテキストチャネルに送る.
3. 「こんにちは」と送ると「こんにち殺法!」とその画像を返してくる.
4. 「こんにち殺法」から始まる文を送ると「†こんにち殺法返し†」とその画像返す.
5. 10%の確率で自分のこんにち殺法にこんにち殺法返しをしてコンボが決まった画像を送る.
6. 「プニキ」と送るとプーさんのホームランダービーのURLを返す.
7. 「tenhou」と送ると自分たちがいつも使うテンホウの部屋URLを返す.
8. 「自主規制が読みたい」と送るとなろうのURLを返す.
9. 「今日のゲーム」と送るとすでに登録されているリストからゲームを一つ選ぶ.
10. 「ynews」と送るとYahoo!ニュースのトップ記事を5つのタイトルと短縮URLを返す.
Botの機能は,身内の要望(自分たちで書いてほしい)や私の気分で増えています.
今回は,Yahoo!ニュースだけじゃなくてNHKニュースも見たいと思ったため,そのプログラムを書くことになりました.
今回の目的は,NHKトップニュースのタイトルとURLを取ってくることです.

NHKニュース取ってくる

スクレイピング初心者なので,スクレイピングの練習場Yahoo!ニュースで使ったプログラムを参考に作っていくことになります.
まず,NHKニュースのソースを見ましたが
何もわからない...
Yahoo!ニュースさんは,正面のhtmlにURL書いててそこから取ってくるだけだったのに,NHKニュースさんはどこに置いてるんでしょうね.わかんないですね.
困った顔をしながらソースを見ていると39行目のlinkにrel=alternateと書かれたURL(http://k.nhk.jp/knews/)が見つかったのでそちらを見にいくことにしました.こちらのサイトのソースはかなり簡単なもので記事への参照もそのまま貼ってあり,(https://www3.nhk.or.jp/news/html/)にくっつけたら記事へ飛べたのでここからURLをもらうことにしました.以下は使用したソースコードです.

import requests
import time
import urllib.request
from bs4 import BeautifulSoup

def get_nhk_news():
    # ニュース格納用リスト
    news_data = []
    # urlの指定
    url = 'http://k.nhk.jp/knews/index.html'

    # ユーザーエージェントを指定
    ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) '\
     'AppleWebKit/537.36 (KHTML, like Gecko) '\
     'Gecko/20100101 Firefox/60.0 '

    req = urllib.request.Request(url, headers={'User-Agent': ua})

    #htmlの取得
    html = urllib.request.urlopen(req)
    # htmlパース
    soup = BeautifulSoup(html, "html.parser")
    #標準形式のNHKnewsへの変換
    news_path = 'https://www3.nhk.or.jp/news/html/'
    all_a = soup.find_all('a')
    #トップ記事は7個ある
    all_a = all_a[:7]
    for index in (all_a):
        n_url = news_path+index.get('href')
        news_data.append([index.contents[0], n_url])
    return news_data

if __name__ == '__main__':
    get_nhk_news()

urlからhtmlを取得し,パースしてから普通の方のNHKnewsのURLにくっつけて返してもらえるようにしています. 今度は,Bot側に移ります. 以下はBotのプログラムです.

    if message.content.startswith('nnews'):
        news_id = '0000000000000000000'
        news_ch = client.get_channel(news_id)
        await client.send_message(news_ch, "今日のNHKニュースです.")
        # NHKトップのトピック記事を取得
        news_data = nhknews.get_nhk_news()
        top_news = ""
        # URL込みの時
        for news in news_data:
            news[1] = shortenURL.get_shortenURL(news[1])
            top_news += news[0]+'--<'+news[1]+'>--'+'\n'
        await client.send_message(news_ch, top_news)
        

これで,Bot君は記事のタイトルとURLをDiscordに送ってくれるようになりました.やったね. (get_shortenURLはshortenURL.pyにあるbitlyを使った短縮URL生成関数です.)
ここで私は(http://k.nhk.jp/knews/)の記事はほとんど140字以内に収まっていることから,ニュースのURLを貼るよりも簡易版の記事を貼り,urlを後ろにつける方が良いと考えました.以下は簡易版の記事を取ってくるコードです.

import requests
import time
import urllib.request
from bs4 import BeautifulSoup


def get_nhk_news():
    # ニュース格納用リスト
    news_data = []
    # urlの指定
    url = 'http://k.nhk.jp/knews/index.html'

    # ユーザーエージェントを指定
    ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) '\
     'AppleWebKit/537.36 (KHTML, like Gecko) '\
     'Gecko/20100101 Firefox/60.0 '

    req = urllib.request.Request(url, headers={'User-Agent': ua})

    #htmlの取得
    html = urllib.request.urlopen(req)
    # htmlパース
    soup = BeautifulSoup(html, "html.parser")
    #軽く中身だけを表示したいときはこっち
    #簡易型NHKnewsへの変換
    text_data = []
    news_path = 'http://k.nhk.jp/knews/'
    all_a = soup.find_all('a')
    all_a = all_a[:7]
    #対象のURLを獲得
    for index in (all_a):
        n_url = news_path+index.get('href')
        news_data.append([index.contents[0], n_url])
    for data in news_data:
        url = data[1]
        req = urllib.request.Request(url, headers={'User-Agent': ua})
        #htmlの取得
        html = urllib.request.urlopen(req)
        # htmlパース
        soup = BeautifulSoup(html, "html.parser")
        #title取得
        title_text = data[0]
        split_text = soup.text.split("\n")
        for target in range(len(split_text)):
            if(split_text[target].find("。")!=-1):
                main_text = split_text[target]
                date_text = split_text[target+2]+\
                    '--<'+shortenURL.get_shortenURL(data[2])+'>--'
        text_data.append([title_text, main_text, date_text, "-----"])
        time.sleep(1)
        print("*")
    print("-----")
    return text_data, news_data

if __name__ == '__main__':
    get_nhk_news()

先ほどのコードのURLをくっつける先を簡易版に変えて,そこからもう一度スクレイピングするようにしました.私は,対象コードから本文のみを持ってくる手法を知らなかったので,htmlを行ごとにsplitして「。」が含まれる文の行数を検索することにしました(何かいい方法があれば教えてほしい).さらに,本文の2行後に日付情報があったのでついでにとって,text_dataにタイトル・本文・日付(+標準版のURL)・境界線を入れて返すプログラムにしました. time.sleep(1)で1秒に1回アクセスするようにしています.それにより待ち時間が長くなってしまい,プログラムが正常に動いているか気になったのでアスタリスク(*)を表示しています.
これをBot側で受け取り,2重for文でリストを分解してメッセージとして送るように書き換えました.

    if message.content.startswith('nnews'):
        news_id = '000000000000000000'
        news_ch = client.get_channel(news_id)
        await client.send_message(news_ch, "今日のNHKニュースです.")
        # NHKトップのトピック記事を取得
        text_data, news_data = nhknews.get_nhk_news()
        # テキストデータのみの時
        text_ = ""
        for news in text_data:
            for target in news:
                text_ += target+'\n'
        await client.send_message(news_ch, text_)

以下に動作画像を貼ります.

f:id:ihatasi:20190306022755p:plain
Yahoo!ニュースとNHKニュースを流すBot
以上で,今回のDiscord BotNHKトップニュースをとってきてもらった話を終わります.
ありがとうございました.