inuinu blog(開発用)

BOT @wagagun の開発ノウハウや、IT向け?の雑記ブログです。

同一サイトのスクレイピングをRubyとPythonで比較する

この記事では、スクレイピングについて、ごくごく簡単な例題を交え、RubyPythonでの比較をしてみたいと思います。

私がスクレイピングを始めた2010年前後の頃は、書籍はまだ出ておらず、試行錯誤の中でやっていましたが、それから少し経ってからRubyでの専門書が出版された記憶があります

ただ、昨今のPythonブーム?により、スクレイピングPython版は出版される一方、Rubyでの専門書が絶版になってしまい、いつのまにか、スクレイピングPythonという、首を傾げたくなるような、Ruby界隈ではなんとなく嫌なムードが漂っています。

そこで、この記事では、ごくごく簡単な例題を交え、RubyPythonでの比較をしてみたいと思います。

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp

やりたいこと

Javascriptで描画せずに、サーバーサイドでHTMLを整形するタイプ(一般的なニュースサイトはごく一部を除き、ほぼこのタイプ)のサイトから情報を取得。

例題では、Yahoo!ニュースの記事から、タイトル(titleタグおよびh1タグのinnerText)を抜き出す。

抜き出しはCSSセレクタにて行う。

動作確認は、自宅にあるMacintosh(10.13.6)で行った。

自宅MacPython(Python3)のバージョンは3.10.0。
Rubyは2.6.5p114。

Pythonの場合

パッケージ(pip3)のインストール

PythonのHTMLパーサの代表格である、Beautiful Soupを導入。

pip3 install bs4

pip自体のバージョンが古いとインストールが失敗する場合があります。その際は、warning以降に提示されたコマンドを実行してみて下さい。

プログラム例

●yahoo.py

from bs4 import BeautifulSoup as bs

# SSL: CERTIFICATE_VERIFY_FAILEDが発生する場合はコメントを外して実行
# import ssl
# ssl._create_default_https_context = ssl._create_unverified_context

import urllib.request as req

# YahooニュースのURL(リンク切れの場合は差し替えてください)
url ="https://news.yahoo.co.jp/articles/e925ba9c2f157b8cfeee2c5511745f8551cfa0c0"

# HTMLファイル(ソースレベルでjavascript描画されていないもの)を取得
html = req.urlopen(url)

# htmlファイルを、スクレイピングしやすいようにパースする
html_paerse = bs(html, "html.parser")

# headerタグ+h1タグの中に、タイトルが紛れている
# CSSセレクタで取得
h1 = html_paerse.select("header h1")

# h1自体を印字
print(h1)

# header h1は2つ存在するが、2つ目がタイトル
print(h1[1].text)

# こちらはtitleタグの中身
print(html_paerse.title.text)

「header h1」はheaderタグの中にあるh1タグを取得しろ!という意味。
単にh1のみだと、ページによっては多数の結果が取得できてしまう。idやclassなども組み合わせて、いかにシンプルに取得できるかが鍵となります。

実行

python3 yahoo.py

結果

[<h1 class="sc-cBoqAE lRfdj">Yahoo!ニュース</h1>, <h1 class="sc-hgIrPW bDIhnP">大谷翔平は復帰目前? 指揮官「明日が最良の日」 右脇腹の張りで3年ぶり5戦連続ベンチ</h1>]
大谷翔平は復帰目前? 指揮官「明日が最良の日」 右脇腹の張りで3年ぶり5戦連続ベンチ
大谷翔平は復帰目前? 指揮官「明日が最良の日」 右脇腹の張りで3年ぶり5戦連続ベンチ(デイリースポーツ) - Yahoo!ニュース

Rubyの場合

パッケージ(gem)のインストール

RubyのHTMLパーサの代表格である、Nokogiriを導入。

gem install nokogiri

上記例はRubyGemsを利用しない場合です。
RubyGemsを使用する場合は、Gemfileに記述しbundle installしてください。

gem自体のバージョンが古いとインストールが失敗する場合があります。その際は、gem update --systemを実行してみて下さい。

プログラム例

●yahoo.rb

require 'nokogiri'
require 'open-uri'

# YahooニュースのURL(リンク切れの場合は差し替えてください)
url ="https://news.yahoo.co.jp/articles/e925ba9c2f157b8cfeee2c5511745f8551cfa0c0"

# HTMLソース取得とパースを一気に行う
html_paerse = Nokogiri::HTML(open(url))

# headerタグ+h1タグの中に、タイトルが紛れている
# CSSセレクタで取得
h1 = html_paerse.css("header h1")

# h1自体を印字
p h1

# header h1は2つ存在するが、2つ目がタイトル
puts h1[1].text

# こちらはtitleタグの中身(.textは付かない)
puts html_paerse.title

実行

ruby yahoo.rb

結果

[#<Nokogiri::XML::Element:0x3fd3bc4f6ae4 name="h1" attributes=[#<Nokogiri::XML::Attr:0x3fd3bc4f69f4 name="class" value="sc-cBoqAE lRfdj">] children=[#<Nokogiri::XML::Text:0x3fd3bc4f69cc "Yahoo!ニュース">]>, #<Nokogiri::XML::Element:0x3fd3bc4f6ad0 name="h1" attributes=[#<Nokogiri::XML::Attr:0x3fd3bc4f6508 name="class" value="sc-hgIrPW bDIhnP">] children=[#<Nokogiri::XML::Text:0x3fd3bc4f64e0 "大谷翔平は復帰目前? 指揮官「明日が最良の日」 右脇腹の張りで3年ぶり5戦連続ベンチ">]>]
大谷翔平は復帰目前? 指揮官「明日が最良の日」 右脇腹の張りで3年ぶり5戦連続ベンチ
大谷翔平は復帰目前? 指揮官「明日が最良の日」 右脇腹の張りで3年ぶり5戦連続ベンチ(デイリースポーツ) - Yahoo!ニュース

Rubyの方は、最初の行に、なんとなく複雑な文字列?のようなものが表示されていますが、これはNokogiriのオブジェクトの属性ですので、構造体や配列として、普通にアクセス可能です。

ここまでに参考にしたURL

PythonRubyで比べる簡単なスクレイピング
qiita.com

PythonSSL認証に関するエラー返ってきたときの対処法
qiita.com

私感

HTML描画のみのケースはSSLエラー等の対処や、偽装エージェントなどの対策以外は、どちらもCSSセレクタを利用しますので、使用するパッケージに変な方言でもない限り、ほぼ一緒かなと思います。

Javascript描画の場合はSeleniumChromeランタイムのハンドルになると思うので、こちらも、手順的にはそう変わらないのでは?と思います。

SeleniumはHTML描画サイトであっても、例えばログインをしないと値を取得できないようなケースでも使用されます。
もともとはテスト用の自動化ツールだと思いますが、スクレイピングの他に、本番環境での入力自動化などでも利用されるケースがあります(ソースが古いなど、仕様をリバースエンジニアリング困難で、バッチ処理が作成できないといったケースなど)。

見た目=文法的な違いは?(一般論?)

  1. RubyPythonもそれぞれパッケージ名が異なる(パッケージの定義に多少の違いがある)
  2. Rubyはメソッドチェーンが使用でき、多重カッコ地獄から開放される(Pythonでは関数チェーン)
  3. Pythonはend文が不要(ただ、改行やインデントルールが厳格すぎて、メンテナンスが面倒な気もする)
  4. Rubyはreturnやカッコなど含め省略記法が豊富な一方、凝りすぎると、後で何これ?ということになりそう
  5. 変数の種類や振る舞い方はそれぞれ癖がある(これはどの言語もそう)

こんなところでしょうか。

私は他言語メインの方が読みやすいように、省略記法はなるべく避けたほうが良いかな?と思っています。
また、行折返しよりも、改行して見やすくしたほうが良いでしょうね。

大きな違いは使えるパッケージの方向性でしょうね。

株式市況や統計資料等の数値中心にスクレイピングし、分析等を行いたい場合はPythonなのかもしれませんが、文章を取得して取りまとめたい場合は、Rubyが便利かもしれません。

一方、ハッカーっぽいツールはPython発のほうが豊富な印象はあります。
某動画サイトのダウンロードツールとか。
多少グレーなスクレイピングには向くかもしれません(あくまでもイメージですが)。

以上。
好みの問題や、他にやりたいことなどの兼ね合いから、どちらにするか?でしょうね。

上記の選択肢も含め、どちらか一方といった考えにこだわらずに、プロジェクトでの言語選定では、用途により、両者を使い分けましょう。

切り出した文字列に文字化けは発生する?

𠮷野家の𠮷(つちよし)や、𩸽(ほっけ)、🍣🍺(俗に言う寿司ビール問題)といったサロゲートペア・絵文字対応については、以前はRubyの独壇場だったと思いますが、Pythonも追いついていると思います。

どちらも、文字数カウントや、一文字ずつの切り出しに、異常は発生していないようですね。

RubyにはPyCallがある

ところで、Rubyでは、有志が作成したパッケージPyCallにより、Pythonにしかないライブラリや関数、変数等をRuby上で扱うことができます。
これは便利ですよね!

PyCall github.com

PyCall Ruby版 Tips qiita.com

こういったものも含めて考えてみても良いでしょうね。
動作は(Pycallよりも)Python直のほうが早いかと思いますが、既存のRubyソースの改修などでは大いに活躍すると思います。

Ruby使いがPythonスクレイピング案件をこなすには

ここまでの違いがわかれば、あとは、SQLのアクセスや統計等のパッケージの使い方を覚えれば、RubyPythonの二刀流で行けそうですね。

SQLに関しては…

Ruby使いはActive RecordばかりでSQLを知らないでしょ?

…的な通説(都市伝説?)がありそうですが、(スクレイピングを含め)バッチ処理を組んででいる方は、SQLがメインだと思いますけどね。

あと、RubyってC言語系だから、JDBC使えないでしょ?Pythonにはパッケージがあるよ。

Java上で動く、JRubyという系統があり、そちらではJDBC使えます。
実際、私はAS/400で実装経験済みです。
(Javaベースのサービスは)初期動作は遅いですが、どのくらいの頻度で実行するかもありますし、常駐させれば問題ないかなと。

inuinu-tech.hatenablog.jp

最後に

とりあえずは、HTMLのみで取得可能なサイトのスクレイピングを比較しましたが、いかがでしょうか。

機会があればSeleniumについても試してみようかと思います。

それでは、また。

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp