rubyとseleniumでTwitterに自動ログインする。〜リベンジ編
SeleniumでTwitterの自動ブラウザ操作を行っていきたいです。
以前設定していたのですが、しばらくしてみると、そのコードでは対応できなくなっていました。
require 'selenium-webdriver' driver = Selenium::WebDriver.for :firefox driver.get "https://twitter.com/login"
ここまでで以下の画面になります。(firefoxのdriverなどの設定が別途必要だった気がしますが)
このメールアドレスとパスワードに値を自動で挿入してログインボタンをクリックしてログインしたいと思います。
まずは、フォームから。
開発者ツールで、この要素を見てみると、次のようになっています。
要素は
<input class="js-username-field email-input js-initial-focus" name="session[username_or_email]" autocomplete="on" value="" placeholder="電話番号/メールアドレス/ユーザー名" type="text">
です。
ここで、seleniumの書き方に従って、 (classを指定する方法は確か失敗した気がします。。。もしくは一意なclassがなかったか。)
driver.find_element(:name, 'session[username_or_email')..send_key "user_name"
としても、
Selenium::WebDriver::Error::ElementNotInteractableError: Element is not visible
となり、エラーになってしまいました。
qiitaでやってみたらうまく行ったので、TwitterのCSSの問題なのか、それとも単にハッシュみたいな書き方に対応していないのか。
と思ったら、以前書いてたコードでは普通に
driver.find_element(:name, "session[password]").send_key "password"
のような形で書いていたので、おそらくCSS側の問題ですね。
解決策として、xpathを指定してあげたら、どうもうまくいきました。
xpathは開発者ツールで秒速で見つけられます。
我らがfirefoxですね。
...おっと、firefoxではできませんでした。(探せば見つかるはずだけどめんどい)
ということで、我らがchromeで
与沢翼も裸足で逃げ出すくらい秒速でした。
driver.find_element(:xpath, '//*[@id="page-container"]/div/div[1]/form/fieldset/div[1]/input').send_key "user_name" driver.find_element(:xpath, '//*[@id="page-container"]/div/div[1]/form/fieldset/div[2]/input').send_key "password" driver.find_element(:xpath, '//*[@id="page-container"]/div/div[1]/form/div[2]/button').click
こんな感じでログイン成功しました。
あとは煮るなり焼くなり好きにして状態ですね。
自動ファボ&RTのコード下の記事にあります。
SeleniumとRubyでTwitterの複数アカウントを自動ファボ&RT
背景
自分の趣味というか、ちょっとしたプロジェクトでTwitterを運営していて、特定のワードで検索されたときに上位に表示させたいなーと思うことがありました。
一つの方法が、ツイートを頻繁にするということで、それはbotで普通に解決できました。これはノンプログラマーでもできますね
bot化したのはだいぶ前だったので、正確には覚えていませんが、確かココらへんを使ってた思い出があります。というか今も使ってるはずですが忘れて確認するのが面倒くさいだけです。
楽ボッツ - 初心者向き twitter bot(ボット)サービス
ほんといいサービス達です。
で、Twitterで検索したときは、この画面がまず出るわけです。
検索ワードによって少々構成が変わることもあったりはするみたいですが、基本こんな感じで、まず一番に表示されるのは話題のツイートです。
ここではツイートまたはユーザーが表示されます。
僕の独自の調査では、この表示されるのに有効な要素は、
- フォロワー数
- Tweetのファボ数
- TweetのRT数
ですね。普通か。
ということで、ファボ&RT数を増やしたい。
それも自分の内々のアカウント群で。
どうやら調べてみると、なんとかマティックっていう怪しい有料のツールはあるみたいですが、それはなんか使いたくなかったので、自作してそれっぽいことをすることに。
いや〜成長しましたね〜
2年前の僕は必死にそういうサービスがないか検索しまくるのが関の山でした。
ということで、余談おしまいです。
APIは使えないのか?
TwitterにはもちろんAPIがありますが、ざっと調べた感じ、自動ファボはできなかったような気がします…
前はできたみたいなんですけどね
もしかしたら普通にできたかも。
Seleniumとかスクレイピングの練習ってことで!
コード
ちょっと前に実装したので、driverのinstallのあたりで若干苦戦した気もしますが、忘れてしまいました。
基本的には実際にTwitterのページを開いて開発者ツールでHTMLコード見て、ゴリゴリにチューニングしてどうにかこうにか動くって感じです。
結構Twitterは罠が多いです。ログインのところとか結構罠でした。
そういう理由から、チューニングしきれず、ちょいちょいエラーになりそうなのを強引にその後の処理を捨ててるので、精度はそこまで高くないです。
回り続けることを優先しています。
# execute `$ watch -n 1800 ruby path/to/this/project/auto_favo_RT_with_selenium.rb` to run every 30 minutes require "selenium-webdriver" user_names = [ "account1", "account2", "account3" ] for user_name in user_names do driver = Selenium::WebDriver.for :firefox driver.get "https://twitter.com" # ログインする sleep 1 begin driver.find_element(:class, "js-login").click driver.find_element(:name, "session[username_or_email]").send_key "#{user_name}" driver.find_element(:name, "session[password]").send_key "password" #パスワードは全部同じとして driver.find_element(:class, "js-submit").click rescue driver.quit puts "login error" next end puts "login with #{user_name}" # 検索してファボ&RT driver.get "https://twitter.com/search?f=tweets&vertical=default&q=ANYWORD&src=typd" for i in 0..10 do begin driver.find_elements(:class, "js-actionFavorite")[i*2].click driver.find_elements(:class, "js-actionRetweet")[i*2].click driver.find_element(:class, "RetweetDialog-retweetActionLabel").click rescue puts "Error: in #{i}" break end sleep 2 end driver.quit end
同じコードですがgithubに上げときます。 github.com
これをMacのターミナルから実行させときます。
$ watch -n 1800 ruby path/to/this/project/auto_favo_RT_with_selenium.rb
これだと1800秒ごと、30分ごとに実行されます。
これは自分のbotのツイート数と併せてこのくらいの感覚でいいなって思って設定しました。
30分ごとに10個ずつふぁぼっていくので、そのくらいのペースということです。
重複しそうになったら、そこでそのループは終了します。
では、悪用しないで楽しいTwitterライフを送りましょう!!!
課題
一つだけ問題点があって、普通にパソコンを使っていると、30分毎にお気にFirefoxが出現して、最前面でブラウザ操作を行っていくことです。
ちょっとうっとうしい。
次のステップとしては、フォローアンフォローも自動化していきたいです。
あとAWSとか使ってやりたい。
ブラウザ操作なんて不細工なことはしたくないな……
一つのPCから複数のgithubアカウントを使い分けてpushする
一つのPCから複数のgithubアカウントを使い分けてpushする ということをやってみたいと思いました。
背景
というのも、仕事とプライベートみたいな使い分け方じゃなく、開発用のアカウントと、英語学習用のアカウントを使い分けたいなと思ったからです。
日々の学んだことや、日記的なことを英語でアウトプットしていきたいなと思いまして、何かないかなーと。
メモやテキストに書いてるだけなのもなんかなーと思いまして。あと、オンラインで管理したい。
じゃあブログでいいじゃないかと思ったわけですが、ブログだと、検索ができない。(と思ったのですが、普通に検索できますね、ブログでも良かったかもしれない。しかしせっかくエンジニアになったので、エンジニアっぽいことをやってみようじゃないかと)
一つのディレクトリにまとめとけば、エディタ開いて検索とかできるなと。ブログより素早くアクセスできるなと。(しかしgithub嬢では検索できないかぁ〜)
ということで、一つのリポジトリで管理しようと思いました。
これの何がいいって、githubで草を生やせること! これだけのためにgithubを使うと言っても過言でもない
ちゃんと習慣としてできているかが一目瞭然になること!これがいい。
というわけで、githubを使っていく運びになりました。
やりたいこと
特定のリポジトリからpushするときだけは勝手に別のgithubアカウントのリポジトリにしてくれる。
やったこと
githubアカウントを作る
もう一つのgithubアカウントを作ります。普通に。
sshキーを登録する
新しく作ったアカウントのために新しくsshキーを作成します。
$ ls ~/.ssh id_rsa id_rsa.pub #普段使いのやつ $ ssh-keygen -t rsa -C hoge@hoge.com -f fugafuga_rsa #パスフレーズなどは適当に。ファイル名は既存の鍵と被って上書きしないように。
githubから鍵を登録します。
fugafuga_rsa.pub
をgithubに登録。
githubでリポジトリを作成
作る。
configの設定
$ vi ~/.ssh/config
Host github.com-for-English User git Port 22 HostName github.com IdentityFile ~/.ssh/fugafuga_rsa TCPKeepAlive yes IdentitiesOnly yes
を追加。(ここら編怪しいです。)
git clone
$ git clone git@github.com:English/practice/listening.git
とするところを
$ git clone git@github.com-for-English:English/practice/listening.git
とする。
user情報を変更
このままだと、普段のアカウントのままなので、
$ git config --local user.name "アカウント名" $ git config --local user.email "メールアドレス"
とする。
commit $ push
これで晴れてremoteにpushできました〜
苦労したぜ…
will_paginateの1ページあたりの表示数とラベルを変更する
ページネーションをうまいことやりたいとき、railsだとどうやらkaminari
ってのが有名らしい。
しかしrailsのチュートリアルで、will_paginate
を使っていたので、それを活用していきます。
参考までにkaminari github.com
そして今回のwill_paginate github.com
余談ですけど、kaminariとかnokogiriとか4文字の日本語をローマ字にしましたみたいなgemってなんか良いですよね。
僕も将来onigiriってgem作りたいなと構想しています。
1ページに表示するitemの数を指定する
Post.paginate(:page => params[:page], :per_page => 30)
と、:per_page => 30
のようにオプションを付ければできます。
まとめてdefaultの設定もできるようですが、部分ごとに表示させたい数が違ったので、今回は使いませんでした。
# for the Post model class Post self.per_page = 10 end # set per_page globally WillPaginate.per_page = 10
labelを変更する
個別にも設定できるみたいなんですが、1ページあたりの表示数と違って、これはサイト全体で統一するパターンが多いと思うので、一括で設定をしたいところです。
config/application.rb
にて
config.i18n.default_locale = :ja
で、日本語に設定して、config/locals/ja.yml
で
ja: will_paginate: previous_label: "前" next_label: "次" page_gap: "…"
を設定。
rubyの配列、ハッシュ、シンボルあたりを整理する備忘録
ハッシュ、シンボル、配列あたりがごちゃごちゃしているので、まとめる。
備忘録です。
配列
シンプルな配列
array = [1,2,3,4,5]
[1, 2, 3, 4, 5]
要素を追加したり、色々なメソッドを使えます。
array.push(10)
[1, 2, 3, 4, 5, 10]
色んなものが配列の要素になれます。
違う種類のものが入っていてもOKです。
array = [1, "a", [1,2], [3, "b"], {name:"bob", age:18}]
[1, "a", [1, 2], [3, "b"], {:name=>"bob", :age=>18}]
要素を取り出してみます
array[2]
[1, 2]
さらに取り出してみます
array[2][1]
2
ハッシュもこの調子でできそうです。
array[4][:name]
"bob"
ハッシュ
hash = { orange: 100, apple: 200, banana:500 }
{:orange=>100, :apple=>200, :banana=>500}
hash = { a1: 100, b: 200, f:500 }
{:a1=>100, :b=>200, :f=>500}
ハッシュの値にハッシュも配列も入れられる
hash = { a: {aa: 110, ab:120, ac:[130, 140, 150, 160] }, b: 200, f:500 }
{:a=>{:aa=>110, :ab=>120, :ac=>[130, 140, 150, 160]}, :b=>200, :f=>500}
取り出すときは配列と同様に順番に指定していく
hash[:a][:ac][2]
150
ちなみにキーにハッシュや配列も入れることができるっぽい。
使う場面は想像できないが
hash = { {a: 120} => 20, [1,2,3] => 30 }
{{:a=>120}=>20, [1, 2, 3]=>30}
hash[[1,2,3]]
30
hash[{:a=>120}]
20
ハッシュのシンボルについて
keyとして使えるものに、シンボルというものがある。
シンボルは:key
のように使う
Fighter = { :Hanma => "Baki", :Hanayama => "Kaoru", :Orochi => "Doppo" } # 次のように宣言しても全く同じ # Fighter = { Hanma:"Baki", Hanayana: "Kaoru", Orochi: "Doppo" }
{:Hanma=>"Baki", :Hanayama=>"Kaoru", :Orochi=>"Doppo"}
取り出すときは、次のようにして使う
Fighter[:Hanma]
"Baki"
keyは文字列も指定できる。
文字列とシンボルが混在していても、問題なく処理される。
文字列とシンボルは別のものとして扱われるので、注意が必要。
# 同じkeyだと上書きされてしまう Underground_Fighter = { :Hanma => "Baki", :Hanayama => "Kaoru", :Orochi => "Doppo" , Hanma: "Jack" }
<main>:1: warning: key :Hanma is duplicated and overwritten on line 1
{:Hanma=>"Jack", :Hanayama=>"Kaoru", :Orochi=>"Doppo"}
Underground_Fighter[:Hanma]
"Jack"
# keyを:Hanmaと"Hanma"でハッシュに入れる Chika_Fighter = { :Hanma => "Baki", :Hanayama => "Kaoru", :Orochi => "Doppo" , "Hanma" => "Yujiro" }
{:Hanma=>"Baki", :Hanayama=>"Kaoru", :Orochi=>"Doppo", "Hanma"=>"Yujiro"}
Chika_Fighter["Hanma"]
"Yujiro"
Chika_Fighter[:Hanma]
"Baki"
rubyで配列、ハッシュに対する処理をするreduceについてのまとめ備忘録
reduceとは
reduceはinjectの別名。
配列やハッシュを次々に処理していくときに使えるやつ。
早速例示して使ってみる。
まず配列について
# 初期値なしで、配列の中身を順に足していく。 [1,2,3,4,5].reduce() {|sum, i| sum + i }
15
# 初期値を設定できる [1,2,3,4,5].reduce(100) {|sum, i| sum + i }
115
# 偶数なら足す # 初期値なし [1,2,3,4,5].reduce() {|sum, i| puts sum if i % 2 == 0 sum + i else sum end}
1
3
3
7
7
パッと書いてみたが、期待する結果じゃない。 これの挙動を確かめる。
①初期値がないので、[sum, i] = [1, 2]になる。
i=2なので、ifはtrueになり、sum+iが実行される。
よって返り値は3
②Z1周目のループが終わって次のループ。
[sum, i] = [3, 3]になる。
i=3なので、ifはfalseになり、sum(=3)が返される。
③3週目のループ
[sum, i] = [3, 4]になる。
i=4なので、ifはtrueになり、sum+i(=3+4=7)が返される。
④4週目のループ
[sum, i] = [7, 5]になる。
i=5なので、ifはfalseになり、sum(=7)が返される。
初期値がないと、こういった期待しない結果が返ってくる罠に遭遇しそう。
なので、初期値はなるべく都度適切なものを入れたほうがよい気がしている。
# 偶数なら足す # 初期値を0とする [1,2,3,4,5].reduce(0) {|sum, i| if i % 2 == 0 sum + i else sum end}
6
# 1行で書く熟練っぽいコードはまだ慣れない… [1,2,3,4,5].reduce(0) {|sum, i|if i % 2 == 0 then sum + i else sum end}
6
# 偶数なら足す # 初期値は0とする # elseの処理も書かないと、前回の戻り値がないことになってしまう。 [1,2,3,4,5].reduce(0) {|sum, i| if i % 2 == 0 sum + i end}
NoMethodError: undefined method `+' for nil:NilClass
ハッシュについて
# ハッシュのvalueだけを利用する fruit = { apple: 100, banana: 200, orange: 300, lemon: 400 } fruit.reduce(0) {|sum, (key, value)| sum += value}
1000
# ハッシュのvalueを条件にする # 初期値には空の配列[]を使用する fruit.reduce([]) {|array, (key, value)| array.push(key)}
[:apple, :banana, :orange, :lemon]
# reduceは毎回返り値を持たせないとエラーになる罠がある。 fruit.reduce([]) {|array, (key, value)| array.push(key)if value > 250 }
SyntaxError: <main>:1: syntax error, unexpected tIDENTIFIER, expecting '}'
ray.push(key)if value > 250 }
^
# 返り値がないときは、とりあえず何か強引に返しとく # 最後の値が返り値になる #ちゃんと書くならこう fruit.reduce([]) {|array, (key, value)| if value > 250 array.push(key) else array end}
[:orange, :lemon]
# 短く書くならこう fruit.reduce([]) {|array, (key, value)| array.push(key)if value > 250; array}
[:orange, :lemon]
mapよりは若干複雑かな
rubyで配列、ハッシュに対する処理をするmapについてのまとめ備忘録
rubyのmapなどについて使い方をまとめてみます。
map
配列やハッシュの各値に対して同じ操作をしていく。
配列について
# 各値を2倍にする [1,2,3,4,5].map {|i| i*2}
[2, 4, 6, 8, 10]
# 何もしない [1,2,3,4,5].map{|i| i}
[1, 2, 3, 4, 5]
# 全部1にする [1,2,3,4,5].map{|i| 1}
[1, 1, 1, 1, 1]
# 偶数のときだけ2乗にしてみる(失敗) [1,2,3,4,5].map{|i| i**2 if i%2==0}
[nil, 4, nil, 8, nil]
返り値がないとnilが渡される。
# 偶数のときだけ2乗にする [1,2,3,4,5].map{|i| if i % 2 == 0 i ** 2 else i end}
[1, 4, 3, 16, 5]
# 偶数のときだけ2乗にする #1行にまとめる # ※この場合thenを省略するとエラーになる [1,2,3,4,5].map{|i| if i % 2 == 0 then i ** 2 else i end }
[1, 4, 3, 16, 5]
ハッシュについて
ハッシュに対してmapを使っても、返り値がハッシュになるわけではなく、あくまで配列を返す。
これは地味に罠。
# keyを配列にして返す { :name => "hoge", :age => "24"}.map { |key, value| key }
[:name, :age]
# valueを配列にして返す { :name => "hoge", :age => "24"}.map { |key, value| value}
["hoge", "24"]
# keyとvalueを結合した文字列を返す。 { :name => "hoge", :age => "24"}.map { |key, value| "#{key} is #{value}" }
["name is hoge", "age is 24"]
# ちなみにハッシュの書き方はこれでもいい。(というかこれが今は主流…?) { apple: 100, orange: 120, banana: 200, lemon: 250 }
{:apple=>100, :orange=>120, :banana=>200, :lemon=>250}
# valueの条件によってkeyを変更して返してみる。 { apple: 100, orange: 120, banana: 200, lemon: 250 }.map { |key, value| if value > 150 "高い" else "安い" end}
["安い", "安い", "高い", "高い"]
でもこれって、結局ハッシュに対して処理してるならハッシュで返してほしくな〜い?
というのはよくある。気がする。
to_hというのを使うらしい。なるほど〜
# 条件によってvalueを変更してハッシュで返す { apple: 100, orange: 120, banana: 200, lemon: 250 }.map {|key,value| if value > 150 [key, "高い"] else [key, "安い"] end}.to_h
{:apple=>"安い", :orange=>"安い", :banana=>"高い", :lemon=>"高い"}