人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

Rubyでスクレイピングやってみたので見てもらえますか??

先日Rubyのお勉強を始めまして、スクレイピングに挑戦しました。やったらできました!
うまく動いてとても高ぶってるのですが、プログラミングはほとんど初めてなので、いろいろとお作法から外れていそうです。
そこで、変なところや、もっと一般化して書けるところがあればお教えいただけますか?

■やったこと:
CSV形式の曲のリストを用意して、初音ミクWikiから該当する曲の歌詞をスクレイピングする。

■知りたいこと:
私の書いたコードの変なところ、一般化できるところを教えてほしい。
気づいてる範囲ではたとえば、
・今回URLは「?page=」のあとにタイトルつければ繋がることが偶然わかったのでそうしています。そういう仕組みではないサイトでも平気な方法はありますか。
・「Nokogiri」はわかんなくてあきらめてしまったけど、それを使ったほうがよかったのでしょうか。
・最後にばらばらのテキストファイルに書き出してるけど、ふつうはデータベースに入れますか??
など?

基本なにもわからないので、いろいろ教えてください! よろしくお願いします。

長くなるので、コードは別に書きます!

●質問者: ハコサト
●カテゴリ:ウェブ制作
○ 状態 :終了
└ 回答数 : 2/2件

▽最新の回答へ

質問者から

CSVファイルは
https://docs.google.com/spreadsheet/pub?key=0AidlTjRleM3mdHJBSjJPbmtIM0NsMG41Z1VsVVc1T2c
これをもとにして変換したものです。
0列目には順位が、2列目には曲名を初音ミクWikiの表記に合わせたものを正規表現で作って入れました。
CSVファイルは全部で200行ですが、初音ミクWikiにない曲もあるので、処理できたのは170曲ほどでした。

最終的にはRで読み込んで、MeCabで形態素解析して、ボカロ曲の特徴を考えたりしたいです。まだお勉強中です。
http://hacosato.hatenablog.com/
結果はこのブログに載せようと思っています。

Rubyで私が書いたのは以下のようなコードです。

require 'open-uri'
require 'csv'
require 'uri'

CSV.foreach("(csvファイルのパス)", "r") do |row|
title = row[2]
url = "http://www5.atwiki.jp/hmiku/?page="+title
url = URI.escape(url)

charset = nil
html = open(url) do |f|
charset = f.charset
f.read
end
if (/<title>初音ミク Wiki -.*は見つかりません<\/title>/ =~ html)
next
end
/(?<=<h3 id="id_0a172479">歌詞<\/h3>)(.*?)(?=<h3)/m =~ html
lyric = $1
if (lyric == nil)
next
end
lyric.gsub!(/<div>|\n/m, '') #divタグと\nを取り除く
lyric.gsub!(/<\/div>|\n/m, '<br />') #div閉じタグを<br \/>にする
lyric.gsub!(/\A\s+?|\s+?\Z|(<br \/>)+\Z/m, '')#文字列の最初と最後の空白や<br />を取り除く
lyric.gsub!("<br />", "\n")#brタグを改行にする
lyric.gsub!(/<\/?[^>]*>/, "")#文中のhtmlタグは外す
lyric.chomp!
File.write("/lyric/"+row[0]+"_"+title+".txt", lyric)#それぞれテキストファイルに書き出す
sleep 1
end

色をつけられるんですね!


1 ● a-kuma3
●50ポイント

・今回URLは「?page=」のあとにタイトルつければ繋がることが偶然わかったのでそうしています。そういう仕組みではないサイトでも平気な方法はありますか。

「そういう仕組みではない」にも、いろいろなタイプがあるので、ぶち当たってから、でも良さそうな気がします。
よくあるところだと、POST と、リダイレクトでしょうか。

POST は、パラメータが URL で見えないやつです。
ネットバンクや電車の予約のサイトでは、URL にパラメータがついてません。

リダイレクトは、ホームページの引っ越しとかで、アクセスしたURLから、別のサイトに飛ばされるやつです。
Net::HTTP のライブラリを使うと、「リダイレクトしろ、って言ってるよ」というのが分かるので、もう一回、ページの内容を取りに行きます。

・「Nokogiri」はわかんなくてあきらめてしまったけど、それを使ったほうがよかったのでしょうか。

Nokogiri を使わないと、どうにも面倒だ、ってなってからでも良いと思います。
例えば、先の「リダイレクト」。
これなんかを自動でやってくれます(確か)。

歌詞を切り取るところで、ピンポイントな目印があれば、こんな感じのソースで行けますけど、HTML の構造をたどらなきゃいけなかったりすると、Nokogiri を使うと楽になります。

# とか書きながら、ぼくも文字列ベースでやることがほとんどです (^^;

・最後にばらばらのテキストファイルに書き出してるけど、ふつうはデータベースに入れますか??

ページの内容をマルっと取っておきたいだけだったら、どっちでも。
件数が多くなってくると、データベースを使いたくなるような気がします。
検索したり、バックアップが楽だったり。
データベースを使わないとできない、というわけじゃありませんが。


スクレイピングは、取ってくる先のページの構造に大きく左右されるので、一般化はあまり気にしなくても良いと思います。
やるとしたら URL から中身を取ってくるところまでのエラー処理とかくらいでしょうか。
でも、その辺りは open-uri がやってくれているので。


動いているコードなので、このままでも良いと思うんですけど、以下の件は、

 /(?<=<h3 id="id_0a172479">歌詞<\/h3>)(.*?)(?=<h3)/m =~ html
 lyric = $1
 if (lyric == nil)
 next
 end
 lyric.gsub!(/<div>|\n/m, '') #divタグと\nを取り除く
 ...
 sleep 1

こういうふうに書くと思います。

 if (/(?<=<h3 id="id_0a172479">歌詞<\/h3>)(.*?)(?=<h3)/m =~ html)
 lyric = $1
 lyric.gsub!(/<div>|\n/m, '') #divタグと\nを取り除く
 ...
 sleep 1
 end

それか、こう。

 unless (/(?<=<h3 id="id_0a172479">歌詞<\/h3>)(.*?)(?=<h3)/m =~ html)
 next
 end
 lyric = $1
 lyric.gsub!(/<div>|\n/m, '') #divタグと\nを取り除く
 ...
 sleep 1

この直前の「?見つかりません」と同じような形になってないと、気持ち悪いな、ってだけですが。


ハコサトさんのコメント
a-kuma3さん回答ありがとうございます! >> 「そういう仕組みではない」にも、いろいろなタイプがあるので、ぶち当たってから、でも良さそうな気がします。 << たとえば、うたまっぷの歌詞のページ、どれかひとつを開いて ソースを表示してリンクを一つたどれば、歌詞のテキストが拾えそうです (いろいろ見てみたのですが、違法とかではないですよね?)。 これならたぶん私の手持ちの技術でもできそうな気がします。 ということは、その前段階として曲のタイトルやアーティストとかの情報を元にして、 歌詞のページにたどりつくことができれば、うたまっぷからも 歌詞をスクレイピングすることができるようになりそうです。 解決策として私が思い付くのは、まず 「https://www.google.co.jp/search?q=」 のあとに「曲名 うたまっぷ」とかって入れて、検索結果の1つめを拾うことですが、 たぶんもっとスマートな方法がありますよね…。 こういうときにはみなさんどうしていらっしゃるのでしょうか。 >> Nokogiri を使わないと、どうにも面倒だ、ってなってからでも良いと思います。 << ちょっと触ってはみたのですが、 正規表現にできないどんなことができるのかよくわからなかったので、 私にはしばらく保留になりそうです…。 書き換えの例示ありがとうございます。 行数が少なくなってスマートに見えます! 一般化とか、少しこだわりすぎていたなと気づきました。 動けばいいですもんね! 気持ちが少し楽になりました。 プログラミングはほとんど初めてですが、楽しさがわかってきたように思います!

2 ● TransFreeBSD
●50ポイント ベストアンサー

ruby使いじゃないのでrubyでのお決まりはちょっとわかりませんが、補足的+αで書いてみます。

・今回URLは「?page=」のあとにタイトルつければ繋がることが偶然わかったのでそうしています。そういう仕組みではないサイトでも平気な方法はありますか。

仕組みもですが、元にするデータも色々なので、その都度調べて法則を見つけたり、一覧をスクレイプしてみたりです。
スクレイピングってのが元々そういう感じです。
ただ、それだと大変なんで、ってことでプログラムで読み書きするために出来たのがWebAPIで、これがあれば楽が出来ますし、別途マニュアルが用意されていたりします。
ただWebAPIが用意されているかが、サイトによるわけで、それも含めて「このリストからあのデータを得るにはどうしよう?」と毎度調べるしか無いですね。

・「Nokogiri」はわかんなくてあきらめてしまったけど、それを使ったほうがよかったのでしょうか。

今回は無しでOKだと思います。
Nokogiriだとタグで取ってくる感じなので、たとえば作者名だとか、ニコニコのリンクだとか、リンクから作者情報も得るだとかになるとNokogiriが便利になるかもしれません。
今は正規表現を使っていますが、Nokogiriを使うとCSSのセレクタやXPathが使えます。これは、どっちが分かりやすいか、書きやすいかという話で、スクレイピングだとデータさえ取れれば使いやすい方で良いです。

・最後にばらばらのテキストファイルに書き出してるけど、ふつうはデータベースに入れますか??

用途によります。
この後、Rを使ったりということですが、そのRで使いやすい方を選べば良いです。
今後、作者名とか製作時期とか付加情報も得るならデータベースが扱いやすいかもしれませんが、テキストファイルがあるならその時にデータベース化するのも難しく無いですし。

・その他

URI.escapeはobsoleteとありますし、曲名に「+」や「?」があるとURI.escapeではダメなんじゃないかと思います。
CGI.escapeかURI.encode_www_formを使うとかで、下記のどちらかのようにするのが良いのではないでしょうか?
(ruby使いじゃないので外しているかも?)

url = "http://www5.atwiki.jp/hmiku/?page=" + CGI.escape(title)
url = "http://www5.atwiki.jp/hmiku/?" + URI.encode_www_form({"page"=>title})

上記の関連で曲名に「:」「/」が有ると、ファイル名としてどうなるんでしょう?
曲名のような自由な文字列を元にするときはチェックや正規化をしたほうが安心できる気がします。
ライブラリにそういうのもあったりしませんかね?
(ruby使いじゃないのでよくわかりません)

データの取得に失敗してif文でnextした場合、sleepなしで連続して試みています。
何らかの原因で連続して失敗する場合、続けざまに行ってしまうので、最後にsleepじゃなく、openの前にsleepとかで、確実にsleepしたほうが良い気がします。
今どきはこの程度どうって事無い気もしますけど。

上記関連で、失敗した時はメッセージ出しておくと、なんかおかしいな?って時に早めに気が付きます。
(なんとなく例外使っても良い気がしますが、ruby的にはどうなのでしょう?)

あと、一時的な問題で失敗したとか、途中で例外が発生して、再度取り直すとかの場合でも、最初のリストを修正しないと全部取り直しをしてしまいます。
最初にファイルがあるかチェックして、無いときだけ取り直すようにすると、失敗した時とか、増やした時とかに対応し易いかもしれません。
さらに、別フォルダなどにhtmlそのものを保存したり、それを自動で行うようなライブラリを使う、つまりキャッシュしておくと、ちょっと取る範囲を変えてみたとか、そういうのもやりやすくなります。
(逆に失敗した時のキャッシュが残っていて……ということもありますが……)

とは言っても、自分だけが使うこういうツールは、ようはデータが取れれば良いので、使って行って気が付いたところを修正改良していけば十分なんじゃないですかね?


a-kuma3さんのコメント
>> (なんとなく例外使っても良い気がしますが、ruby的にはどうなのでしょう?) << バンバン、使って良いと思います。 [http://docs.ruby-lang.org/ja/2.1.0/library/open=2duri.html:title=open-uri]もエラーは例外で飛んでくるみたいですし。

ハコサトさんのコメント
TransFreeBSDさん回答ありがとうございます! >> 仕組みもですが、元にするデータも色々なので、その都度調べて法則を見つけたり、一覧をスクレイプしてみたりです。 << 法則を見つけるコツのようなものはありますか? 「一覧をスクレイプ」というのは、たとえば http://www5.atwiki.jp/hmiku/pages/18305.html こういう記事があったとしたら、数字を1つずつ繰り上げて 「18306」「18307」と順に全部スクレイプする、みたいな感じでしょうか。 APIというのは聞いたことがありますが、難しそうなので避けて通ってました。 Rubyでも使える…んですよねきっと。 APIが使えるサイトどこか選んで、時間のあるときに試してみたいです。 >> Nokogiriだとタグで取ってくる感じなので、たとえば作者名だとか、ニコニコのリンクだとか、リンクから作者情報も得るだとかになるとNokogiriが便利になるかもしれません。 << 高度なことをしようと思うとNokogiriが便利なことがある、程度にしか いまの私には理解できませんでした…すみません勉強します…。 XPathは少しだけネットで調べてすぐに挫折してしまったので、 あとでじっくりリベンジしたいです。 >> 今後、作者名とか製作時期とか付加情報も得るならデータベースが扱いやすいかもしれませんが、テキストファイルがあるならその時にデータベース化するのも難しく無いですし。 << 元になっているCSVファイルにすでに作者や製作時期が入っているので、 それもまとめて扱いやすいようにしておきたいです。 でもいまはとりあえずテキストで保管しておいて、 不便があったときにデータベースのことを学んでみたいと思います。 そのときには、SQLiteというものを調べたらいいかなと思っているのですが、 そういう方針で問題ないでしょうか。 >> 上記の関連で曲名に「:」「/」が有ると、ファイル名としてどうなるんでしょう? << 今回は曲名は事前にCSVに入れたので、それをRubyに渡す前に人力チェックしました。 確かに「:」「/」があるとつまづきそうですね… (nilが混ざっていたのが原因で起きたエラーがわかんなくて悩んだりしてたし…)。 >> データの取得に失敗してif文でnextした場合、sleepなしで連続して試みています。 何らかの原因で連続して失敗する場合、続けざまに行ってしまうので、最後にsleepじゃなく、openの前にsleepとかで、確実にsleepしたほうが良い気がします。 << ほんとですね! ありがとうございます。 >> 上記関連で、失敗した時はメッセージ出しておくと、なんかおかしいな?って時に早めに気が付きます。 << やってる途中ではいろんなとこでつまづいてたので、 たくさんのpで変数を表示したりしていました。 今回質問するにあたっては恥ずかしいのでぜんぶ消しましたが…。 >> とは言っても、自分だけが使うこういうツールは、ようはデータが取れれば良いので、使って行って気が付いたところを修正改良していけば十分なんじゃないですかね? << 確かにそういうスタンスで十分ですね。 どうもありがとうございました。

TransFreeBSDさんのコメント
「一覧をスクレイプ」というのは、例えば初音ミクwikiなら http://www5.atwiki.jp/hmiku/pages/18640.html だとか、うたまっぷなら http://www.utamap.com/accessranking/weekly_ranking.html だとか、こういうページからリンクを取ってきて使うってことです。 今回は曲名リストが既にあるので必要無いのですが、こういう一覧からリンクを取ってきて更新するってのをプログラムで自動化して、毎週1回実行するようにすれば新曲がどんどん集まるとかそういう事が出来ます。 法則はそのサイトの色々なページ見たりソース見たりで想像していくしか無いですね。 たとえば、さっきのうたまっぷなら、weekly_ranking.htmlというのは多分毎週同じだろうからココを起点に出来るな、とか、http://www.utamap.com/showkasi.php?surl= で始まるリンクを集めればいいな、とか。 あと、曲名、アーティスト名で検索できるので検索してみると http://www.utamap.com/searchkasi.php?searchname=title&word=%E3%81%9F%E3%81%A8%E3%81%88%E3%81%B0 とかで検索できるな、とか(これは検索したあとのURLを一部分だけ消してみて、消しても良い部分を探して作りました)。 APIだとこういうのが文章化されていて、どうやったら検索できますよ、とか、どの項目が曲名ですよ、リンクですよというのがわかりやすくなっているのと、それらがXMLとかで取得できてNokogiriのようなので簡単に取り出せるようになってます。 普通のページは人間が見るために作られているのでプログラムだと、ここからここまで切出してbrタグ変換してタグ消して……みたいな手順ですが、APIだと機械向けなので、曲名はこのタグ、歌詞はこのタグ、作者はこのタグみたいになってます。 NokogiriだとCSSのセレクタでもいけますよ。たとえばさっきの http://www5.atwiki.jp/hmiku/pages/18640.html なら「.plugin_list_by_tag li a」で一覧のリンクがとれると思います(多分)。 XPathも使えますが、使わなくても問題無いです。 データベースはこういう場合、SQLiteが使いやすくていいですね。 パソコンに仕事をさせるために教えるのに、日本語が通じないのでrubyで教えてるわけなんで、まあ、少しずつ自分のペースでやっていけば良いですよ。パソコンは幾らでも待ってくれるので。 最後にうたまっぷなんですが、実はスクレイピング出来ないように作ってあります。 (コピペも出来ない) 頑張ればやる方法もありそうですが、うたまっぷとしてはやってほしく無いんじゃないですかね。 歌詞にも著作権はあるので商用楽曲だと気を使うのでしょう。 こういう場合は無理せず他所探したほうがいいです。

ハコサトさんのコメント
>> 「一覧をスクレイプ」というのは、 << ありがとうございます。私の理解、ぜんぜん違いましたね…。 今度こそ理解しました! >> APIだとこういうのが文章化されていて、どうやったら検索できますよ、とか、どの項目が曲名ですよ、リンクですよというのがわかりやすくなっているのと、それらがXMLとかで取得できてNokogiriのようなので簡単に取り出せるようになってます。 << APIやNokogiriへの認識がいままであやふやだったのですが、これを読んで理解が進んだ気がします。 確かにbrタグを変換してタグ消して…みたいな作業は試行錯誤を繰り返したので、 そこが楽になればうれしいかもしれないです。 私の場合、データベースの道に進むならSQLite。覚えておきます。 どうもありがとうございます!
関連質問

●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ