見出し画像

初心者がseleniumを使ってInstagramをエゴサするBotを作るお話(selenium編)

・はじめに

自分が携わった酒の評価、感想、当然気になる。slackを導入してすぐに、mythings(1/31でサービス終了)を利用してtwitterやinstagramの投稿をシェアするチャンネルを作ってエゴサを自動化していたが、昨年4月頃に起こったinstagram仕様変更によってエゴサが死んでしまった。mythingsもサービス終了ということで、twitterの検索をIFTTTに切り替えると同時に、instagramを検索するBotを作ろうと頑張りました。初心者の作業ログです。

・筆者の腕前

昨年8月頃からprogateを利用してプログラミング入門をした。9月末にGASを利用したスクレイピングBOTを作った。目標なくruby on rails tutorialをやるも6章で挫折、年始よりAtCoderにてrubyを使って競プロの問題を解いたりしている。この程度の成績です。

・仕様

seleniumでブラウザ操作、nokogiriでソースから必要な要素を取り出してgoogle spreadsheetにAPIを使って記入、slackにポストするというアプリ。AWS Lambdaにデプロイして定期実行する。

・環境(ローカル)

windows10,ruby2.5.1p57
gem: selenium-webdriver,nokogiri,google-api-client,
google chome,chromedriver

前にインストールしたrubyをそのまま使用し、gemもgem install ~~で導入しましたが、Lambdaが対応しているのがruby2.5.0なのでrbenvを使って2.5.0で開発したほうが良かったし、bundleを使うべきでした。cloud9に持ち込んだときはそのようにしました。

・selenium部分の実装

require 'selenium-webdriver'
require 'uri'
require 'nokogiri'
require 'time'

class InstaBot
  attr_accessor :driver
# instagramにログインする
  def initialize(username,password)
    options = Selenium::WebDriver::Chrome::Options.new
    options.add_argument('--incognito')
    options.add_argument('--headless')
    @driver = Selenium::WebDriver.for :chrome, options: options
    @driver.get 'https://www.instagram.com/accounts/login/?source=auth_switcher'
    sleep 1
    username_element = @driver.find_element(:name, 'username')
    password_element = @driver.find_element(:name, 'password')
    username_element.send_keys(username)
    password_element.send_keys(password)
    sleep 1
    password_element.send_keys(:return)
  end

# タグ検索から最新ポストをクリック、データを格納してページを送る
  def search_and_scrape_values(keyword,lastdate)
    values = []
    cnt = 0
    encode_word = URI.encode(keyword)
    wait = Selenium::WebDriver::Wait.new(timeout: 10)
    @driver.get "https://www.instagram.com/explore/tags/#{encode_word}/"
    wait.until {@driver.find_element(:xpath,'//*[@id="react-root"]/section/main/article/div[2]/div/div[1]/div[1]')}
    latest_post = @driver.find_element(:xpath,'//*[@id="react-root"]/section/main/article/div[2]/div/div[1]/div[1]').click()
    sleep 1
    loop do
      wait.until{@driver.find_element(:xpath,'/html/body/div[2]/div[2]/div/article/div[2]/div[2]/a')}
      html = @driver.page_source.encode('utf-8')
      doc = Nokogiri::HTML.parse(html)
      sleep 1
      item_url = 'https://www.instagram.com' + doc.xpath('/html/body/div[2]/div[2]/div/article/div[2]/div[2]/a')[0][:href]
      post_date = doc.xpath('/html/body/div[2]/div[2]/div/article/div[2]/div[2]/a/time')[0][:datetime]
      post_date = Time.parse(post_date).getlocal("+09:00")
      image_url = doc.xpath('/html/body/div//article/div[1]/div/div//img')[0][:src]
      post_text = @driver.find_element(:xpath,'/html/body/div[2]/div[2]/div/article/div[2]/div[1]/ul/li/div/div/div/span').text
      author_name = @driver.find_element(:xpath,'/html/body/div[2]/div[2]/div/article/header/div[2]/div[1]/div[1]/h2/a').text
      author_icon = doc.xpath('/html/body/div[2]/div[2]/div/article/header/div[1]/a/img')[0][:src]
      break if cnt > 15 || post_date <= lastdate
      values.push([item_url,image_url,post_text,author_name,author_icon,post_date])
      cnt +=1
      @driver.find_element(:xpath,'/html/body/div[2]/div[1]/div/div/a[2]').click()
      sleep 1
    end
    values
  end
end

xpathで要素を指定して、ブラウザ操作をしたり値をとったりしました。
xpathの指定に手こずった(今もたまにエラーを吐く)ものの、内容としては難しくなかったです。以下のページを大変参考にしました。


・解説

ほぼほぼ参考にしたページそのままの実装です。タグを検索した後の待ち時間はwait.untilを使って待ってあげるといい感じになりました。ググっていくつか出てきたBOTは自動でいいねをするというものでしたが、今回は投稿のURLや写真、本文テキストなどを取得したいので、nokogiriを併用しました。

html = @driver.page_source.encode('utf-8')
doc = Nokogiri::HTML.parse(html)

で、パースしたhtmlからxpathで欲しいデータを指定してあげて配列に格納。seleniumでページを送ってloopで繰り返し、cntが15を超えるか前回実行時に拾ったポストの時間を下回るとbreakとしました。

・感想

ローカルだと問題なさそうなxpathでの指定が、lambda上だと見つからなくてエラーみたいなことが多発したので細かい調整でかなり手を取られました。まだちょいちょい直したりしてます。わからないことだらけで調べながらなかなか時間をかけたはずなのに、いざまとめて書こうとすると書くほどのことはないので悲しい。半年前の自分にもわかるように見ればわかるだろってことも書いておくことにしました。spreadsheet編、実行部分編、cloud9での動作確認編、lambdaデプロイ編に続きます。

この記事が気に入ったらサポートをしてみませんか?