Alfredのワークフローなどで、スクリプトにzshをはじめました。初心者用にメモを残します
やりたいこと
GoogleニュースのRSSから記事のタイトルとリンクURLを抽出しましょう
スクレイピングというのですかね
RSSやHTMLを解析して、情報を取得しようというものです
GoogleニュースのRSSを見てください
XMLで書かれています
XMLというのは、タグ付きのテキストデータです
ブラウザでRSSが見れますが
タグで整形されていたり、色付けされていたりするので把握しやすいと思います
HTMLに似ていますね
昨今のGoogleニュースはパーソナライズができていて
検索結果からおすすめやら、GPSから地域情報まで表示できたりするのですが、
実は、こちらは解析ができなかったのです。。。
https://news.google.com/topstories?hl=ja&gl=JP&ceid=JP:ja
RSSを取得する(cURL)
RSSのテキストデータは、cURLで取得することができます。
httpsとしてアクセスを提供しているので、HTMLと同様にcURLで取得できるのですね。
コマンドの記述としては、cURLにアドレスを渡すだけでOKです
# このまま実行するとRSSを標準出力に返します
# ターミナルの画面が文字だらけになるのでご注意を
curl 'https://news.google.com/rss?hl=ja&gl=JP&ceid=JP:ja'
テキストを変数に代入することもできます
これでデータとして扱えるようになりますね
# 変数で受けると、下記のような状況が出力されます
rss=`curl 'https://news.google.com/rss?hl=ja&gl=JP&ceid=JP:ja'`
# => % Total % Received % Xferd Average Speed Time Time Time Current
# => Dload Upload Total Spent Left Speed
# =>100 75756 0 75756 0 0 320k 0 --:--:-- --:--:-- --:--:-- 330k
# RSSから情報を収集する(grep,sed)
次は、取得したテキストから、タイトルタグの中身を取り出すためにgrepを使います
具体的には grep -oE '<title>[^<]+</title>'
というコマンドです
オプションの-oE
ですがoはマッチした部分のみを取り出すこと、
Eは拡張正規表現を使うことを意味します(拡張版が必須なのかはわかりません…)
引用符('')で囲まれた正規表現の意味は、<title>
と</title>
に挟まれた『<』以外の連続した文字となります
なお、『<』以外の連続した文字([^<]+)の部分を、連続した文字(.+)と指定すると、
最初の<title>
と、最後の</title>
がマッチして全体で1つのマッチとなってしまいます
それぞれのコマンドを書いておきますので、ターミナルで比較してみてください
正規表現は難しいですね
# grepに『[^<]+』を指定
curl 'https://news.google.com/rss?hl=ja&gl=JP&ceid=JP:ja'|grep -oE '<title>[^<]+</title>'
# grepに『.+』を指定
curl 'https://news.google.com/rss?hl=ja&gl=JP&ceid=JP:ja'|grep -oE '<title>.+</title>'
タイトルを文字データとして扱うので、タグ自体(<title>
や</title>
)はsedの置換で取り除きます
sed 's#<title>\(.*\)</title>#\1#'
置換前は<title>\(.*\)</title>
の部分ですが、バックスラッシュはメタ文字の接頭語なので
無視して<title>(.*)</title>
とするとわかりやすいと思います
grepしたタイトルタグの1つ1つが置換前の文字列となります
そして、置換後は\1ですが、これは置換前の1つ目の括弧の中身を指します
置換前の括弧は1つだけなので、結局『.*』の部分=タグの中身で置き換えられることになります
これを配列に代入します
title=(`echo $rss |sed 's/ //g' | grep -oE '<title>[^<]+</title>'|sed 's#<title>\(.*\)</title>#\1#'`)
配列代入のポイントは
はじめにsed 's/ //g'
として空白を削除している部分です
grepの結果は、複数ある場合は空白で区切られるので
そのまま配列に代入できるのですが
タイトルに空白があると区切り文字と判断されて、配列の数が増えてしまうのです
そのため事前に空白を無くしておきます
タイトル同様にリンク先も抽出しますが
リンクのURLには空白は入らないので、sed 's/ //g'
は省いています
ここでのポイントは、タイトルとリンクでそれぞれの配列で両方の添字が
揃っていることです
そうするとロジックで扱いやすくなります
さてさて、完成です
最後に配列の添字とタイトル、リンクをechoしています
rss=`curl 'https://news.google.com/rss?hl=ja&gl=JP&ceid=JP:ja'`;title=(`echo $rss |sed 's/ //g' |grep -oE '<title>[^<]+</title>'|sed 's#<title>\(.*\)</title>#\1#'`);link=(`echo $rss |grep -oE '<link>[^<]+</link>'|sed 's#<link>\(.*\)</link>#\1#'`);for i in `seq 1 ${#title[@]}`; do echo 'No.'$i; echo ' title='$title[i]; echo ' url='$link[i]; echo; done
雑談
RSSのsearchパラメータを付けることでニュースの検索もできるので試してみました
echo 'ニュース検索のキーワードを入力してね'; read kw;rss=$(curl 'https://news.google.com/rss/search?q='`echo $kw | nkf -WwMQ | tr = %`'&hl=ja&gl=JP&ceid=JP:ja');array=(`echo $rss |sed 's/ //g' |grep -oE '<title>[^<]+</title><link>[^<]+</link>'|sed 's#<title>\(.*\)</title><link>\(.*\)</link>#\1 \2#'`); for i in {1..$((${#array[@]}/2-1))}; do echo 'No.'$i; echo ' title='$array[$((i*2+1))]; echo ' url='$array[$((i*2+2))]; echo; done
細々とした修正をしています
その1
検索ワードを変数kwで受け取り、%エンコードの変換をして
URLのパラメータに追加したのですが、
バックスラッシュのネストがうまくできませんでした
そこで、外側のバックスラッシュ(curlコマンド)を『$(コマンド)』の書式に
変更しました
その2
タイトルタグとリンクタグを別々の配列に代入していましたが、1つの配列にしました
その結果、表示する際に利用する添字を計算するようにしましたが、
かえってわかりにくいコードになってしまったかもしれません
イメージしやすいように配列の変更を例示します
変更前
タイトル配列=(タイトル1 タイトル2 タイトル3 …)
リンク先配列=(リンク先1 リンク先2 リンク先3 …)
変更後
全体配列=(タイトル1 リンク先1 タイトル2 リンク先2 タイトル3 リンク先3 …)
その3
検索結果数がgoogleニュースの仕様で、最大100件のようです
基本的に100件は検索されるようなので、最大数を100に固定して、
ロジックを簡単にしてみたのですが、
モノによっては100件もないものがあるので、結局、配列数から計算式で求めるようにしました