カテゴリー: BackEnd

Pythonで書かれたスクレイピングのコードをRubyで書いてみる


はじめに

以前、技術評論社さんのデータサイエンティスト養成読本という本を読みました。
いろいろな技術の紹介があり、非常に参考になりました。

その中でスクレイピングに関するサンプルコードもありました。
今回はPythonで書かれていたスクレイピングのコードをRubyで書き直した記事になります。

なお、Pythonで書かれたサンプルの方は実際に書籍をご覧になってください。

仕様

技術評論社さんのサイトに行き、直近の記事タイトルとそのリンクを取得してJSONで吐き出します。

ソースコード

実際に書いたソースコードはこちらになります。
その後、各項目について記載したいと思います。

require 'uri'
require 'open-uri'
require 'nokogiri'
require 'json'

class GihyoCrawler
  BASE_URL = 'http://gihyo.jp'.freeze
  CONTENT_LIST_URL = 'http://gihyo.jp/contentslist'.freeze
  TITLE_XPATH = '//*[@id="latestArticle"]/dl/dd/h3/a'.freeze
  NEXT_URL_XPATH = '//*[@id="latestArticle"]/div[4]/p[2]/a'.freeze
  OUTPUT_FILE = 'output.json'.freeze
  PAGE_NUM = 2
  SLEEP_TIME = 1

  def self.run
    url = CONTENT_LIST_URL
    File.open(OUTPUT_FILE, 'w:utf-8') do |f|
      f.write("{\n")
      PAGE_NUM.times do
        dom = read_html(url)
        scrape_content_page(dom).each do |title, url|
          data = {"title": title, "url": url}
          f.write(JSON.dump(data) + ",\n")
        end
        url = get_next_url(dom)
        break if url.nil?
        sleep(SLEEP_TIME)
      end
      f.write("}")
    end
  end

  def self.read_html(url)
    charset = nil
    user_agent = {"User-Agent" => "Gihyo Bot"}
    html = open(url, user_agent) do |f|
      charset = f.charset
      f.read
    end
    Nokogiri::HTML.parse(html, nil, charset)
  rescue StandardError => e
    p "Error: #{e}"
  end

  def self.scrape_content_page(dom)
    ret_arr = []
    dom.xpath(TITLE_XPATH).each do |dom_by_xpath|
      title = get_title(dom_by_xpath)
      url   = get_article_url(dom_by_xpath)
      ret_arr << [title, url]
    end
    ret_arr
  end

  def self.get_title(dom)
    dom.inner_text
  end

  def self.get_article_url(dom)
    url = dom.attribute('href').value
    URI.join(BASE_URL, url).to_s
  end

  def self.get_next_url(dom)
    next_url = dom.xpath(NEXT_URL_XPATH)[0].attribute('href').value
    return URI.join(BASE_URL, next_url).to_s
  rescue
    return nil
  end
end

GihyoCrawler.run

ファイル名を指定してrubyコマンドで実行できます。

$ ruby gihyo_crawler.rb

使用したモジュール、Gem

対象ページを取得

open-uriのopenメソッドを使用し、対象URLを開いて取得します。
その際、オプションとしてUser Agentが渡せるので、クローラーだと分かるような名前をつけてあげます。
取得したページはNokogiriでHTMLとして解析して返却します。

def self.read_html(url)
  charset = nil
  user_agent = {"User-Agent" => "Gihyo Bot"}
  html = open(url, user_agent) do |f|
    charset = f.charset
    f.read
  end
  Nokogiri::HTML.parse(html, nil, charset)
rescue StandardError => e
  p "Error: #{e}"
end

XPATHから目的のものを抜き出す

Google Chromeなどを使用すれば、対象のHTMLタグのXPATHを取得することができるので、そちらから目的のものを取得します。
XPATHの説明は、下記の方の記事が分かりやすいかと思います。

同じ階層の、例えば<div>などは配列で返ってくるので、ループして必要なだけ抽出します。
記事タイトル、リンク先それぞれ取得の方法が違うので、メソッドを用意してあげます。

def self.scrape_content_page(dom)
  ret_arr = []
  dom.xpath(TITLE_XPATH).each do |dom_by_xpath|
    title = get_title(dom_by_xpath)
    url   = get_article_url(dom_by_xpath)
    ret_arr << [title, url]
  end
  ret_arr
end

次のページのリンクを取得する

はじめに定数として、PAGE_NUM = 2と与えています。
技術評論社さんの記事ページはページングされており、過去の記事を見るためには次のページへ行く必要があります。

このとき、次のページボタンのXPATHを指定し、そのDOMのURLを取得すれば、同様の処理を繰り返すことで次のページの記事も取得できます。
今回のサンプルでは、何ページ目まで取得するか、という意味でPAGE_NUM = 2が与えられています。
もしそれ以上過去の記事がなければ、リンクが取得できないはずなので、その場合はbreakします。

def self.run
  # 省略

    PAGE_NUM.times do
      dom = read_html(url)
      scrape_content_page(dom).each do |title, url|
        data = {"title": title, "url": url}
        f.write(JSON.dump(data) + ",\n")
      end
      # 次のページへのリンクを取得
      url = get_next_url(dom)
      # もし取得できなければループを抜ける
      break if url.nil?
      # 連続してアクセスすると迷惑なので、次のループに入る前に一拍入れる
      sleep(SLEEP_TIME)
    end

  # 省略
end

さいごに

スクレイピングの勉強がてら書き換えてみました。
Railsで素早くWebページを作成することは違うので、Ruby自体の勉強にオススメです。

naoki85

シェア
執筆者:
naoki85
タグ: Ruby

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前