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

10 min read読了の目安(約9400字

はじめに

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

Google Fuzzy Search とは

gfzs

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

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

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

cat fixtures/tokyo.json | gfzs

gfzs

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

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

仮想環境でデモを試す

まずはトライアル!

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

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

gfzs init
gfzs demo

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

image

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

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

googlerを使った例

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

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

だがしかし...

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

alert_tape

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

$ 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

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

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が勝手にそこらへん良しなにしてくれます。

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 という環境変数にエディタを開くためのコマンドを設定して下さい。

例)

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

詳しい設定に関してはこちらで確認して下さい。

valid

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

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

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

コピペ用
{
  "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"
        }
      }
    }
  }
}

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

$ 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 コマンドを試してみましょう。

$ 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 を使って実装しており、このライブラリーが返してくるスコア〇〇以上の結果だけ画面に表示するための -s(--score) オプション(デフォルト値は30)が用意されています。

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

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

gfzs -s 90 demo

image

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

gfzs -s 5 demo

image

なんか欲しいオプションあったらIssueを英語で起案して下さい。

まとめ

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

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