Googleの検索結果をpecoりたい「Google Fuzzy Search」を作った

## はじめに

peco, percol, fzf, skimとかいろんな`fuzzy find`ツールがある中、 googleの検索結果だけ`fuzzy find`するツールがなかったので作ってみました。

## Google Fuzzy Search とは

![gfzs](https://user-images.githubusercontent.com/11146767/105204059-25556c80-5b87-11eb-862e-828a80aa6a94.png)

- [yukihirop/gfzs](https://github.com/yukihirop/gfzs)
- [jarun/googler](https://github.com/jarun/googler)

とってもシンプルなツールで __「標準入力から受けたjsonをインクリメンタルサーチして、選択したアイテムをブラウザで開くためのツールです。jsonはgooglerで用意したものを使います。」__

百聞は一見にしかずということで動いているものをご覧ください。(__拡大してご覧ください。__)

実行しているコマンドはこれです。

```bash
cat fixtures/tokyo.json | gfzs
```

![gfzs](https://user-images.githubusercontent.com/11146767/105366627-e2f16580-5c42-11eb-92ef-8cd1aa87ce77.gif)

`fixtures/tokyo.json` に `東京` をキーワードにgooglerで調べたものを保存しておいて、それを標準入力として渡して使ってます。 `QUERY>` となっている部分に絞りたい単語 `コロナ` を入れて検索結果を絞り、`Enter` を押してブラウザで開いている様子です。

実際、使ってみたくなったでしょうか？

## 仮想環境でデモを試す

まずはトライアル！

仮想環境を作ることで、ローカルの環境を汚さずに試すことができます。
以下のコマンドを順番に入力して下さい。

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install gfzs
# or
pipx install gfzs

gfzs init
gfzs demo
```

こんな風に表示されたら正しく動いてます。

![image](https://user-images.githubusercontent.com/11146767/105573500-5f9a5600-5da1-11eb-949f-4e84f676953c.png)

閉じるときは、`Ctrl-c` で閉じます。

気にったら `pip  install gfzs` or `pipx install gfzs` で入れて使って貰えると幸いです。

## googlerを使った例

__googlerを使った例を試すには1つお約束事項があります。__

googlerでは検索結果をデフォルトで10件までしか取得できませんが、オプションを渡す事で一応100件とか取得できるように作られています。

だがしかし...

🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
__100件とか1000件とか取得しようとするとGoogle側にIPをブロックされるかもしれない事を忘れないでください。(試してIPがブロックされても責任は負えません。)__
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨

![alert_tape](https://user-images.githubusercontent.com/11146767/105363618-93f60100-5c3f-11eb-8cb9-4f01ec49b486.png)

これは動作確認の時、私がやらかしてしまった時のgooglerのレスポンスをご覧ください。

```bash
$ googler --json -n 1000 東京
[ERROR] Connection blocked due to unusual activity. THIS IS NOT A BUG, please do NOT report it as a bug unless you have specific information that may lead to the development of a workaround. You IP address is temporarily or permanently blocked by Google and requires reCAPTCHA-solving to use the service, which googler is not capable of. Possible causes include issuing too many queries in a short time frame, or operating from a shared / low reputation IP with a history of abuse. Please do NOT use googler for automated scraping.
```

>[エラー]異常なアクティビティが原因で接続がブロックされました。これはバグではありません。回避策の開発につながる可能性のある特定の情報がない限り、バグとして報告しないでください。お客様のIPアドレスはGoogleによって一時的または永続的にブロックされており、サービスを使用するにはreCAPTCHAを解決する必要がありますが、Googlerではできません。考えられる原因には、短期間で発行するクエリが多すぎる、または悪用の履歴がある共有/レピュテーションの低いIPから操作することが含まれます。自動スクレイピングにグーグルを使用しないでください。

という感じでIPがブロックされました。「あぁ... もう一生使えないかも...」って思ったのですが、2〜3日おいたら使えるようになりました。内心良かったーって思いました。😅

という事で...

👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍
googlerがデフォルトで取得してくる件数をオプションでいじれなくした状態で使う事を激しく推奨します。
👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍

![alert_tape](https://user-images.githubusercontent.com/11146767/105363618-93f60100-5c3f-11eb-8cb9-4f01ec49b486.png)

私はこんな感じでaliasを貼って使ってます。

```bash
function gfzs_google_fuzzy_search(){
  # 結果を取得しすぎてロボット判定されないようにgooglerの-nと--countオプションを渡せなくする
  FLAG_N=0
  FLAG_S=0

  GFZS_SCORE=30
  googler_input=()

  for OPT in "$@"
  do
      case $OPT in
          -n | --count)
              FLAG_N=1
              ;;
          -s | --score)
              FLAG_S=1
              GFZS_SCORE=$2
              shift
              ;;
          *)
              googler_input+=($1)
              ;;
      esac
      
      if [ $# -ne 0 ]; then shift; fi
  done

  if [ "$FLAG_N" -eq 1 ]; then
    echo "Can't use -n(--count) option"
  else
    local res

    if [ "$FLAG_S" -eq 1 ]; then
      res=$(googler --json $googler_input | gfzs -s $GFZS_SCORE)
    else
      res=$(googler --json $googler_input | gfzs)
    fi

    if [ "$res" != "" ]; then
      echo $res
    fi
  fi
}
alias ggr=gfzs_google_fuzzy_search

# 自分の場合
function ggr(){
  command=$1
  case $command in
    "abema")
      shift
      googler_input=($@)
      googler_input+=(-w abema.tv)
      gfzs_google_fuzzy_search $googler_input
      ;;
    "hulu")
      shift
      googler_input=($@)
      googler_input+=(-w hulu.jp)
      gfzs_google_fuzzy_search $googler_input
      ;;
    "amazon")
      shift
      googler_input=($@)
      googler_input+=(-w amazon.co.jp)
      gfzs_google_fuzzy_search $googler_input
      ;;
    "github")
      shift
      googler_input=($@)
      googler_input+=(-w github.com)
      gfzs_google_fuzzy_search $googler_input
      ;;
    "zenn")
      shift
      googler_input=($@)
      googler_input+=(-w zenn.dev)
      gfzs_google_fuzzy_search $googler_input
      ;;
    "qiita")
      shift
      googler_input=($@)
      googler_input+=(-w qiita.com)
      gfzs_google_fuzzy_search $googler_input
      ;;
    "gem")
      shift
      googler_input=($@)
      googler_input+=(-w rubygems.org)
      gfzs_google_fuzzy_search $googler_input
      ;;
    "npm")
      shift
      googler_input=($@)
      googler_input+=(-w npmjs.com)
      gfzs_google_fuzzy_search $googler_input
      ;;
    "pypi")
      shift
      googler_input=($@)
      googler_input+=(-w pypi.org)
      gfzs_google_fuzzy_search $googler_input
      ;;
    "crates")
      shift
      googler_input=($@)
      googler_input+=(-w crates.io)
      gfzs_google_fuzzy_search $googler_input
      ;;
    *)
      gfzs_google_fuzzy_search $@
      ;;
  esac
}
```

使う時はこんな感じです。 
typoしてても大丈夫です。googlerが勝手にそこらへん良しなにしてくれます。


```bash
ggr -s 30 python machne lerning
```

`Ctrl-C` を押して閉じてみましょう。

```
$ ggr python machne lerning
** Showing results for python machine learning; use -x, --exact for an exact search.
```

typoしてたから、「`python machine learning`」で探してみたよって言ってますね。
素晴らしい！

## コマンドに関して

使えるコマンドは `gfzs -h` で確認できます。
ここまでで二つほど使ってないコマンドがあります。

- `gfzs edit`
- `gfzs valid`

### edit

`edit` コマンドは、初期化の際、作られた設定ファイル(`~/.gfzrc`)を編集するために使われるコマンドです。`EDITOR` という環境変数にエディタを開くためのコマンドを設定して下さい。

例)

```bash
export EDITOR=code  # vscodeで開く場合
gfzs edit
```

詳しい設定に関しては[こちら](https://github.com/yukihirop/gfzs#-configure)で確認して下さい。

### valid

`valid` コマンドは、`~/.gfzrc` が有効な設定ファイルかどうかを調べるためのコマンドです。

```bash
# 有効な場合
$ gfzs valid
Config is valid.
```

無効な場合を試すために、`gfzs edit` で設定ファイルを以下のように修正してみて下さい。

<details>
<summary>コピペ用</summary>
```
{
  "view": {
    "hoge": {},
    "fuga": [],
    "footer": {
      "messageaaa": "QUERY>",
      "color": {
        "message": {
          "text": 8,
          "background": 0,
          "style": "normalaaaaa"
        },
        "hline": {
          "text": 7,
          "background": 0,
          "styleaaaaaaaa": "linkbbbbbbbb"
        }
      }
    },
    "header": {
      "color": {
        "hline": {
          "text": 7,
          "background": 0,
          "style": "normal"
        }
      }
    },
    "search_result": {
      "color": {
        "index": {
          "text": 6,
          "background": 0,
          "style": "normal"
        },
        "title": {
          "text": 2,
          "background": 0,
          "style": "bold"
        },
        "url": {
          "text": 3,
          "background": 0,
          "style": "link"
        },
        "abstract": {
          "text": 7,
          "background": 0,
          "style": "normal"
        },
        "markup_partial": {
          "text": 2,
          "background": 5,
          "style": "normal"
        },
        "markup_char": {
          "text": 1,
          "background": 0,
          "style": "normal"
        }
      }
    },
    "paging": {
      "color": {
        "common": {
          "text": 2,
          "background": 0,
          "style": "bold"
        }
      }
    }
  }
}
```

</details>

この状態でコマンドを試すと以下のように表示されどこがおかしい設定かあたりをつけれるようになってます。

```bash
$ gfzs valid
Config is invalid.
Error: Contains unsupported key.   (key_path, value) = (view.hoge, {}).
Error: Contains unsupported key.   (key_path, value) = (view.fuga, []).
Error: Contains unsupported key.   (key_path, value) = (view.footer.messageaaa, QUERY>).
Error: Contains unsupported value. (key_path, value) = (view.footer.color.message.text, 8).
Error: Contains unsupported value. (key_path, value) = (view.footer.color.message.style, normalaaaaa).
Error: Contains unsupported key.   (key_path, value) = (view.footer.color.hline.styleaaaaaaaa, linkbbbbbbbb).
```

設定がおかしい状態で、`demo` コマンドを試してみましょう。

```bash
$ gfzs demo
Config is invalid.
Error: Contains unsupported key.   (key_path, value) = (view.hoge, {}).
Error: Contains unsupported key.   (key_path, value) = (view.fuga, []).
Error: Contains unsupported key.   (key_path, value) = (view.footer.messageaaa, QUERY>).
Error: Contains unsupported value. (key_path, value) = (view.footer.color.message.text, 8).
Error: Contains unsupported value. (key_path, value) = (view.footer.color.message.style, normalaaaaa).
Error: Contains unsupported key.   (key_path, value) = (view.footer.color.hline.styleaaaaaaaa, linkbbbbbbbb).
```

こんな風に設定がおかしいと起動しないようになってます。

## 実行時オプションに関して

キーワードでの絞りこみには、[fuzzywuzzy](https://github.com/seatgeek/fuzzywuzzy) を使って実装しており、このライブラリーが返してくるスコア〇〇以上の結果だけ画面に表示するための `-s(--score)` オプション(デフォルト値は30)が用意されています。

実際に使ってみたらよくわかります。 

スコアを上げすぎると全く検索に引っかからなくなります。

```bash
gfzs -s 90 demo
```

![image](https://user-images.githubusercontent.com/11146767/105576425-0ee12800-5db6-11eb-8443-91681ab0ab14.png)

逆に下げすぎると全く絞れなくなります。

```bash
gfzs -s 5 demo
```

![image](https://user-images.githubusercontent.com/11146767/105576439-24565200-5db6-11eb-9002-c4250185e538.png)

__なんか欲しいオプションあったら[Issue](https://github.com/yukihirop/gfzs/issues)を英語で起案して下さい。__

## まとめ

Googleの規制が緩くなって、一気に100件くらい取得できるようになったら本当に面白いツールになるかなって思ってます。(望み薄)

簡単に使えるので使ってみて下さい。(^ ^)