検索順位のチェック

Google検索で自分のサイトがあるキーワードで何番目に来るかというのはサイトを運営する上で非常に重要なデータであり指標だと思うんですが、なかなか簡単で正しい取得方法というのが無いような気がします。

要はブラウザでシークレットアクセスで検索した時の順位がプログラムから取得できればいいんですが、Googleの検索結果ページというのは例外的に「検索エンジンからどう見えるか?」というのを無視した作りになっており(自分自身が世界一の検索エンジンだから)、超速度重視のページ構造になっています。

seleniumやcapybara-webkitやphantomjsでスクレイプするスクリプトを書いたとしてもクラス名がgとか_Rmなんでxpathやcss selectorがすぐ変わりそうで信用できません。

そこでプラグラムから取得する真っ当な方法はGoogle Custom Search APIを使って取ることだと思います。(これが本当にシークレットアクセスでブラウザから取った時と全く同じかどうかはわかりませんが、自分の実用上は誤差の範囲のように思います)

Custom Search APIは無料枠が1日100リクエストかつ1リクエスト辺り10件まで取得可能で100位までなので、1日1回だけ調べるとすると最悪10キーワードまでしか無料でできないことになります。

しかし有料といっても大した金額にはならないですし、Google検索結果は重要なデータなので怖話のダッシュボードを作るときにこのCustom Search APIを使いました。

gem作った

ただ、GoogleのAPIは扱いが面倒なので検索順位が簡単に取れるgemを作りました。

komagata/google-search_rank

$ gem install google-search_rank
client = Google::SearchRank.new(api_key: "xxxx", cse_id: "xxxx")
client.find("怖い話", %r{http://kowabana.jp/.*}) # => 3

怖話は「怖い話」で検索すると今は3番目に出てくるので3が取得できます。

Google Cloud Platform

Google Custom Search APIで検索するにはAPI KeyとCSE ID(CustomSearchEngine ID)が必要です。Google Developers Consoleから下記の要領でProjectとCustom Search Engineを作成してKeyとIDをゲットしてください。

Custom Search APIを有効にする。

公開APIのサーバーキーを作成する。

カスタム検索エンジンを作成し、カスタム検索エンジンID(CSE ID)を取得する。

Google Cloud Platformはちょっと取っ付きづらいですが、Googleの全てのAPIを統一的に扱うことができるので一度慣れれば凄く便利です。

APIを検索するAPIがあって、そこでAPIのエンドポイントを取ってきてから使うみたいな感じになっています。

認証も課金もGoogle関係の全てのサービスが共通のAPIで使えるのは超汎用的ですが、昔の個別のAPIからマイグレーションをする必要が発生した人にとってはとても煩雑に見えることでしょう。

一時期は乱立したgemのgeneratorをbundle gemがほぼ統一。travisのファイルが入ってたり、Code of Conductが入ったり、rspecかminitestか選べたりといろいろ新しくなってました。

% bundle gem google-search_rank
Creating gem 'google-search_rank'...
Do you want to include a code of conduct in gems you generate?
Codes of conduct can increase contributions to your project by contributors who prefer collaborative, safe spaces. You can read more about the code of conduct at contributor-covenat.org. Having a code of conduct means agreeing to the responsibility of enforcing it, so be sure that you are prepared to do that. For suggestions about how to enforce codes of conuct, see bit.ly/coc-enforcement. y/(n): y
Do you want to license your code permissively under the MIT license?
This means that any other developer or company will be legally allowed to use your code for free as long as they admit you created it. You can read more about the MIT license at choosealicense.com/licenses/mit. y/(n): y
Do you want to generate tests with your gem?
Type 'rspec' or 'minitest' to generate those test files now and in the future. rspec/minitest/(none): minitest
      create  google-search_rank/Gemfile
      create  google-search_rank/.gitignore
      create  google-search_rank/lib/google/search_rank.rb
      create  google-search_rank/lib/google/search_rank/version.rb
      create  google-search_rank/google-search_rank.gemspec
      create  google-search_rank/Rakefile
      create  google-search_rank/README.md
      create  google-search_rank/bin/console
      create  google-search_rank/bin/setup
      create  google-search_rank/CODE_OF_CONDUCT.md
      create  google-search_rank/LICENSE.txt
      create  google-search_rank/.travis.yml
      create  google-search_rank/test/minitest_helper.rb
      create  google-search_rank/test/test_google/search_rank.rb
Initializing git repo in /Users/komagata/code/google-search_rank

一回選択したら設定ファイルに書かれるみたいです。

% cat ~/.bundle/config 
---
BUNDLE_GEM__COC: true
BUNDLE_GEM__MIT: true
BUNDLE_GEM__TEST: minitest

もちろん僕はminitestちゃん!

wootheeを見習って他reposからgit submoduleしやすいように祝日データをrubyからyamlにしました。(organization化と言語非依存にrepos化はちょっとしたholiday_jpのようなライブラリではやり過ぎかなと思ってやってません。submodule使えば祝日データの一元管理という要は足りるしね。)

komagata/holiday_jp

僕がgemを作る時ってすごく画期的なアイデアが見つかったとかあんまりないので、ほとんどが仕事上で必要だけど既存のgemが存在しない=日本語やi18n関係ばっかりになっちゃうんですよね。

昔に使ったので我ながら設計が変ですが、テストが合ったのでテストに変更が無いように実装を変えただけです。あとはテストにshoulda-contextとか今日び使わないのでtest-unit3に変えたぐらい。

最近gem作る時ちょっと便利だなと思うのはgemspecとか自動生成系ファイルをコーディング規約に合わせるために手作業しなくても$ rubocop -aでほとんどいけちゃうところ。

railsでいつも下記のようにしてたのでgemにしました。

# lib/seed_helper.rb:
require 'active_record/fixtures'
                               
module SeedHelper              
  def import_fixture(name)
    puts "Import #{name}..."   
    ActiveRecord::FixtureSet.create_fixtures \
      "#{Rails.root}/db/fixtures", name
  end                          
end
# db/seeds.rb:
require 'seed_helper'
include SeedHelper # この2行がウザい

import_fixture :users
import_fixture :posts

seed系のgemはたくさんあるけど俺にはどれもオーバースペックだったので。

simple_seed

komagata/simple_seed

使い方

$ mkdir db/fixtures
$ vi db/fixtures/users.yml
user_1:
  name: Jean Valjean
user_2:
  name: Bishop Myriel
user_3:
  name: Cosette
$ vi db/seeds.rb
import_fixture :users
$ rake db:seed
Import users...

時間を表示する時に、rails標準の〜日前とかではなく、現在時刻を基準に

5月2日 # 同じ年だったら
2日 # 月まで同じだったら
10時20分 # 日まで同じだったら
20分 # 時間まで同じだったら

という感じでコンパクトに表示したいという要件があったのでgemにしました。

komagata/time_compact

active_decoratorは他の類似gemより気持ち良く書けて好きなんですが、時々

「decoratorのmethodが呼べないなあ?」

みたいな時があるのにもかかわらずなんとなく使ってて反省したのでコードを読んでみた。

要はview_assignsというその名の通りのrailsのmethodを使ってview_contextのinstance変数を列挙してActiveRecord系のものだったらdecorateすると。

なるほどなぁ〜。この辺が他の明示的にdecorateする系とは違うピリッと効いてるところなのかな。

それでやっとわかった。

= current_user.foo

とか

= @post.user.foo

とかいうようなassignしてない部分で「あれ、効かないな」となってたわけだ。

俺が使い方をわかってなかっただけなんですが、特に後者は結構でてくると思うけどみんなどうやってるんだろう?

追記:

と思ったらこのPRが正にそれっぽい。

Decorate associations of a decorated object by ronen · Pull Request #8 · amatsuda/active_decorator

association全部decorateするのはやり過ぎ感とかあるのかな?

railsでお決まりのliタグをシンプルに書くsexy_liというgemを作りました。

komagata/sexy_li

Before:

ul
  - @posts.each do |post|
    li
      .id post.id
      .title= post.title

After:

ul= render_li_for @posts

_post.html.slim:

.id= post.id
.title= post.title

という感じです。liに付くclassやid、partial名やlocal変数を決め打ちにしちゃおうというだけです。

dm-validations-i18nがノルウェーのボークモールに対応しました。

add locale for Norwegian bokmaal (nb-NO) by drtortoise · Pull Request #7 · komagata/dm-validations-i18n

何言ってるかわからんかもしれんが俺もです。

東アジアの文字幅を取るeastasianwidthを以前npmに上げましたが、やっぱり怖話で使えそうなので文字列の幅を数字で取るメソッドを追加しました。(半角は1、全角は2みたいに)

javascriptで東アジアの文字幅を取得する - komagata

Ambiguous(曖昧)も含めて東アジアの文字列を考慮した感じで取ります。色んな環境で画面が崩れるようなのでもちゃんと取れるはず。

なんでこんなのが必要なのかというと、要は文字の折り返しを自前で実装するときに、monospaceのフォントで全角分幅を取るのか、半角分幅を取るのかを厳密にわかる必要があるからです。

komagata/eastasianwidth · GitHub

npmで作りましたが結局railsで使うのでgemも作りました。

komagata/eastasianwidth-rails · GitHub

スマホのonclick遅い問題(代わりにtouchstart使う)用のライブラリ、fastclickが定番っぽいので勝手にfastclick-rails作っときました。デフォルトでこうなってほしい。

komagata/fastclick-rails · GitHub

使い方

# Gemfile:
gem 'fastclick-rails'
// app/assets/javascripts/application.js
// require fastclick
# app/assets/javascripts/foo.js.coffee
$ ->
  new FastClick(document.body)

これでスマホの時は勝手にtouchstartになってくれます。300ms違うから体感的にもかなり違う。