PythonとGCPで「毎日0時に英語のラジオニュースを文字起こししてメールするシステム」の開発

概要

英語のリスニング練習のためにAmazon musicのポッドキャストでNPR newsというアメリカのラジオ局のニュースを毎朝聞いています。

が、リスニング力が無さすぎて、文字起こしした結果がないと何言ってるかわからん。また毎日自動的に音声データのリンクと一緒にメールで送ってきて欲しい。ということでpythonで作ってみました。

仕様の検討

やることをざっくり整理すると以下の通り。

1)英語ニュース音声(NPR)を
2)英語テキスト起こしして
3)メール送信する
4)1)~3)をスケジュールに従い自動実行する

各要素について詳細を検討していきます。

1)音声データの入手

まずは英語ニュース音声の入手をどうするか。podcastを参照することもできるのかもしれないが、NPRニュースのHPを見ると一部ニュースはHPで音声が公開されているのでこれを使うのが楽そう。

HPの情報なのでウェブスクレイピングで取得する。ウェブスクレイピングは初挑戦なので、ここら辺を参考にしてやります。

https://ai-inter1.com/python-webscraping/

2)テキスト起こし

英語音声からのテキスト起こしはGoogle speech to textを使おうと思ったが、openai APIの文字起こしwhisper-1で簡単にできそうなのでこれを使ってみることにします。

openaiのAPIは GPT3.5turboを使っていて勝手がわかっているのも良い。

https://platform.openai.com/docs/guides/speech-to-text

ここに書いてある通りの数行で実装できます。めっちゃ簡単。

3)メール送信

Pythonからのメール送信も初めてやるのでネット上の情報を参考に作成します。Gmailを使います。

https://gakushikiweblog.com/python-email

4)自動実行

定期的な自動実行はGCP(Google Cloud Platform)でサーバーレス実行させます。過去に職場のTeamsチャネルに定時自動投稿する仕組みを作った時にGCPを使っているので同じ要領でできるはず。やり方はこのHPを参考にします。

https://gammasoft.jp/blog/schdule-running-python-script-by-serverless/

最終的な仕様

最終的に以下の仕様になりました。

1)NPRニュースのトップページをスクレイピングして、音声データへのリンクを取得する。
2)openai APIのwhisper−1で英語テキスト起こしする。
3)Gmailでテキストをメール送信する。
4)1~3の処理をGCP上でスケジュール実行する。

開発

上記の検討結果をもとに実装します。わからないところや、うまくいかないことがあったらネット検索や GPT−4に助けてもらいながら開発しました。

結果

最終的にGCP上で実行するコードです。

import requests
import re
from bs4 import BeautifulSoup
import openai
import smtplib
from email.mime.multipart import  MIMEMultipart
from email.mime.text import MIMEText
#openai APIの認証キー
openai.api_key = "openaiで取得したAPIキーを入力"
url='https://www.npr.org/sections/news/' #スクレイピング先のURL
def main(event, context):
    #「url」のサイトのHTMLを取得する。
    response=requests.get(url)
    #HTMLの内容をbs4で解析する。
    soup=BeautifulSoup(response.text,"html.parser")
    #https://ondemand.npr.org/anon.npr-mp3 があるところにmp3データへのリンクがあることを見つけたので、そこをbs4で探してくる。
    elems=soup.find_all(href=re.compile("https://ondemand.npr.org/anon.npr-mp3"))
    audio_url=elems[0].attrs['href'] #音声データのURLを取得
    #audioのタイトル=記事名を取得する。
    h4_tag = soup.find('h4', {'class': 'audio-module-title'})
    audio_title = h4_tag.text if h4_tag else 'Not Found'
    #音声データを一時保存してファイルパスを作る。
    response=requests.get(audio_url)
    audio_data = response.content
    
    # 一時ファイルのパスを指定
    temp_file_path = '/tmp/temp.mp3'
    # 一時ファイルを開く(書き込みモード)
    with open(temp_file_path, 'wb') as f:
        f.write(audio_data)
    # Whisper APIに音声データを送信し、結果を取得
    response = openai.Audio.transcribe("whisper-1",
    file=open('/tmp/temp.mp3', 'rb'),
    )
    #メール送信部分
    smtp_server = "smtp.gmail.com"
    port = 587
    server = smtplib.SMTP(smtp_server, port)
    server.starttls()
    login_address = "hoge@gmail.com"
    login_password = "アプリパスワードを入力"
    server.login(login_address, login_password)
    message = MIMEMultipart()
    message["Subject"] = "Today's news" #メールの件名
    message["From"] = "hoge@gmail.com" #送信元のアドレス
    message["To"] = "hogehoge@gmail.com" #送信先のアドレス
    text = MIMEText("["+str(audio_title)+"]"+"\n"+"https://www.npr.org/sections/news/"+"\n"+str(audio_url)+"\n"+str(response["text"]))
    message.attach(text)
    server.send_message(message)
    server.quit()
#main()

こんな感じで毎日日付が変わると同時に最新の音声ニュースのリンクと文字起こし結果が送られてきます。

スクレイピングはコーディングよりもHPの構造がどうなっているか、どのように解析すれば欲しい情報に辿りつくか、を調べるのが重要と思いました。

なお上記のコードは「NPRのHPのトップページに音声データ付きのニュースが最低一件絶対にあること」を前提にしてるので、あんまり良くないです。が個人用ならたまに不具合起こしても良いのでこのままで使ってみます。

今のところたまに最新のニュースでなく、古いニュースを拾ってくる点、音声データのタイトルがうまく取得できず別の記事のタイトルがつく時がある点が不具合です。

音声データをwhisperで処理するために、mp3データを一時的に保存するところが手元では動いてもGCP上では動かなかったのですが、 GPT-4に教えてもらってtemp.mp3のファイルパスに「/tmp」を追加することで動くようになりました。ありがとう GPT!

追記:23年5月24日

このあと、結局「日本語訳も欲しいよね」ってなってdeeplで翻訳をかけたテキストも送信するように改変したのですが、ローカルではうまく動いてもGCPからうまく実行できなくなってしまいました。GCPの問題解決に時間がかかりそうだったので、iPhoneでpythonが動かせるpythonistaでiphoneから毎朝手動で実行するように運用を変えています。

iOSから、スケジュール実行できるようにできるはずなのですがこちらもなぜか上手くいってません。。。

追記:23年6月3日

NPRのトップページから音声ファイルをスクレイピングしていましたが、時々数十分以上ある音声ファイルが上がることがあり、対応が困難なため5分間のニュース番組「NPR News Now」のページからスクレイピングすることにしました。

これで安定するならOK。

import requests
import re
from bs4 import BeautifulSoup
import openai
import json

import smtplib
from email.mime.multipart import  MIMEMultipart
from email.mime.text import MIMEText

#openai APIの認証キー
openai.api_key = "認証キーを入力する"
url='https://www.npr.org/podcasts/500005/npr-news-now' #スクレイピング先のURL

def translate_text(text):#deeplによる翻訳部分
    url = "https://api.deepl.com/v2/translate"
    parameters = {
        "auth_key": "deeplのAPIキーを入力",  # ここにあなたのDeepL APIキーを入力してください
        "text": text,
        "target_lang": "JA",
        "source_lang": "EN"
    }
    response = requests.post(url, data=parameters)
    translation = json.loads(response.text)
    return translation['translations'][0]['text']

def main():
    #「url」のサイトのHTMLを取得する。
    response=requests.get(url)
    #HTMLの内容をbs4で解析する。
    soup=BeautifulSoup(response.text,"html.parser")
    #https://play.podtrac.com/npr-500005/edge1.pod.npr.org/anon.npr-mp3/npr/newscasts/ があるところにmp3データへのリンクがあることを見つけたので、そこをbs4で探してくる。
    elems=soup.find_all(href=re.compile("https://play.podtrac.com/npr-500005/edge1.pod.npr.org/anon.npr-mp3/npr/newscasts/"))
    audio_url=elems[0].attrs['href'] #音声データのURLを取得
    print(audio_url)
    #audioのタイトル=記事名を取得する
    #h4_tag = soup.find('h4', {'class': 'audio-module-title'})
    #audio_title = h4_tag.text if h4_tag else 'Not Found'

    #音声データを一時保存してファイルパスを作る。
    response=requests.get(audio_url)
    audio_data = response.content

    with open('temp.mp3', 'wb') as f:
        f.write(audio_data)

    # Whisper APIに音声データを送信し、結果を取得
    response = openai.Audio.transcribe("whisper-1",
    file=open('temp.mp3', 'rb'),
    )

    #メール送信部分
    smtp_server = "smtp.gmail.com"
    port = 587
    server = smtplib.SMTP(smtp_server, port)

    server.starttls()
    login_address = "hoge@gmail.com" #送信メールのログインアドレス
    login_password = "アプリパスワードを入力する"
    server.login(login_address, login_password)
    message = MIMEMultipart()
    message["Subject"] = "Today's news"
    message["From"] = "hoge@gmail.com"
    message["To"] = "hogehoge@gmail.com"
    text = MIMEText(url+"\n"\
        +str(audio_url)+"\n"+str(response["text"])+"\n"+translate_text(response["text"]))
    message.attach(text)
    server.send_message(message)
    server.quit()

print("start")
main()
print("Success")

シェアする

  • このエントリーをはてなブックマークに追加

フォローする