RSSから情報を取得する

:::note info
Alfredのワークフローなどで、スクリプトにzshをはじめました。初心者用にメモを残します
:::

# やりたいこと

GoogleニュースのRSSから記事のタイトルとリンクURLを抽出しましょう
スクレイピングというのですかね
RSSやHTMLを解析して、情報を取得しようというものです

[GoogleニュースのRSS](https://news.google.com/rss?hl=ja&gl=JP&ceid=JP:ja)を見てください
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です

``` zsh
 # このまま実行するとRSSを標準出力に返します
 # ターミナルの画面が文字だらけになるのでご注意を
curl 'https://news.google.com/rss?hl=ja&gl=JP&ceid=JP:ja'
```

テキストを変数に代入することもできます
これでデータとして扱えるようになりますね
```zsh
 # 変数で受けると、下記のような状況が出力されます
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つのマッチとなってしまいます
それぞれのコマンドを書いておきますので、ターミナルで比較してみてください
正規表現は難しいですね

```zsh
 #　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つだけなので、結局『.*』の部分＝タグの中身で置き換えられることになります

これを配列に代入します
```zsh
title=(`echo $rss |sed 's/ //g' | grep -oE '<title>[^<]+</title>'|sed 's#<title>\(.*\)</title>#\1#'`)
```

配列代入のポイントは
はじめに```sed 's/ //g'```として空白を削除している部分です
grepの結果は、複数ある場合は空白で区切られるので
そのまま配列に代入できるのですが
タイトルに空白があると区切り文字と判断されて、配列の数が増えてしまうのです
そのため事前に空白を無くしておきます

タイトル同様にリンク先も抽出しますが
リンクのURLには空白は入らないので、```sed 's/ //g'```は省いています

ここでのポイントは、タイトルとリンクでそれぞれの配列で両方の添字が
揃っていることです
そうするとロジックで扱いやすくなります

さてさて、完成です
最後に配列の添字とタイトル、リンクをechoしています
```zsh
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パラメータを付けることでニュースの検索もできるので試してみました

```zsh
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件もないものがあるので、結局、配列数から計算式で求めるようにしました