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

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

ただいまの
回答率

90.03%

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

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 4,542

takasi

score 5

前提・実現したいこと

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

該当のソースコード

require 'open-uri'
require 'nokogiri'
require 'anemone'


opts = {
    depth_limit: 1,
        # 0 => 指定したURL先のみ
        # 1 => 指定したURLにあるlinkから1回のジャンプで辿れる先も見る。
    delay: 1,#ページ訪問間隔を1秒空ける
}

# 取得したいサイトURL
url = 'http://nekonekomamire.blog86.fc2.com/'

html = open(url) do |f|
   f.read
end


# htmlを解析して目的のURLを取得
Anemone.crawl(url, opts) do |anemone|

     # 起点となるページから飛ぶ先を予め指定
    anemone.focus_crawl do |page|
     page.links.keep_if { |link|
      link.to_s.match(/http:\/\/nekonekomamire.blog86.fc2.com\//)
      # 正規表現でhttp://nekonekomamire.blog86.fc2.com/に当てはまるurlを指定してます。
     }
    end


    anemone.on_every_page do |page|
      doc = Nokogiri::HTML.parse(html)

      page.doc.css('div[class="body"]').each do |node|

        gazouURL  = node.css('a').attribute('href').to_s
        puts "#{gazouURL} を取得。"
        name = File.basename(gazouURL)

        #正規表現で画像ファイル以外は弾くようにしてます。
        if /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$/ =~ name then 
         else
         puts "#{name} は画像ファイルではないのでスルーします。"
         next

        end


        # ここで、URLの画像をダウンロードします。
        open(name, 'wb') do |output|
         open(gazouURL) do |input|
          output.write(input.read)
         end
        end
        puts "#{name} をダウンロードしました。"
        puts "1秒休憩\n\n"
        sleep(1)
      end
    end
end


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

発生している問題・エラーメッセージ

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


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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

require"open-uri"
require"certified"

url = "http://nekonekomamire.blog86.fc2.com/"

Dir.mkdir("画像フォルダ") unless Dir.exist?("画像フォルダ")
Dir.chdir("画像フォルダ")

loop{
page_source = open(url, &:read)
img_url = page_source.scan(%r|img src="(.+?)"|).flatten!
img_url.reject!{|e| e =~ /logo|counter|banner|close/}

img_url.each do |img|
  name = File.basename(img)
  open(name, 'wb') do |output|
   open(img) do |input|
    output.write(input.read)
   end
  end
  sleep(rand(4..6))
end

next_url = page_source.scan(%r| <a href="(.+?)">次のページ</a>|).flatten!
if next_url[0] == nil then
  break
end
url = next_url[0]
}
require"open-uri"
#1:open-uriをrequire(インターネットに接続)
require"certified"
#2:certifiedをrequire(SSL証明書エラーを避けるためのgem。)

url = "http://nekonekomamire.blog86.fc2.com/" #3:接続するサイトのURL

Dir.mkdir("画像フォルダ") unless Dir.exist?("画像フォルダ") #4:とってきた画像を保存するためのフォルダを作成
Dir.chdir("画像フォルダ") #5:#4で作った画像保存用フォルダに移動

loop{#6:ループ
page_source = open(url, &:read) #7:対象になるページを開いてpage_sourceに入れる。&:readがないとページが読み込まれません。

img_url = page_source.scan(%r|img src="(.+?)"|).flatten!
=begin
#8:page_sourceから画像のurlを正規表現でとる。
結果は[[image0.jpg],[image0.jpg]]みたいな配列になる。このままでは
#10でFile.basenameをしたときに「配列ですよ」とエラーがでるので
flatten!をして配列を[image0.jpg, image1.jpg]という形に変更。
=end

img_url.reject!{|e| e =~ /logo|counter|banner|close/}
=begin
#9:#8でとった画像のurlには要らない画像(バナーなど)のurlが含まれている。
いらない画像のurlを見てみると「logo,counter,banner,close」のいずれかが
含まれているので該当するimg_urlの要素を削除する。
=end

img_url.each do |img| #10:img_urlの要素を順番に保存
  name = File.basename(img)
  open(name, 'wb') do |output|
   open(img) do |input|
    output.write(input.read)
   end
  end
  sleep(rand(4..6))
end

next_url = page_source.scan(%r| <a href="(.+?)">次のページ</a>|).flatten! #11:次のページのURLを正規表現でとる。
if next_url[0] == nil then #12:next_urlの最初の要素がnilになったらloopを終了させる。
  break
end
url = next_url[0] #13:urlにnext_urlを入れる。
}


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

参考
robots.txt解析ツール

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

Dir.chdir("Cache")
list = Dir.glob("*")

list.each do |name|
  header = nil
  File.open(name, "rb") do |f|
    header = f.read(8)
  end
  if header =~ /GIF|JF|Ex|PNG/ then
    File.rename(name, name + ".gif") if header.include?("GIF")
    File.rename(name, name + ".jpg") if header.include?("JF")
    File.rename(name, name + ".jpg") if header.include?("Ex")
    File.rename(name, name + ".png") if header.include?("PNG")
  else
    File.delete(name)
  end
end


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

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

require 'open-uri'
require 'nokogiri'
require 'anemone'


Dir.mkdir("画像フォルダ") unless Dir.exist?("画像フォルダ")
Dir.chdir("画像フォルダ")

opts = {
    depth_limit: 1,
        # 0 => 指定したURL先のみ
        # 1 => 指定したURLにあるlinkから1回のジャンプで辿れる先も見る。
    delay: 1,#ページ訪問間隔を1秒空ける
}

# 取得したいサイトURL
url = 'http://nekonekomamire.blog86.fc2.com/'

html = open(url) do |f|
   f.read
end


# htmlを解析して目的のURLを取得
Anemone.crawl(url, opts) do |anemone|

     # 起点となるページから飛ぶ先を予め指定
    anemone.focus_crawl do |page|
     page.links.keep_if { |link|
      link.to_s.match(/http:\/\/nekonekomamire.blog86.fc2.com\//)
      # 正規表現でhttp://nekonekomamire.blog86.fc2.com/に当てはまるurlを指定してます。
     }
    end


    anemone.on_every_page do |page|
      doc = Nokogiri::HTML.parse(html)

      page.doc.css('div[class="body"]').each do |node|
        begin
          gazouURL  = node.css('a').attribute('href').to_s
          puts "#{gazouURL} を取得。"
          name = File.basename(gazouURL)

          #正規表現で画像ファイル以外は弾くようにしてます。
          if /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$/ =~ name then
            # ここで、URLの画像をダウンロードします。
            open(name, 'wb') do |output|
             open(gazouURL) do |input|
              output.write(input.read)
             end
            end
            puts "#{name} をダウンロードしました。"
          else
            puts "#{name} は画像ファイルではないのでスルーします。"
          end
         rescue
           puts("エラー")
         end
        puts "4秒休憩\n\n"
        sleep(4)
      end
    end
end


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/02 12:01

    コメントの質問にも答えてくださってありがとうございます。
    certifiedはSSL証明書エラーするかもと思ったら使えばいいgemなんですね。

    追記のプログラムも、ひとつひとつコメントで丁寧に解説してくださってとてもわかりやすかったです。
    すごく勉強になりました。本当にありがとうございます。

    キャンセル

  • 2017/01/03 21:51

    さらにまたプログラムを作ってくださってありがとうございます。
    ブラウザキャッシュから保存する方法もあるんですね。
    そんなこともできるとは知りませんでした。

    わたしが書いた方のプログラムのほうも
    resucueを入れれば止まらず保存してくれるんですね。
    エラーが出ても、そのまま止まらずちゃんと最後まで保存してくれているようです。

    ご心配してくれている同じ画像を何回も保存して時間がかかることに関しては

    # name のファイルが存在すればという条件式
    if FileTest.exist?(name) then
    puts "#{name} は既にあるのでスルー。"
    next
    end

    こんな感じのコードを入れようと思っています。


    親身に何度も追記してくださって本当にありがとうございます。
    ベストアンサーにさせていただきます。

    キャンセル

  • 2017/01/04 00:14

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

    キャンセル

0

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/01 16:09

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

    キャンセル

  • 2017/01/01 16:12

    必要です。

    キャンセル

  • 2017/01/01 16:23

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

    キャンセル

  • 2017/01/02 12:57

    スクレイピングで画像を収集することは、直接/間接的に作者に迷惑をかけることになります。
    必ず、作者に理解をえて下さい。

    作者の被る迷惑
    ・機械的なアクセスはカウンターに反映されないことが多いので、サイトの露出機会が低下する。
    ・サイトの広告スペースが大きくなり、使いにくくなる可能性がある(理由は別記)

    無料ブログサイトの被る迷惑
    ・機械アクセスでダウンロードされると閲覧による広告収入が減る。
    ・機械アクセスに対する負荷対策を行わなければならなくなるので、運用コストが上がる。
    →結果として、広告スペースを大きくしたり、掲載数を増やす必要が出る。

    スクレイピングを行う上でまず理解しなければならないのは、ルール確認とマナーです。

    一般的に、コンテンツ作者が機械アクセスを肯定するシステムであれば、APIが用意されます。
    そうでない場合は、サイト利用のポリシーに反するケースが多いので、確実にコンテンツ作者の許可を取り、サイトの利用規約に違反していないか確認を取ることが第一歩です。

    私の運営するサイトでもスクレイピングのせいで運営費用が1.5倍になり、スクレイピング対策で、システム構築工数/期間が増えました。
    ISP経由でスクレイピングを行っていた方にクレームを入れたこともあります。

    機械的なアクセスは、ちょっとした間違いで、サイトに迷惑をかけることもあるので、技術への十分な理解とコンテンツの作者への敬意を持って、作者からの理解をえた上で実施して下さい。

    キャンセル

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

  • ただいまの回答率 90.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る