Ashigaruコンピューター道

ソフトウェアの話とか、キャリアの話とか

サイトアクセスをどう伸ばしていくか?

Kindleセール情報サイトを作ったが、まあ作っただけではどんなサイトでもアクセスは増えない。


現状はこんな感じだが、今後の効果の備忘録のためにのせておく。


























自分たち以外のアクセスはほぼないと言っても良さそう。
Webマーケティングの知り合いにヒアリングしてやらなければいけないのは以下。
  • キーワード調査
    • そのキーワードのページを作る 例えばコミック セールなど
    • 被リンク
  • 有名人にメール、Twitterでツールの情報を宣伝してみる
    • 本当にいいツールだったら使ってもらえるかも?
  • 広告はあまりPayしないかも1クリック50円とかかかる
  • Life Hack
これらをやりつつ効果を定期的に見ていく。

nginxによるアクセス制御に関して調べてみた

業務でアクセス制御する必要があったので、どのような解決策があるかを調べてみた。


要件

ある外部システムをWebAPIで叩いて連携する必要があるが、そのシステムはURLごとに2req/sしか叩かれないように制御したい。

例えば
/searchと/user はそれぞれに2req/sになるように制御したい。

この要件を聞いてみて使えそうかな?とおもったnginx調べてみた。

nginx

http limit req moduleでいけるか調べてみる。
ある$keyごとに1req/secondなど柔軟に処理できそうなので、URLごとにリクエストを制御できるかテストしてみる。

検証環境はmac
index.htmlを / と /testの2つに用意
mkdir /usr/local/var/www/test
cp /usr/local/var/www/index.html /usr/local/var/www/test
そこに$uriごとにアクセス制限をかける以下を追加

http {
    limit_req_zone $uri zone=one:10m rate=2r/s;

    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/access.log  main;

    sendfile        on;

    keepalive_timeout  65;

    server {
        limit_req zone=one burst=5;
        listen       8080;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

設定完了したので、nginx再起動。

sudo nginx -s reload

そしてアクセスログを見守りつつ、/ と /testにpararel 4,  request数12で飛ばす。
ab -n 12 -c 4 http://127.0.0.1:8080/index.html & ab -n 12 -c 4 http://127.0.0.1:8080/test/index.html
するとアクセスログは以下のように同じ秒間にはuriごとに2リクエストしかさばいてないので、nginxの方でよしなに遅延させて処理してくれた模様。
[01/May/2017:00:31:23 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:23 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:23 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:24 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:24 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:24 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:24 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:25 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:25 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:25 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:25 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" [01/May/2017:00:31:26 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:26 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:26 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:26 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:27 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:27 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:27 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:27 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:28 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:28 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:28 +0900] "GET /index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
[01/May/2017:00:31:28 +0900] "GET /test/index.html HTTP/1.0" 200 612 "-" "ApacheBench/2.3" "-"
秒間をこえたリクエストを遅延して処理するかどうかはburstが決めているのでそれをいじってエラーにしてみる。

十分これで目的を達成できそうな気がするな。

react-router  を使った遷移の話

キンドルセール情報のサイトでreact-routerを使ってホーム画面と検索画面の遷移を作成したのでメモ。

はまったのは検索画面のURLが直接叩かれても、正しい検索結果を返さなければならないところ、普通にやるとサーバサイドのURLはホーム画面しかないので検索画面のURLを入力しても404になってしまう。

ホーム画面
検索結果画面



そこでreact-routerの処理だけでなく、railsの方にも同じURLを登録すると、ホーム->検索画面といったノーマルな遷移だけでなく、検索画面URLが直接叩かれたり、ブラウザの戻るににも対応できる。

rails側 config/routes.rbに設定しURLを有効にする
ここで重要なのは/booksで受け取ったものはホーム画面と同じパスに渡して、ルーティング処理はフロントのreact-routerに任せるところ。
root 'main#index' # ホーム画面
get '/books', to: 'main#index' # 検索結果画面

react側のルーティング処理 
import React from 'react';
import ReactDOM from 'react-dom';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import Sales from './components/sales.jsx';
import Books from './components/books.jsx';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { IndexRoute, Link, Router, Route, browserHistory } from 'react-router';

export default class Main extends React.Component {

  constructor(props) {
    super(props);
    injectTapEventPlugin();
  }

  render() {
    return (
      <MuiThemeProvider muiTheme={getMuiTheme(lightBaseTheme)}>
        <div>
          <Header query={this.props.location.query}/>
          { this.props.children }
          <Footer />
        </div>
      </MuiThemeProvider>
    );
  }
}
ReactDOM.render((
  <Router history={browserHistory} > 
    <Route path="/" component={Main}>
      <IndexRoute component={Sales} />
      <Route path="/books" component={Books} />
    </Route>
  </Router>

  ), document.getElementById('app'),
);

なかなかWebにぴったりの情報がなくてはまったが、これでうまくいき検索結果がブックマークできるようになった。









EC2の外部接続許可について

セルフプロジェクトとして頑張っている、Kindleセール情報おすすめサイトの開発環境、バッチサーバをAWSに移行したのだが、外部からの接続ではまったのでメモ。

通常サーバを立ち上げたあと、セキュリティの設定iptablesなどいじいじしてポートを開けたりしなければならないのだがEC2ではその必要がなさそうだ。


EC2でサーバを立ち上げた後、開発環境なので

bundle exec rails s -b 0.0.0.0 -p 3000

で開発環境サーバを立ち上げ、外部から接続してみたが繋がらないのでIptables -L で調べてみると何も設定されたない。。。

なんじゃこりゃ?と思ってAWSコンソールを調べてみると、なんのことはない、Security Groupを設定する必要があった。

以下画像のように開けたいポートをSecurity GroupのInboundに追加する必要あり。




AWSやっぱすごい。

Ubuntuスクレイピングサーバの構築

なんかしらの価値ある情報サービスを提供するばあい、

  1. 自分でコンテンツを作成する
  2. コンテンツを集めて加工する
  3. ユーザがコンテンツを提供するフラットフォームを提供

のどれかの手段を取る必要があるが、今回作成したキンドルセール情報の検索サイトは「2」なので、スクレイピングして情報を集めることにした。

結構この手段を使うひとはいると思うので、構築手順を書いておく。

スクレイピングサーバ構築手順

node.js install
 sudo apt-get install nodejs
ruby install
apt install rbenv ruby-build
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
sudo apt-get install -y autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm3 libgdbm-dev ruby-dev  libmysqlclient-dev
mkdir -p "$(rbenv root)/plugins"
git clone https://github.com/rkh/rbenv-update.git "$(rbenv root)/plugins/rbenv-update"
rbenv install 2.3.1 
ruby project deploy
git clone xxxxxxx
sudo apt install ruby-bundler
bundle install
ブラウザ動作環境を整える
chromium install
sudo apt-get install chromium-browser
sudo apt-get install xvfb
chrome-driver install
wget https://chromedriver.storage.googleapis.com/2.26/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
cp chromedriver ~/.rbenv/versions/2.3.1/bin
~/.bash.rcに追加
# rbenv
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
nohup Xvfb :99 -ac -screen 0 1024x768x8 &
export DISPLAY=":99" 

のちにAnsibleで書いて自動化する気持だけは強くもっている。




キンスメの技術スタックの話

シンガポールから帰還後、時間があったのでKindleのセール情報を収集して、検索できるサイトを作成している。

このサイトでどんな技術を使っているか紹介する。自分でサイトを作成するとき興味があるもの、つかってみたいものが遠慮なく使えるのがいいなぁと

とはいいつつ興味があるものを選択しながら、開発スピードが出るものを選ぶのが結構難しい。

サーバ


バッチ

Gem


フロント

  • node.js
  • npm
  • react
  • material-ui
  • awesome-font
  • babel
  • webpack (設定がかなり面倒だった。。。)
フロントは初めてReact, webpackなど使ったので苦労した。フロント周りはまだこれを入れれば設定完了!みたいな至れり尽くせりけいのパッケージがなく自分でどれを組み合わせるか考え、設定ファイルを書くのがしんどい。みんなやることは大して変わらないと思うんだけどなぁ。

徐々にサイトを作る上でハマった点や、サービスのグロースについて書いていければと。




Rubyの継承, include, prepend, extendの違い

Rubyの継承の仕方

Rubyオブジェクト指向言語であるのだけれど、Javaの継承と考え方、方法が違うようなので、メタプログラミング Rubyを読みつつ調べてみた。

Module
RubyにはModuleという概念があり、これはメソッドがまとめられたものである。通常の継承のほかにこのModuleをincludeやprependすることにより、メソッドをクラスに取り込むことができるもよう。

早速試してみる。

通常の継承の例
[1] pry(main)> class MySubClass
[1] pry(main)   def subClass
[1] pry(main)     p "sub class"
[1] pry(main)   end  
[1] pry(main) end  
=> :subClass
[2] pry(main)> class MyClass < MySubClass
[2] pry(main)   def myClass
[2] pry(main)     p "my class"
[2] pry(main)   end  
[2] pry(main) end  
=> :myClass
[3] pry(main)> myclass = MyClass.new
=> #<MyClass:0x0000000d1eb628>
[4] pry(main)> myclass.subClass
"sub class"

=> "sub class"
ふむ、普通にメソッドを継承してる。


includeの例
[2] pry(main)> module MyModule
[2] pry(main)   def myModuleMethod
[2] pry(main)     p "myModuleMethod"
[2] pry(main)   end  
[2] pry(main) end  
=> :myModuleMethod
[3] pry(main)> class MyClass
[3] pry(main)   include MyModule
[3] pry(main)   def myMethod
[3] pry(main)     p "myMethod"
[3] pry(main)   end  
[3] pry(main) end  
=> :myMethod
[4] pry(main)> myClass = MyClass.new
=> #<MyClass:0x0000000bf719e8>
[5] pry(main)> myClass.myModuleMethod
"myModuleMethod"

=> "myModuleMethod"
むむ、こちらも普通にメソッドを継承してる。

prependの例
[3] pry(main)> module MyModule
[3] pry(main)   def myModuleMethod   
[3] pry(main)     p "myModuleMethod"     
[3] pry(main)   end     
[3] pry(main) end  
=> :myModuleMethod
[4] pry(main)> class MyClass
[4] pry(main)   prepend MyModule  
[4] pry(main)   def myClass  
[4] pry(main)     p "my class"    
[4] pry(main)   end    
[4] pry(main) end  
=> :myClass
[5] pry(main)> myclass = MyClass.new
=> #<MyClass:0x0000000cd80e08>
[6] pry(main)> myclass.myModuleMethod
"myModuleMethod"

=> "myModuleMethod"
はい、こちらも普通に継承

extendの例
[1] pry(main)> module MyModule
[1] pry(main)   def myModuleMethod   
[1] pry(main)     p "myModuleMethod"     
[1] pry(main)   end     
[1] pry(main) end  
=> :myModuleMethod
[2] pry(main)> class MyClass
[2] pry(main)   extend MyModule  
[2] pry(main)   def myClass  
[2] pry(main)     p "my class"    
[2] pry(main)   end    
[2] pry(main)* end  
=> :myClass
[3] pry(main)> myclass = MyClass.new
=> #<MyClass:0x0000000d374c88>
[4] pry(main)> myclass.myModuleMethod
NoMethodError: undefined method myModuleMethod' for #&lt;MyClass:0x0000000d374c88&gt;</div><div style="background-color: black; color: whitesmoke; font-family: Ricty; line-height: normal;">from (pry):13:in<main>'
[5] pry(main)> MyClass.myModuleMethod
"myModuleMethod"

=> "myModuleMethod"
おお、extendはクラスメソッドになった!

じゃあinclude と pretend は一体何が違うのかというと、メソッドを呼び出していく順番に違いが出てくるらしい。

Includeの例 クラスの親階層にはいる。
2.1.5 :001 > module MyModule
2.1.5 :002?>     def my_class
2.1.5 :003?>         p "my_class in my module"
2.1.5 :004?>       end
2.1.5 :005?>   end
 => :my_class
2.1.5 :006 > class MyClass
2.1.5 :007?>     include MyModule
2.1.5 :008?>     def my_class
2.1.5 :009?>         p "my class"
2.1.5 :010?>       end
2.1.5 :011?>   end
 => :my_class
2.1.5 :012 > class SubMyClass < MyClass; end
 => nil
2.1.5 :013 > SubMyClass.ancestors
 => [SubMyClass, MyClass, MyModule, Object, Kernel, BasicObject]

2.1.5 :019 > sub_my_class = SubMyClass.new
 => #<SubMyClass:0x000000024ecff8>
2.1.5 :020 > sub_my_class.my_class
"my class"
 => "my class" 
Pretendの例 クラスの子階層の子になる。
2.1.5 :001 > module MyModule
2.1.5 :002?>     def my_class
2.1.5 :003?>         p "my_class in my module"
2.1.5 :004?>       end
2.1.5 :005?>   end
 => :my_class
2.1.5 :006 > class MyClass
2.1.5 :007?>     prepend MyModule
2.1.5 :008?>     def my_class
2.1.5 :009?>         p "my class"
2.1.5 :010?>       end
2.1.5 :011?>   end
 => :my_class
2.1.5 :012 > class SubMyClass < MyClass; end
 => nil
2.1.5 :013 > SubMyClass.ancestors
 => [SubMyClass, MyModule, MyClass, Object, Kernel, BasicObject]
2.1.5 :016 > sub_my_class = SubMyClass.new
 => #<SubMyClass:0x000000011fabc0>
2.1.5 :017 > sub_my_class.my_class
"my_class in my module"
 => "my_class in my module"

SubMyClassから呼び出した時に、PretendだとMyModuleが先にメソッドがあるかどうか走査され、IncludeだとMyClassが先に走査される。