LoginSignup
8
14

More than 5 years have passed since last update.

RubyとRailsでクローラー開発をしてみた

Last updated at Posted at 2017-12-18

はじめに

Railsで開発したアプリケーションにタスクとしてクローラーを開発する必要があったので、ほぼ初めてのRubyでクローラーを開発してみました。

今回は海外メディアサイトのRecodeの記事をクローリングしてきてGCPのCloud Translation APIで翻訳後、データベースに保存します。

クローラーはrails_project/lib/tasks/recode_crawler.rbというファイル名で作成しました。

プログラム

recode_crawler.rb
require 'open-uri'
require 'nokogiri'
require 'net/http'
require 'uri'
require 'json'

class Tasks::RecodeCrawler

  # recodeのurlを定数として定義
  TOP_URL = 'https://www.recode.net'.freeze

  @@archives_url = 'https://www.recode.net/archives'

  # 過去の記事(archives)のクローラー
  def self.archives_crawling()
    while true
      doc = Nokogiri.HTML(open(@@archives_url))
      article_url_list = []

      doc.xpath('//h2[@class="c-entry-box--compact__title"]').each do |element|
        element.css('a').each do |a|
          if a[:href] != nil && a[:href].include?('recode')
            article_url_list.push(a[:href])
          end
        end
      end

      article_url_list.each do |article_url|
        articles = Nokogiri.HTML(open(article_url))
        url_split = article_url.split('/')
        date = url_split[3]+ '-' + url_split[4] + '-' + url_split[5]
        title = articles.xpath('//h1[@class="c-page-title"]').inner_text
        body = articles.xpath('//div[@class="c-entry-content"]').inner_text.strip
        puts title + "\n"
        img_url = !articles.xpath('//picture[@class="c-picture"]//img').empty? ?
                      articles.xpath('//picture[@class="c-picture"]//img').first[:srcset].split(',').first.gsub(' 320w', '') : nil


        # クローラー実行して、すでにurl一致するレコードがない場合以下の処理を実行
        unless RecodeArticle.exists?(url: article_url)
          recode_article = RecodeArticle::create(
              url: article_url,
              date: date,
              title: title,
              body: body,
              img_url: img_url,
          )

          parse_body = body.split("\n")
          translate_body = ''

          # 翻訳APIへpostするデータが大きすぎるとエラーになるので細かくパースしてpost
          parse_body.each do |sentences|
            parse_sentences = sentences.split(". ")
            parse_sentences.each do |sentence|
              translate_body += translate(sentence).gsub('"', '')
            end
          end

          TranslateArticle::create(
              recode_article_id: recode_article[:id],
              url: article_url,
              date: date,
              title: translate(title),
              body: translate_body,
              img_url: img_url,
          )
        end

        sleep(rand(2..4))
      end

      pagination_next = doc.css('.c-pagination__next')
      if pagination_next != nil
        pagination_next.each do |n|
          @@archives_url = TOP_URL + n[:href]
        end
      else
        break
      end

    end

  end


  # トップページの記事のクローラー
  def self.crawling

    doc = Nokogiri.HTML(open(TOP_URL))
    article_url_list = []

    doc.xpath('//h2[@class="c-entry-box__title"]').each do |element|
      element.css('a').each do |a|
        if a[:href] != nil && a[:href].include?('recode')
          article_url_list.push(a[:href])
        end
      end
    end

    article_url_list.each do |article_url|

      # 例外処理: ページが存在しなければ次のページ
      begin
        articles = Nokogiri.HTML(open(article_url))
      rescue OpenURI::HTTPError
        sleep(rand(2..4))
        next
      end

      url_split = article_url.split('/')
      date = url_split[3]+ '-' + url_split[4] + '-' + url_split[5]
      title = articles.xpath('//h1[@class="c-page-title"]').inner_text
      body = articles.xpath('//div[@class="c-entry-content"]//p').inner_text.strip
      puts title + "\n"
      img_url = !articles.xpath('//picture[@class="c-picture"]//img').empty? ?
                    articles.xpath('//picture[@class="c-picture"]//img').first[:srcset].split(',').first.gsub(' 320w', '') : nil

      # クローラー実行して、すでにurl一致するレコードがない場合以下の処理を実行
      unless RecodeArticle.exists?(url: article_url)
        begin
          ActiveRecord::Base.transaction do
            recode_article = RecodeArticle::create(
              url: article_url,
              date: date,
              title: title,
              body: body,
              img_url: img_url,
            )

            parse_body = body.split("\n")
            translate_body = ''

            # 翻訳APIへpostするデータが大きすぎるとエラーになるので細かくパースしてpost
            parse_body.each do |sentences|
              parse_sentences = sentences.split('. ')
              parse_sentences.each do |sentence|
                translate_body += translate(sentence).gsub('"', '')
              end
            end

            TranslateArticle::create(
              recode_article_id: recode_article[:id],
              url: article_url,
              date: date,
              title: translate(title),
              body: translate_body,
              img_url: img_url,
            )
          end
        rescue
          next
        end
      end

      sleep(rand(2..4))
    end

  end


  # GCPのCloud Translation APIの翻訳用メソッド
  def self.translate(q)
    url = URI.parse('https://www.googleapis.com/language/translate/v2')
    params = {
        q: q,
        target: 'ja',
        source: 'en',
        key: '****************************'
    }
    url.query = URI.encode_www_form(params)
    res = Net::HTTP.get_response(url)
    JSON.parse(res.body)['data']['translations'].first['translatedText']
  end


end


実行方法

$ bin/rails runner Tasks::RecodeCrawler.crawling
$ bin/rails runner Tasks::RecodeCrawler.archives_crawling

でクローラーを実行します。

最後に

RubyもRailsも初めてなのでお作法だったりがよくわかってない状態でクローラーを開発しましたが、Ruby結構楽しい。

8
14
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
8
14