Pythonでスクレイピング MLBニュースを自動翻訳してトレンドを追いかけよう

この記事では、Pythonを使ってMLBニュースを自動翻訳して、それを更にDeepL翻訳にかけ、Wordファイルにまとめる作業の自動化を紹介します。

MLBのトレンドを追いかけたい!!

野球好きであれば、NPBだけでなくMLBの最新トレンドを追いかけたいですよね?

日本人選手の移籍情報、新外国人選手の獲得報道など、日本のメディアがまとめた記事だけでなく1次ソースからも情報を得たいと思いますよね?

英語読んだり、サイト遷移したり、めんどくさいよね

ご存じのように、MLBの最新情報は英語で公開されます。

GoogleChromeをお使いの方には、「記事を日本語に翻訳する」機能がありますし、そもそもテキストを翻訳サイトにコピペしてしまえば解決します。

しかし、毎日のニュースのためにサイトをいちいち移動したり、何度もコピペするのは大変ですし、おそらく続きません。

これらを解決するのが、今回開発するものになります。

BeautifulSoupとSelenium

自動化にはPythonを用います。

自動化の中身はスクレイピングという、Webサイトからデータを抽出し、Web上のでデータを保存する技術になります。

スクレイピングには、よく使われるライブラリであるBeautifulSoupSelenium両方を使います

一般的にBeautifulSoupは静的なWebサイト、Seleniumはログインフォームなどを伴う動的なサイトのスクレイピングで利用されます。

ChromeDriverをダウンロードする

Seleniumの利用にはWebブラウザを操作するためのDriverが必要なのでダウンロードしていきます。

基本的にどんなブラウザでも可能ですが、このブログではWeb上に沢山情報があるGoogleChromeを用いて行っていきましょう。

というわけで、Chromedriverをダウンロードしていきます。

Versionの確認

ダウンロードの前に自身が使っているChromeのバージョンを確認しましょう。

確認には下の画像のように、GoogleChromeの左上の縦の点をクリックし、「ヘルプ」の中の「GoogleChromeについて」を選択します。

その後表示されたページに自分が使っているGoogleChromeのバージョンが確認できます。

確認が出来たらChromedriverをダウンロードしてきます。

ダウンロードはこちら「ChromeDriver-WebDriver for Chrome」

ダウンロードができたらZipファイルを解凍して、自動操作させたいPythonファイル(JupyterNotebookでも可)と同じファイルに配置します。

MLBのニュースを収集する候補

準備ができたので、いよいよMLBのニュースを探していきましょう。

基本的には大手サイトであればどこでも良いと思うので、私が勝手に3つピックアップしてみました。

MLB.com 最大手だけど、関係ない記事も多い

最初は、MLB.comです。

MLBの公式サイトで、選手の成績や試合の動画など幅広いコンテンツを提供しています。

今回は最新のニュースが欲しいので、トップページの「Latest News」内の記事を対象としていきます。

ちなみに左側にある「LATEST FREE AGENT NEWS」「HOT STOVE GUIDE」も興味深いですが、こちらはおそらくオフシーズンだけの更新で、シーズンが開幕した際には別のコンテンツに置き換わってしまう可能性があります。

時期によってコードのメンテナンスをするのも面倒なので、なるべく変化がなく解析がシンプルな部分を狙っていきます。

MLB Trade rumors 移籍情報なら間違いなし

次は、MLB Trade rumorsです。

こちらのサイトは、主にMLB関連の移籍情報を扱っているサイトです。FAやトレードはもちろん、NPBに移籍する新外国人選手などニッチな情報もしっかり掲載されている素晴らしいサイトです。

(そして何よりサイトがシンプル!!)

肝心のニュースですが、トップ画面から下にスクロールしていくと、「TOP STORIES」にある記事を取り出していくことにしましょう。

FoxSprots 贔屓のチームを狙い撃ち

上記二つのサイトでMLBの大まかな情報は手に入るでしょう。

しかし、「おれはこのチームが好きだ!!」という熱心なファンの方もいるでしょう。

そんなあなたにおすすめしたいのがFoxSportsです。

FoxSportsではMLB以外のスポーツはもちろん、贔屓の球団に絞って記事を表示できます。

私はカージナルスが好きなので、カージナルスの記事を毎日翻訳していますが、お好きな球団を選んで記事を読むこともできます。

MLBの情報は他にも沢山ありますが、今回のコードでは上記3つのサイトに対応することにします。

もし皆さんが対応して欲しいサイトがあれば、TwitterのDMまでご連絡ください。

プログラムの中身

それでは、いよいよプログラムを書いていきましょう。

サイトを解析してタイトルなどを取得する

スクレイピングを行うためには、Webサイトの構造を解析する必要があります。

同じようなサイトであっても、構造が異なるため全てのサイトでチェックしていきます。

(各サイトごとのコードは関数にまとめて最後に付けます)

MLB.comの場合

まずはMLB.comです。

サイトの解析にはGoogleChrome上で右クリックして「検証」を選択します。

クリックすると、下のようなHTMLコードが表示されます。

この画面で、コード画面の左上の→をクリックして、サイト上をマウスオーバーすると該当する場所をハイライトしてもらえます。

今回は「LATEST NEWS」の記事を取り出したいので、そこにマウスを持って行くと「<li class=”p-headkine-stack__headline”>…</li>」となっており、同じ表示が下に続いていることがわかります。

…の内部を確認したい場合はダブルクリックをすると表示されます。

表示すると、記事の名前と、記事のURLなどの情報が入っています。

MLB Trade Rumorsの場合

Trade Rumorsの場合も同じようにやっていきます。

こちらは、「<h2 class=”entry-title>」の中に記事のURLとタイトルが含まれています。

他の記事にマウスオーバーしてもらえば他の記事でも同様の構造になっているので繰り返し処理で大丈夫そうです。

Fox Sportsの場合

次はFoxSportsです。

こちらのサイトでは、各チームのニュースに絞っているので、特にオフシーズンは1日に更新される記事が少ないと考えられます。

後の翻訳コードを含めると処理の時間が伸びてしまうので、ここでは上から3つまでを取得することにします。

また他のサイトと違って、記事のタイトルとURLが入っている場所が違うのでそれぞれで取得してやる必要があります。

タイトルは、<class=”article-title ff-sb fs-20 lh-1pt25″>、URLは、<class=”news”>内部に記載されているので、まとめてループ文にいれていきます。

記事タイトル取得用のソースコード

ここまでのソースコードはこちらです。

# MLBnews_autotrans_01.py
import time
from bs4 import BeautifulSoup
import requests

def mlbcom_headline(t_list = [], u_list = []):
  """MLB.comの「LATEST NEWS」からタイトルとURLをリスト
  形式で取得する
  入力:なし
  出力:t_list,u_list # タイトルとURLのリスト
  """
    main_url = 'https://www.mlb.com/'
    response = requests.get(main_url)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # 記事の情報が含まれるclassを取得
    contents = soup.find_all(class_="p-headline-stack__headline")
    count = 0
    for content in contents:
            count += 1
            title = content.get_text()
            urls = content.find("a")
            url = urls.get("href")
            t_list.append(title)
            u_list.append(url)

            print(title)
            print(url)
            print()
            if count == 10:
                break
    return t_list,u_list
 

def mlb_trade_rumors_headline(t_list = [], u_list = []):
  """MLB Trade RumorsからタイトルとURLをリスト
  形式で取得する
  入力:なし
  出力:t_list,u_list # タイトルとURLのリスト
  """
    main_url = 'https://www.mlbtraderumors.com/'
    response = requests.get(main_url)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    contents = soup.find_all(class_="entry-title")
    metas = soup.find_all(class_="entry-meta")
    count = 1
    for content, meta in zip(contents, metas):
        count += 1
        title = content.get_text()
        urls = content.find("a")
        url = urls.get("href")
        t_list.append(title)
        u_list.append(url)

        print(title)
        print(url)
        print()
        if count == 10:
            break
    return t_list,u_list

def foxsports_headline(t_list = [], u_list = [], d_list = []):
  """FoxSportsの「LATEST NEWS」からタイトルとURLをリスト
  形式で取得する
  入力:なし
  出力:t_list,u_list # タイトルとURLのリスト
  """
  # 以下のURLを自分の贔屓のチームのURLを入れてもらえばOK
    main_url = 'https://www.foxsports.com/mlb/st-louis-cardinals-team'
    response = requests.get(main_url)
    soup = BeautifulSoup(response.text, 'html.parser')

    contents = soup.find_all(class_="article-title ff-sb fs-20 lh-1pt25")
    dates = soup.find_all(class_="article-time-source ff-n fs-11 cl-gr-7")
    urls = soup.find_all(class_ = "news")


    count = 0
    for content, date, url in zip(contents, dates, urls[2:]):
        count += 1
        title = content.get_text()
        date = date.get_text()
        url = url.get("href")
        t_list.append(title)
        u_list.append(url)
        d_list.append(date)

        print(title)
        print(date)
        print(url)
        print()
        if count == 3:
            break
    return t_list,u_list, d_list
  
# タイトル取得関数を実行するたびにリセットされるので、自動翻訳の処理は
# タイトル取得関数の直後に入れてください
t_list,u_list = mlbcom_headline()

####################################
# 自動翻訳のコードを入れる
####################################

t_list,u_list = mlb_trade_rumors_headline()

####################################
# 自動翻訳のコードを入れる
####################################

t_list,u_list, d_list = foxsports_headline()

####################################
# 自動翻訳のコードを入れる
####################################
  

記事からテキストを収集

次は取得した記事に移って、テキストデータを取得していきます。

ここで注意が必要なのは、まとめサイトのように様々なサイトの記事を集めている場合、遷移した際のWebサイトの構造が違う場合があります。

以下の画像は、左が「MLB.com」で右が「FoxSports」に記載されている記事の中身です。

※FoxSports内にはMLB.comから引用された記事もありますが、今回はその記事はスキップするようにしています。

# MLBnews_autotrans_02.py
import pyperclip as ppc
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from threading import Thread, Lock

def deepl_trans(url,name = "TradeRumors"):
  """
  取得した記事のURLを入力して、記事内部のテキストを取得
  その後DeepLにコピーアンドペーストして翻訳、
  翻訳後のテキストをリストに保存する
  
  """
    main_url = url
    response = requests.get(main_url)
    soup = BeautifulSoup(response.text, 'html.parser')

    if name == "MLB.com":
        main_texts = []
        contents = soup.find(class_="article-item__body")
        contents = contents.find_all("p")
        for content in contents:
            main_text = content.get_text()
            main_texts.append(main_text)
    elif name == "FoxSports":
        main_texts = []
        contents = soup.find(class_="article-content")
        contents = contents.find_all("p")
        for content in contents:
            main_text = content.get_text()
            main_texts.append(main_text)
    else:
        contents = soup.find(class_="entry-content")
        main_text = contents.get_text()
        main_texts = main_text.split("\n")
    
    # chromedriverを起動
    driver = webdriver.Chrome('chromedriver')
    # ()内のURLに移動(ここではgoogleの初期画面)
    driver.get('https://www.deepl.com/translator')

    stextarea = driver.find_element_by_css_selector(
            '.lmt__textarea.lmt__source_textarea.lmt__textarea_base_style')
    ttextarea = driver.find_element_by_css_selector(
            '.lmt__textarea.lmt__target_textarea.lmt__textarea_base_style')
    
    translated_text_list= []
    for sourse_text in main_texts:
    # 本文を切り分けてdeeplに
        ppc.copy(sourse_text)
        stextarea.send_keys(Keys.CONTROL, "v")

        time.sleep(5)
        # deeplから取り出す
        translated_text = ttextarea.get_property("value")
        stextarea.send_keys(Keys.CONTROL, "a")
        stextarea.send_keys(Keys.BACKSPACE)
        translated_text_list.append(translated_text)
        
    return translated_text_list
  

DeepL翻訳を自動化する

次はいよいよDeepLを自動化させていきます。

記事に移動して、記事内のテキストを取得、そのままDeepLに貼り付けて翻訳させるところまで一気にいきます。

DeepLにコピペする際に使うのが、Seleniumです。

SeleniumでアクセスするURLはDeepLのトップページではなく、下のような翻訳画面を指定します。(https://www.deepl.com/ja/translator)

赤線内をfind_elementで指定して、テキストの入力にはsend.keys()を使います。

普段コピペするときは、「Ctrl+S」でクリップボードに保存して「Ctrl+V」で貼り付けを行いますが、この操作をChromeDriverでも実施します。

50行目のppc.copy()でクリップボードにコピー、51行目の(keys.CONTROL,”V”)でクリップボードの中身を貼り付ける操作です。

翻訳後のテキストを取り出す動きは、55行目と56行目で、テキストを入力したエリアを空にするのが57行目です。

ループしてWordファイルにまとめる

最後は、取得したURLをループしてWordファイルにまとめます。

Wordファイルの中身はこんな感じです。

見栄えを気にして、タイトルは見出しとして記入し、その下の行にタイトル更に下に日本語訳した本文を入れています。

一応記事ごとにページを切り分けておきます。

中身が少々複雑になっていますが、text_to_doc関数を実行すると、先ほど解説したdeepl_trans関数も実行されるようになっています。

# MLBnews_autotrans_03.py
import docx
import datetime
# site_name = "MLBcom ,MLBTradeRumors, FoxSports" のいずれかに対応
def text_to_doc(t_list, u_list, site_name):
    
    # 記事ごとに翻訳されるので、本文をリスト形式にまとめる
    te_list = []
    for url in u_list:
        text = deepl_trans(url,name = site_name)
        te_list.append(text)
        print(url)
        print(text)
        print()
    
    
    d_today = datetime.date.today()
    # ファイル名を指定
    word_write_file_name = site_name + "_" + str(d_today) + ".docx"
    # 空のドキュメントを作成
    doc = docx.Document()
    # タイトル、URL、本文をWordファイルに
    for t, u, te in zip(t_list, u_list, te_list):
        doc.add_heading(t, 0)
        doc.add_paragraph(u)
        doc.add_paragraph(te)
        doc.add_page_break()

    doc.save(word_write_file_name)

まとめ

今回は、スクレイピングを使ってMLBの記事を自動で取得して、翻訳してWordファイルにまとめてみました。(長くなってしまい、わかりにくい部分も多かったと思います。)

私自身、MLBについてリサーチする時間がなかなか取れていなかったのですが、こちらで毎日苦労することなく日本語でMLBの最新情報を楽しむことができそうです。

今後は、今回開発したコードのエラーハンドリングを行って、Webサーバー上で定期実行、出力結果をLINE Notifyなどで自分のLINEに送るなどの手を加えれば更にMLB二関する情報収集が捗ることでしょう。

今回記事にまとめることで、自分がテキトーに書いていたコードをかなり整理することができました。

これからも色々な手法を使って、野球ライフをハックしていきたいと思います!