AWS の What's New(英語版)を自動翻訳する
AWS では、What's New として最新発表ページが用意されていますが、公式での日本語翻訳 は数日遅れるため、最新を追うためには、英語のページを見ておくことが最良だと思っています。
思っているのですが、英語へのハードルが高いので、RSS から当日公開されているページを拾い、Amazon Translate を使って自動翻訳した翻訳文と原文を一つの HTML ページに仕上げ、静的ウェブホスティングをしている S3 バケットへアップロードする仕組みを作ってみました。
ついでに、似非 Podcast 的に利用できればいいなと思い、翻訳文を Amazon Polly を利用して簡易的な読み上げファイルを作成し、翻訳ページ内で再生できるようにしています。
出来上がったページ
AWS 最新情報(自動翻訳版)
トップページ
通知の個別ページ
当初、テーブル部分が長くなる懸念があったが、jQuery の DataTables プラグイン を利用した検索とソート、ページネーションを導入して解決。
処理の概要
実装内容
以前のブログ でもやっていたように、内部の処理は bash で作成しています。
今回は、シェルスクリプトの中で AWS CLI を利用しているので、AWS CLI を利用可能な Lambda レイヤーの作成を、以下のサイトを参考に作成しました。
参考にさせていただいたサイト
Lambda レイヤーにライブラリを追加する ( AWS CLI 編) | Grasswake‘s Blog
Lambda レイヤーの作成
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 # 各種作業ディレクトリの作成
mkdir ~/aws_cli_layer
cd ~/aws_cli_layer
mkdir aws-cli aws-bin bin
# AWS CLI のインストールファイルのダウンロード
wget https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip
# ファイルの解凍
unzip awscli-exe-linux-x86_64.zip
# 作業用ディレクトリへのインストール
./aws/install -i ~/aws_cli_layer/aws-cli -b ~/aws_cli_layer/aws-bin
# aws コマンドのバイナリーファイルなどが入ったディレクトリの中身を全てコピー
cp -r ./aws-cli/v2/current/dist/* ./bin
# 必要なファイルを格納した bin ディレクトリを zip ファイル化
cd ~/aws_cli_layer
zip -r9 aws_cli_layer.zip bin
# 上記までで作成した aws_cli_layer.zip から Lambdaレイヤーを作成
aws lambda publish-layer-version --layer-name aws_cli_layer \
--zip-file fileb://aws_cli_layer.zip
--compatible-runtimes provided.al2
通常、上記の aws lambda publish-layer-version
サブコマンドの実行で、Lambda レイヤーの作成ができるが、zip ファイルの容量が大きく、エラー発生したため、以下の方法で、S3 バケットから読み込ませる方法で対応しました。
aws lambda publish-layer-version --layer-name aws_cli_layer \
--content S3Bucket = ${ S3_BUCKET_NAME } ,S3Key= aws_cli_layer.zip \
--compatible-runtimes provided.al2
bootstrap の作成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 #!/bin/sh
set -euo pipefail
# Initialization - load function handler
source $LAMBDA_TASK_ROOT /" $( echo $_HANDLER | cut -d. -f1) .sh"
# Processing
while true; do
HEADERS = " $( mktemp) "
# Get an event
EVENT_DATA = $( curl -sS -LD " $HEADERS " -X GET "http:// ${ AWS_LAMBDA_RUNTIME_API } /2018-06-01/runtime/invocation/next" )
REQUEST_ID = $( grep -Fi Lambda-Runtime-Aws-Request-Id " $HEADERS " | tr -d '[:space:]' | cut -d: -f2)
# Execute the handler function from the script
RESPONSE = $($( echo " $_HANDLER " | cut -d. -f2) " $EVENT_DATA " )
# Send the response
curl -X POST "http:// ${ AWS_LAMBDA_RUNTIME_API } /2018-06-01/runtime/invocation/ $REQUEST_ID /response" -d " $RESPONSE "
done
処理プログラムの作成(functions.sh)
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117 function handler() {
EVENT_DATA = $1
echo " $EVENT_DATA " 1 >& 2
RESPONSE = $( #!/bin/sh
cd /tmp
curl https://aws.amazon.com/about-aws/whats-new/recent/feed/ > tmp.xml
xmllint --format tmp.xml > whatsnew.xml
NUM = $( cat whatsnew.xml | grep "<item>" | wc -l)
aws s3 cp s3://${ S3_BUCKET_NAME } /index.html .
mkdir -p audio
export LANG = C
DATE = $( date +'%a, %d %b %Y' --date '1 day ago' )
FILE_DATE = $( date +'%Y%m%d' --date '1 day ago' )
cat << EOF > template.html
<html lang="ja">
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link href="./css/common.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src='https://code.jquery.com/jquery-3.4.1.min.js'></script>
<script src="./js/common.js"></script>
<title>TITLE</title>
<body>
<header><h1><a href="./index.html">AWS 最新情報(自動翻訳版)</a></h1></header>
<div class="container">
EN
<h3>JP_TIT</h3>
<div class="jp-text">
EOF
for (( i = 1 ; i < ${ NUM } ; i++) ) ; do
XMLDATE = $( xmllint --xpath "//item[ ${ i } ]/pubDate" whatsnew.xml | sed 's/<[^>]*>//g' )
if [[ " ${ XMLDATE } " = ~ " ${ DATE } " ]] ; then
URL = $( xmllint --xpath "//item[ ${ i } ]/link" whatsnew.xml | sed 's/<[^>]*>//g' )
FILENAME = $( echo $URL | awk -F '/' '{print $(NF-1)}' )
curl ${ URL } > ${ FILENAME } -tmp.html
EN = $( cat ${ FILENAME } -tmp.html | \
tr '\r\n' '\t' | \
sed -e 's/\t//g' | \
grep -oP '<main id="aws-page-content-main" role="main" tabindex="-1">.*?</main>' )
UP_DATE = $( cat ${ FILENAME } -tmp.html | \
tr '\r\n' '\t' | \
sed -e 's/\t//g' | \
grep -oP '<span class="date">.*?</span>' \
| sed 's/<[^>]*>//g' )
TITLE = $( cat ${ FILENAME } -tmp.html | \
tr '\r\n' '\t' | \
sed -e 's/\t//g' | \
grep -oP '<h1 id=".*?"> <a name=".*?">.*?</a> </h1>' \
| sed 's/<[^>]*>//g' )
JP_WITHOUT_TAG = $( cat ${ FILENAME } -tmp.html | \
tr '\r\n' '\t' | \
sed -e 's/\t//g' | \
grep -oP '<main id="aws-page-content-main" role="main" tabindex="-1">.*?</main>' | \
grep -oP '<div class="aws-text-box">.*?</div>' \
| sed 's/<[^>]*>//g' )
JP = $( aws translate translate-text \
--source-language-code "en" \
--target-language-code "ja" \
--text " ${ JP_WITHOUT_TAG } " \
--query 'TranslatedText' \
--output text)
JP_TITLE = $( aws translate translate-text \
--source-language-code "en" \
--target-language-code "ja" \
--text " ${ TITLE } " \
--query 'TranslatedText' \
--output text)
cat template.html | \
sed -e "s|EN| ${ EN } |g" | \
sed -e "s|TITLE| ${ TITLE } |g" > ${ FILE_DATE } -${ FILENAME } .html
echo ${ JP } "</div>" >> ${ FILE_DATE } -${ FILENAME } .html
JP_WITHOUT_TAG_FOR_POLLY = $( cat ${ FILE_DATE } -${ FILENAME } .html | \
tr '\r\n' '\t' | \
sed -e 's/\t//g' | \
grep -oP '<div class="jp-text">.*?</div>' \
| sed 's/<[^>]*>//g' )
aws polly synthesize-speech \
--text " ${ JP_WITHOUT_TAG_FOR_POLLY } " \
--voice-id Mizuki \
--output-format mp3 ./audio/${ FILE_DATE } -${ FILENAME } .mp3
AUDIO = $( printf '<audio controls="controls"><source type="audio/mpeg" src="./audio/' %s'"><a href="./audio/' %s'">./audio/' %s'</a></audio></div><footer>Created by <a href="https://twitter.com/kazzpapa3" target="_blank">@kazzpapa3</a></footer></body></html>' " ${ FILE_DATE } - ${ FILENAME } .mp3" " ${ FILE_DATE } - ${ FILENAME } .mp3" " ${ FILE_DATE } - ${ FILENAME } .mp3" )
echo ${ AUDIO } >> ${ FILE_DATE } -${ FILENAME } .html
LINK = $( printf '<tr><td>%s</td><td><a href="./' %s'">%s<br>%s</a></td><td><audio controls="controls"><source type="audio/mpeg" src="./audio/' %s'"><a href="./audio/' %s'">./audio/' %s'</a></audio></td></tr>' " ${ UP_DATE } " " ${ FILE_DATE } - ${ FILENAME } .html" " ${ TITLE } " " ${ JP_TITLE } " " ${ FILE_DATE } - ${ FILENAME } .mp3" " ${ FILE_DATE } - ${ FILENAME } .mp3" " ${ FILE_DATE } - ${ FILENAME } .mp3" )
sed -i "s|JP_TIT| ${ JP_TITLE } |g" ${ FILE_DATE } -${ FILENAME } .html
sed -i "22a ${ LINK } " index.html
aws s3 cp ./audio/${ FILE_DATE } -${ FILENAME } .mp3 s3://${ S3_BUCKET_NAME } /audio/
aws s3 cp ${ FILE_DATE } -${ FILENAME } .html s3://${ S3_BUCKET_NAME } /
aws s3 cp index.html s3://${ S3_BUCKET_NAME } /
else
break
fi
done
)
echo $RESPONSE
}
あとは、必要な権限のある IAM ロール、Lambda 関数を作成し、トリガーとして EventBridge の Cron 式で、毎日午前 8時(日本時間)に実行するようにしています。
今後やりたいこと
音声ファイル、および翻訳文ページを紹介するツイートの自動化(かつ、なるべくなら bash でやりたい。)
このブログと同様に、CloudFront + S3 バケットでの静的ウェブホスティングに移行して HTTPS 化したいが、mp3 ファイルなどのキャッシュ具合など、CloudFront での課金発生がどの程度になるか読めずちょっと怖いので、一旦保留。
Please enable JavaScript to view the comments powered by Disqus.