brew cask installでインストールできるアプリをランダムに表示するやつ

# あらまし

*homebrew* は主にMac向けのシステムです。

ジャンルはパッケージ管理になるのかな？

*Formulae* と呼ばれるアプリというかライブラリというかパッケージというかをターミナルから簡単にインストールできるソフトウェアです。

そんな homebrew には *homebrew-cask* という親戚がいて、こちらはいわゆるアプリ、アプリケーションフォルダに入れたりDockにアイコンが並んだりするアプリをインストールするのに使います。

homebrew-cask の Formulae は [こちら](https://formulae.brew.sh/cask/) から一覧できるのですが、

名前とバージョン番号以外の情報が無いのでスクレイピングで補ってみました。

完成したのがこちらです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92027/806a8bcf-5a17-b780-b83c-11d4cec81f9e.png)

[Awesome Brew Cask (https://mi-ki-ri.github.io/awesome-brew-cask-site/)](https://mi-ki-ri.github.io/awesome-brew-cask-site/)

[Github](https://github.com/mi-ki-ri/awesome-brew-cask-site)

# スクレイピング部分( BeautifulSoup )

まず、[formulae一覧ページ](https://formulae.brew.sh/cask/) からリンクされていた JSON API を叩きました。

この時点では *BeautifulSoup* ではなく、普通の requests ライブラリを使用しています。

JSON ファイルの中には Formulae 情報のオブジェクトが配列になって入っていました。

その中で、 **homepage** というキーに URL が格納されていたので、それを BeautifulSoup で取ってくる処理です。

JSONファイルのイメージは

```json
[
  {token:"hogehoge", name:["hogehoge"], homepage: "https://hogehoge/"},
  {token:"fugafuga", name:["fugafuga"], homepage: "https://fugafuga/"}
]
```

こんな感じです。

取ってくる処理は簡単で、

```python
import requests

casks = "https://formulae.brew.sh/api/cask.json"
casks_get = requests.get(casks)

for cask in casks_get.json():
  # 個別処理
```

こんな感じ。

この辺は気軽にできて Python はいいなと思いました。

で、ループ内で BeautifulSoup を用いて、

- meta タグの description 情報
- title タグ

を取ってくることにしました。

homepage キーがセットされてない場合なのか、エラーが出ることもあったのでエラーをキャッチする文も書きました。

こんな感じ。

```python

import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

from bs4 import BeautifulSoup

try:
  url_get = requests.get(url, verify=False)

  except Exception as exc:
    print(exc)
  else:
    if url_get.status_code == 200 or url_get.status_code == "200":
      html = url_get.content
      soup = BeautifulSoup(html, "html.parser")
     
      # タグ解析処理
```

`requests.packages.urllib3.disable_warnings(InsecureRequestWarning)` はワーニングの表示を抑制するためのものです。

こういう書き方ができるのを初めて知りました。

Python 部分の最後では、

name, token, url, title( BeautifulSoup 由来 ), meta( BeautifulSoup 由来 )の5つの情報を *md* ファイルに保存しました。

# hugo 部分

md ファイルが揃ったら( 2時間はかかったと思います )、静的サイトジェネレーターの *hugo* を用いてフロントを作ります。

「検索できる・パラパラと一覧できる」という要素が揃ったテーマを探したのですがちょうどいいのがなかったので、自作になりました。

( .gitmodule ファイルにその痕跡がある…… )

hugo の場合、テーマの自作は `hugo new theme themename` を入力して、出力されたファイルを元に改造していく形になります。

`themeフォルダ/layouts/_default/` にある、

- baseof.html
- list.html
- single.html

の3ファイルに必要に応じて読み込み先のファイルなどを追加していく形です。

が、今回はlistをスルーしてbaseofにリストの役割を負わせたり、

singleが都合によりベタ書きになったりしていて参考にはなりません。

参考になりそうなのは、検索機能に *Lunr* というライブラリを使った点。

予めインデックスファイルを作成してそれを読み出して検索する方式で、

[hugo-lunr](https://www.npmjs.com/package/hugo-lunr) という npm ライブラリを使えば簡単にインデックスファイルを作れます。

そして[公式サイト](https://lunrjs.com/guides/getting_started.html)を参考に、

```js
let documents = [];
let idx = null;
await axios
.get("{INDEX_FILE_PATH}")
.then((res) => {
  documents = res.data;
  idx = lunr(function () {
  this.ref("uri");
  this.field("content");

  documents.forEach(function (doc) {
    this.add(doc);
  }, this);
  });

});

let sch = idx.search("{SEARCH_WORD}");
```

大体こんな感じで実装しました。

検索が軽快なのがいいです。

## あとは公開

公開は *github pages* にしました。

公開までに細かい調整が必要だったので、hugo-lunr や hugo を気楽に叩けるように、

package.json に次のような行を加えました。

```json
 "scripts": {
    "fetch": "python app.py",
    "fetch3": "python3 app.py",
    "build": "rm -rf docs && hugo-lunr -i \"content/docs/**\" -o \"{{INDEX_FILE_PATH}}\" &&  hugo && mv public docs"
  }
```

これでコンソールから `npm run fetch3` とすれば python3 が動いてスクレイピングをし、

`npm run build` とすればインデックスファイルの作成、 hugo のビルド、フォルダ名の変更を一気にできます。

あとは git でコミット、プッシュをすれば数分後には github pages に上がっています。

便利です。

## 注意事項

スクレイピングには一般的に迷惑がかからないよう注意が必要です。

**サーバー負荷への配慮**, **個人利用**, **利用規約の遵守** あたりがキーワードのようで、

サーバー負荷については一つのサイトへの集中的なスクレイピングではなく、

多くのサイトへの並列的なスクレイピングだったのでおそらく大丈夫なのですが、

個人サイトでの公開が個人利用なのかについてはいまいち確信が持てていません。

なので消えてたら苦情が入ったものと思ってください。

## 終わり

長かった！

いずれ今回のサイトを利用しての「brew caskで入れられるおすすめのアプリ」的な記事も書こうと思います。