LoginSignup
1
2

More than 3 years have passed since last update.

『CAPTCHA』を突破するサービス『2Captcha』とRuby+Chrome_Remoteで自動スクレイピング

Last updated at Posted at 2020-11-15

はじめに

スクレイピングしていると、CAPTCHAが出てプログラムが止まった経験、あると思います。
(そういう方しかこの記事は見ませんよね。)
なんとかCAPTHCAを回避するために、BOTっぽくない動きをさせたり、IP分散という手もありますが、今回は素直にCAPTCHAを解いてやろうと思います。
もちろん、エンジニアなので自分の手で解くより、プログラム上で自動的に解かせたいですよね。
機械学習は学習コストや導入コストが高く、もっと楽したいです。
2Cpathcaというサービスがそれを叶えてくれます。
他にも色々サービスがあるので、自分にあったものを見つけてください。
Pythonの記事はあったのですが、Rubyの記事は見つからなかったので書きました。

2Capthcaとは

image.png
CAPTHCA機能を突破するためのサービスで、APIを利用することで認証を自動化することができます。
有料サービスですが、reCAPTCHA v2なら1,000リクエストで$2.99と安いです。
念の為お断りを入れておきますが、私と2Captchaとの間に販促等での金銭のやりとりはございません。

Chrome_Remoteとは

ChromeのインスタンスをRubyから操作できるライブラリです。
詳しい使い方は解説ページリポジトリをご参照ください。
スクレイピングする前提として、そもそもCAPTHCAが出にくいようにしてやる必要があります。
Selenium等と違ってChromeをそのまま動かすChrome_RemoteのほうがBOT判定されにくいと思います。(そのうち違いを検証したい。)

やりたいこと

image.png
reCAPTCHAのデモページを突破します。
2Capthcaのアカウントの作成やapiキーの取得については先人の記事をご参照ください。

『2Captcha』とRuby+Chrome_RemoteでreCAPTHCAを突破

2Captchaのapiキーを取得して、ファイル保存しておきます。

key.yaml
---
:2Capthca: 2Captchaのapiキー

Chromeをdebugging-portつきで起動します。

Macの場合
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 &

必要なGemをインストールします.

Gemfile
source "https://rubygems.org"
gem 'nokogiri'
gem 'chrome_remote'
bundle install

rubyプログラム本体です。

crawler.rb

require 'nokogiri'
require 'chrome_remote'
require 'yaml'

class CaptchaDetectedException < StandardError; end

class ChromeController
  def initialize
    @chrome = ChromeRemote.client

    # Enable events
    @chrome.send_cmd "Network.enable"
    @chrome.send_cmd "Page.enable"
  end

  def open(url)
    # ページアクセス
    move_to url
    captcha_detect
  end

  def reload_page
    sleep 1
    @chrome.send_cmd "Page.reload", ignoreCache: false
    wait_event_fired
  end

  def execute_js(js)
      @chrome.send_cmd "Runtime.evaluate", expression: js
  end

  def wait_event_fired
      @chrome.wait_for "Page.loadEventFired"
  end

  # ページ移動
  def move_to(url)
    sleep 1
    @chrome.send_cmd "Page.navigate", url: url
    wait_event_fired
  end

  # HTMLを取得
  def get_html
    response = execute_js 'document.getElementsByTagName("html")[0].innerHTML'
    html = '<html>' + response['result']['value'] + '</html>'
  end

  def captcha_detect
    bot_detect_cnt = 0
    begin
      html = get_html
      raise CaptchaDetectedException, 'captchaが確認されました' if html.include?("captcha")
    rescue CaptchaDetectedException => e
      p e
      bot_detect_cnt += 1
      p "captcha突破試行: #{bot_detect_cnt}回目"
      doc = Nokogiri::HTML.parse(html, nil, 'UTF-8')
      return if captcha_solve(doc) == '解除成功'
      reload_page
      retry if bot_detect_cnt < 3
      p 'captcha突破エラー。Rubyを終了します'
      exit
    end
    p 'captchaはありませんでした'
  end

  def captcha_solve(doc)
    id = request_id(doc).match(/(\d.*)/)[1]
    solution = request_solution(id)
    return false unless solution
    submit_solution(solution)
    p captcha_result
  end

  def request_id(doc)
    # APIキーの読み込み
    @key = YAML.load_file("key.yaml")[:"2Capthca"]
    # data-sitekey属性の値を取得
    googlekey = doc.at_css('#recaptcha-demo')["data-sitekey"]
    method = "userrecaptcha"
    pageurl = execute_js("location.href")['result']['value']
    request_url="https://2captcha.com/in.php?key=#{@key}&method=#{method}&googlekey=#{googlekey}&pageurl=#{pageurl}"
    # captcha解除を依頼
    fetch_url(request_url)
  end

  def request_solution(id)
    action = "get"
    response_url = "https://2captcha.com/res.php?key=#{@key}&action=#{action}&id=#{id}"
    sleep 15
    retry_cnt = 0
    begin
      sleep 5
      # captcha解除コードを取得
      response_str = fetch_url(response_url)
      raise 'captcha解除前' if response_str.include?('CAPCHA_NOT_READY')
    rescue => e
      p e
      retry_cnt += 1
      p "リトライ:#{retry_cnt}回目"
      retry if retry_cnt < 10
      return false
    end
    response_str.slice(/OK\|(.*)/,1)
  end

  def submit_solution(solution)
    # 解除コードを所定のtextareaに入力
    execute_js("document.getElementById('g-recaptcha-response').innerHTML=\"#{solution}\";")
    sleep 1
    # 送信ボタンクリック
    execute_js("document.getElementById('recaptcha-demo-submit').click();")
  end

  def captcha_result
    sleep 1
    html = get_html
    doc = Nokogiri::HTML.parse(html, nil, 'UTF-8')
    doc.at_css('.recaptcha-success') ? '解除成功' : '解除失敗'
  end


  def fetch_url(url)
    sleep 1
    `curl "#{url}"`
  end

end

crawler = ChromeController.new
url = 'https://www.google.com/recaptcha/api2/demo'
crawler.open(url)

プログラムを実行すると、reCAPTCHAのデモページにアクセスして、CAPTCHAの突破を試みます。

bundle exec ruby crawler.rb

最後に

スクレイピングを行う目的や態様、スクレイピングで得たデータの取り扱い方によっては、著作権法、個人情報保護法に抵触してしまう恐れがあります。
皆さんに楽しいスクレイピングライフがあることを祈ります。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2