質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Q&A

解決済

2回答

6526閲覧

Nokogiri、anemone、open-uriを使って、指定したサイトをクロール、スクレイピング

takasi

総合スコア7

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

0グッド

0クリップ

投稿2017/01/01 06:36

###前提・実現したいこと
Nokogiri、anemone、open-uriを使い、指定したウェブサイトをクローリング、スクレイピングして、画像リンクURLを取得し、そのURLから画像をダウンロードをするために、次のプログラムを書きました。
###該当のソースコード

Ruby

1require 'open-uri' 2require 'nokogiri' 3require 'anemone' 4 5 6opts = { 7 depth_limit: 1, 8 # 0 => 指定したURL先のみ 9 # 1 => 指定したURLにあるlinkから1回のジャンプで辿れる先も見る。 10 delay: 1,#ページ訪問間隔を1秒空ける 11} 12 13# 取得したいサイトURL 14url = 'http://nekonekomamire.blog86.fc2.com/' 15 16html = open(url) do |f| 17 f.read 18end 19 20 21# htmlを解析して目的のURLを取得 22Anemone.crawl(url, opts) do |anemone| 23 24 # 起点となるページから飛ぶ先を予め指定 25 anemone.focus_crawl do |page| 26 page.links.keep_if { |link| 27 link.to_s.match(/http:\/\/nekonekomamire.blog86.fc2.com\//) 28 # 正規表現でhttp://nekonekomamire.blog86.fc2.com/に当てはまるurlを指定してます。 29 } 30 end 31 32 33 anemone.on_every_page do |page| 34 doc = Nokogiri::HTML.parse(html) 35 36 page.doc.css('div[class="body"]').each do |node| 37 38 gazouURL = node.css('a').attribute('href').to_s 39 puts "#{gazouURL} を取得。" 40 name = File.basename(gazouURL) 41 42 #正規表現で画像ファイル以外は弾くようにしてます。 43 if /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$/ =~ name then 44 else 45 puts "#{name} は画像ファイルではないのでスルーします。" 46 next 47 48 end 49 50 51 # ここで、URLの画像をダウンロードします。 52 open(name, 'wb') do |output| 53 open(gazouURL) do |input| 54 output.write(input.read) 55 end 56 end 57 puts "#{name} をダウンロードしました。" 58 puts "1秒休憩\n\n" 59 sleep(1) 60 end 61 end 62end

これを実行すると、途中までは普通に画像ファイルをダウンロードしてくれるようなのですが、途中で下記のエラーが出て、止まってしまいます。
###発生している問題・エラーメッセージ

Ruby

1C:/pg/Ruby23-x64/lib/ruby/gems/2.3.0/gems/nokogiri-1.7.0-x64-mingw32/ 2lib/nokogiri/xml/node_set.rb:164:in `attr': undefined method 3 `attribute' for nil:NilClass (NoMethodError)

なぜこのようなエラーが出てしまうのでしょうか?
どうすれば解決できるでしょうか?
ご教授いただけると幸いです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

Ruby

1require"open-uri" 2require"certified" 3 4url = "http://nekonekomamire.blog86.fc2.com/" 5 6Dir.mkdir("画像フォルダ") unless Dir.exist?("画像フォルダ") 7Dir.chdir("画像フォルダ") 8 9loop{ 10page_source = open(url, &:read) 11img_url = page_source.scan(%r|img src="(.+?)"|).flatten! 12img_url.reject!{|e| e =~ /logo|counter|banner|close/} 13 14img_url.each do |img| 15 name = File.basename(img) 16 open(name, 'wb') do |output| 17 open(img) do |input| 18 output.write(input.read) 19 end 20 end 21 sleep(rand(4..6)) 22end 23 24next_url = page_source.scan(%r| <a href="(.+?)">次のページ</a>|).flatten! 25if next_url[0] == nil then 26 break 27end 28url = next_url[0] 29} 30

Ruby

1require"open-uri" 2#1:open-uriをrequire(インターネットに接続) 3require"certified" 4#2:certifiedをrequire(SSL証明書エラーを避けるためのgem。) 5 6url = "http://nekonekomamire.blog86.fc2.com/" #3:接続するサイトのURL 7 8Dir.mkdir("画像フォルダ") unless Dir.exist?("画像フォルダ") #4:とってきた画像を保存するためのフォルダを作成 9Dir.chdir("画像フォルダ") #5:#4で作った画像保存用フォルダに移動 10 11loop{#6:ループ 12page_source = open(url, &:read) #7:対象になるページを開いてpage_sourceに入れる。&:readがないとページが読み込まれません。 13 14img_url = page_source.scan(%r|img src="(.+?)"|).flatten! 15=begin 16#8:page_sourceから画像のurlを正規表現でとる。 17結果は[[image0.jpg],[image0.jpg]]みたいな配列になる。このままでは 18#10でFile.basenameをしたときに「配列ですよ」とエラーがでるので 19flatten!をして配列を[image0.jpg, image1.jpg]という形に変更。 20=end 21 22img_url.reject!{|e| e =~ /logo|counter|banner|close/} 23=begin 24#9:#8でとった画像のurlには要らない画像(バナーなど)のurlが含まれている。 25いらない画像のurlを見てみると「logo,counter,banner,close」のいずれかが 26含まれているので該当するimg_urlの要素を削除する。 27=end 28 29img_url.each do |img| #10:img_urlの要素を順番に保存 30 name = File.basename(img) 31 open(name, 'wb') do |output| 32 open(img) do |input| 33 output.write(input.read) 34 end 35 end 36 sleep(rand(4..6)) 37end 38 39next_url = page_source.scan(%r| <a href="(.+?)">次のページ</a>|).flatten! #11:次のページのURLを正規表現でとる。 40if next_url[0] == nil then #12:next_urlの最初の要素がnilになったらloopを終了させる。 41 break 42end 43url = next_url[0] #13:urlにnext_urlを入れる。 44}

私も画像を収集するプログラムを作ってみました。
回答を書いている時点で画像を200程保存できています。
私はanemoneに詳しくないのでそこについては分かりませんが
サイトの方には原因がないような気がします。

参考
robots.txt解析ツール

2017/1/02自己満足のために追記
2017/1/03自己満足のために追記

Ruby

1Dir.chdir("Cache") 2list = Dir.glob("*") 3 4list.each do |name| 5 header = nil 6 File.open(name, "rb") do |f| 7 header = f.read(8) 8 end 9 if header =~ /GIF|JF|Ex|PNG/ then 10 File.rename(name, name + ".gif") if header.include?("GIF") 11 File.rename(name, name + ".jpg") if header.include?("JF") 12 File.rename(name, name + ".jpg") if header.include?("Ex") 13 File.rename(name, name + ".png") if header.include?("PNG") 14 else 15 File.delete(name) 16 end 17end

自分のブラウザのキャッシュから画像を手に入れればサイトに接続する必要がないということで
こんなプログラムを考えてみました。
私はChromeを使っているのでその他のブラウザについてはどうなるかわかりません。
Cacheフォルダの画像以外のファイルを削除するようにしているので注意。

キャッシュの場所についての参考
キャッシュの場所を確認する

Ruby

1require 'open-uri' 2require 'nokogiri' 3require 'anemone' 4 5 6Dir.mkdir("画像フォルダ") unless Dir.exist?("画像フォルダ") 7Dir.chdir("画像フォルダ") 8 9opts = { 10 depth_limit: 1, 11 # 0 => 指定したURL先のみ 12 # 1 => 指定したURLにあるlinkから1回のジャンプで辿れる先も見る。 13 delay: 1,#ページ訪問間隔を1秒空ける 14} 15 16# 取得したいサイトURL 17url = 'http://nekonekomamire.blog86.fc2.com/' 18 19html = open(url) do |f| 20 f.read 21end 22 23 24# htmlを解析して目的のURLを取得 25Anemone.crawl(url, opts) do |anemone| 26 27 # 起点となるページから飛ぶ先を予め指定 28 anemone.focus_crawl do |page| 29 page.links.keep_if { |link| 30 link.to_s.match(/http:\/\/nekonekomamire.blog86.fc2.com\//) 31 # 正規表現でhttp://nekonekomamire.blog86.fc2.com/に当てはまるurlを指定してます。 32 } 33 end 34 35 36 anemone.on_every_page do |page| 37 doc = Nokogiri::HTML.parse(html) 38 39 page.doc.css('div[class="body"]').each do |node| 40 begin 41 gazouURL = node.css('a').attribute('href').to_s 42 puts "#{gazouURL} を取得。" 43 name = File.basename(gazouURL) 44 45 #正規表現で画像ファイル以外は弾くようにしてます。 46 if /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$/ =~ name then 47 # ここで、URLの画像をダウンロードします。 48 open(name, 'wb') do |output| 49 open(gazouURL) do |input| 50 output.write(input.read) 51 end 52 end 53 puts "#{name} をダウンロードしました。" 54 else 55 puts "#{name} は画像ファイルではないのでスルーします。" 56 end 57 rescue 58 puts("エラー") 59 end 60 puts "4秒休憩\n\n" 61 sleep(4) 62 end 63 end 64end 65

resucueを入れてエラーが出ても一応は止まらないようにしました。
これがエラーが出る前のnode.css('a')です。↓

[#<Nokogiri::XML::Element:0x277ec60 name="a" attributes=[#<Nokogiri::XML::Attr:0x277ec24 name="href" value="http://blog-imgs-52.fc2.com/n/e/k/nekonekomamire/20121105013114dde.jpg">, #<Nokogiri::XML::Attr:0x277ec18 name="target" value="_blank">] children=[#<Nokogiri::XML::Element:0x277e720 name="img" attributes=[#<Nokogiri::XML::Attr:0x277e6d8 name="src" value="http://blog-imgs-52.fc2.com/n/e/k/nekonekomamire/20121105013114dde.jpg">, #<Nokogiri::XML::Attr:0x277e6cc name="alt" value="\u30EA\u30DC\u30EB\u30D0\u30FC">, #<Nokogiri::XML::Attr:0x277e6c0 name="border" value="0">, #<Nokogiri::XML::Attr:0x277e6b4 name="width" value="910">, #<Nokogiri::XML::Attr:0x277e6a8 name="height" value="848">]>]>, #<Nokogiri::XML::Element:0x1bc9cfc name="a" attributes=[#<Nokogiri::XML::Attr:0x1bc9ca8 name="href" value="http://blog.fc2.com/theme-6448-11.html">, #<Nokogiri::XML::Attr:0x1bc9c9c name="title" value="\u81EA\u4F5C\u30A4\u30E9\u30B9\u30C8\uFF08\u4E8C\u6B21\u5275\u4F5C\uFF09">] children=[#<Nokogiri::XML::Text:0x1bc9804 "\u81EA\u4F5C\u30A4\u30E9\u30B9\u30C8\uFF08\u4E8C\u6B21\u5275\u4F5C\uFF09">]>, #<Nokogiri::XML::Element:0x1bc96b4 name="a" attributes=[#<Nokogiri::XML::Attr:0x1bc966c name="href" value="http://blog.fc2.com/community-11.html">, #<Nokogiri::XML::Attr:0x1bc9660 name="title" value="\u30A2\u30CB\u30E1\u30FB\u30B3\u30DF\u30C3\u30AF">] children=[#<Nokogiri::XML::Text:0x1bc91a4 "\u30A2\u30CB\u30E1\u30FB\u30B3\u30DF\u30C3\u30AF">]>, #<Nokogiri::XML::Element:0x1bc9054 name="a" attributes=[#<Nokogiri::XML::Attr:0x1bc9018 name="href" value="http://nekonekomamire.blog86.fc2.com/blog-entry-435.html">] children=[#<Nokogiri::XML::Text:0x1bc8cf4 "2012/11/05(\u6708) 01:33:25">]>, #<Nokogiri::XML::Element:0x1bc8bbc name="a" attributes=[#<Nokogiri::XML::Attr:0x1bc8b80 name="href" value="blog-category-10.html">] children=[#<Nokogiri::XML::Text:0x1bc88b0 "\u30EA\u30EA\u30AB\u30EB\u306A\u306E\u306F">]>, #<Nokogiri::XML::Element:0x1bc8760 name="a" attributes=[#<Nokogiri::XML::Attr:0x1bc8724 name="href" value="http://nekonekomamire.blog86.fc2.com/blog-entry-435.html#trackback">] children=[#<Nokogiri::XML::Text:0x1bc8400 "\u30C8\u30E9\u30C3\u30AF\u30D0\u30C3\u30AF:1">]>, #<Nokogiri::XML::Element:0x1bc82c8 name="a" attributes=[#<Nokogiri::XML::Attr:0x1bc828c name="href" value="http://nekonekomamire.blog86.fc2.com/blog-entry-435.html#comment">] children=[#<Nokogiri::XML::Text:0x1bf7efc "\u30B3\u30E1\u30F3\u30C8:0">]>]

これがエラーが出た時のnode.css('a')です。↓

[]

これとは別にこのプログラムは同じ画像を何回も保存しているので
全ての画像を保存し終わるまでにだいぶ時間がかかると考えられます。

投稿2017/01/01 09:58

編集2017/01/02 21:43
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

takasi

2017/01/01 14:53

わざわざプログラムを作ってくださってありがとうございます。 h_aさんが作ってくださったプログラムを私のほうで実行させてもらったところ、たしかにエラーもなく、ちゃんと収集してくれているようです。すごいです。 プログラム初心者なのでcertifiedというライブラリについて全く知らなかったのですが、どんなライブラリなのでしょうか? 調べてみたのですが、Ruby技術者認定試験などばかりが出てきてよくわからなくて… 質問している立場でありながら図々しいかもしれませんが、certifiedを学べるサイトなども教えて頂けると幸いです。
退会済みユーザー

退会済みユーザー

2017/01/01 15:52

「certified」はSSL証明書エラーよけのためのGemです。 open(url)でSSL証明書エラーが発生する人が使います。 2行目の「certified」を削除しても動くようなら「certified」 は削除しても問題はありません。 「certified」が何を行っているかについては 「RubyでHTTPS」(URL:https://mcrn.jp/blog/2015/10/28/205049.html) このページに やってることといえばNet::HTTPとOpenSSL::X509::Storeにモンキーパッチを当てて 証明書の指定(※2) 証明書検証の必須 を自動化してるだけ。 という記述があります。 SSL証明書エラーの原因も含めた詳しい話はこちらのページが参考になるかもしれません。 「Win8+Ruby2.0で SSLのルート証明書の設定・・・諦めた・・・とおもいきや回避! → ・Rubyによるクローラー開発技法(URL:http://anou365.blog.fc2.com/blog-entry-52.html)
takasi

2017/01/02 03:01

コメントの質問にも答えてくださってありがとうございます。 certifiedはSSL証明書エラーするかもと思ったら使えばいいgemなんですね。 追記のプログラムも、ひとつひとつコメントで丁寧に解説してくださってとてもわかりやすかったです。 すごく勉強になりました。本当にありがとうございます。
takasi

2017/01/03 12:51

さらにまたプログラムを作ってくださってありがとうございます。 ブラウザキャッシュから保存する方法もあるんですね。 そんなこともできるとは知りませんでした。 わたしが書いた方のプログラムのほうも resucueを入れれば止まらず保存してくれるんですね。 エラーが出ても、そのまま止まらずちゃんと最後まで保存してくれているようです。 ご心配してくれている同じ画像を何回も保存して時間がかかることに関しては # name のファイルが存在すればという条件式 if FileTest.exist?(name) then puts "#{name} は既にあるのでスルー。" next end こんな感じのコードを入れようと思っています。 親身に何度も追記してくださって本当にありがとうございます。 ベストアンサーにさせていただきます。
退会済みユーザー

退会済みユーザー

2017/01/03 15:14

リンク先の画像も収集する仕組みなら、そのうち月別アーカイブの画像を保存するようになるはずなのでたぶんすべての画像を保存することができるのではと私は思います。 もし可能であれば、初めから月別アーカイブのリンクを踏んでその先にある画像を保存するようにすればさらに良くなるかもしれません。 クローラーの使用は場合によって逮捕されることがあるので、アクセスの間隔には注意してください。 参考:岡崎市立中央図書館事件 キャッシュを使うプログラムは、キャッシュがどの画像かを調べて画像なら拡張子をくっつけて見られるようにするというものです。 twitterの画像を収集したいときには使えるかも。使用する際はブラウザを閉じておかないとエラーが出る場合があるようです。 私こそ長々とありがとうございました。
guest

0

日記画像の無断転載は当然ながら禁止です。

とあるので、普通は画像の閲覧以外の使用を嫌がると思います。
まずは作者にコンタクトし、使用用途を説明した上で、元データを入手されることをおすすめします。

投稿2017/01/01 06:44

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

takasi

2017/01/01 07:09

作者さんのサイトの画像を個人的に閲覧するためのクロールとダウンロードのつもりだったのですが、そういう場合でも作者さんに許可をもらったほうがいいのでしょうか?
退会済みユーザー

退会済みユーザー

2017/01/01 07:12

必要です。
takasi

2017/01/01 07:23

了解しました。作者さんに、許可をいただけるかどうかコンタクトを取ってみます。
退会済みユーザー

退会済みユーザー

2017/01/02 03:57

スクレイピングで画像を収集することは、直接/間接的に作者に迷惑をかけることになります。 必ず、作者に理解をえて下さい。 作者の被る迷惑 ・機械的なアクセスはカウンターに反映されないことが多いので、サイトの露出機会が低下する。 ・サイトの広告スペースが大きくなり、使いにくくなる可能性がある(理由は別記) 無料ブログサイトの被る迷惑 ・機械アクセスでダウンロードされると閲覧による広告収入が減る。 ・機械アクセスに対する負荷対策を行わなければならなくなるので、運用コストが上がる。 →結果として、広告スペースを大きくしたり、掲載数を増やす必要が出る。 スクレイピングを行う上でまず理解しなければならないのは、ルール確認とマナーです。 一般的に、コンテンツ作者が機械アクセスを肯定するシステムであれば、APIが用意されます。 そうでない場合は、サイト利用のポリシーに反するケースが多いので、確実にコンテンツ作者の許可を取り、サイトの利用規約に違反していないか確認を取ることが第一歩です。 私の運営するサイトでもスクレイピングのせいで運営費用が1.5倍になり、スクレイピング対策で、システム構築工数/期間が増えました。 ISP経由でスクレイピングを行っていた方にクレームを入れたこともあります。 機械的なアクセスは、ちょっとした間違いで、サイトに迷惑をかけることもあるので、技術への十分な理解とコンテンツの作者への敬意を持って、作者からの理解をえた上で実施して下さい。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問