

勉強でruby言語を使用して、webスクレイピングに挑戦しております。

googleから検索を行い、以下の様なCSVファイルを作成するイメージです。

★CSV出力イメージ（※左から「タイトル」,「URL」,「文面」）

[code]

    google,www.google.co.jp,世界中のあらゆる情報を検索するためのツールを提供しています。
    
[/code]

`each`を使用して出力ファイルを作成して、  
別のプログラムから3個のファイルを1つのファイルに結合しようとしました。  
そうすると3個のファイルの検索順序があっていないことがわかりました。

【出力結果のイメージ】

■タイトル.txt

[code]

    タイトル1:a
    タイトル2:b
    タイトル3:c
    
[/code]

■URL.txt

[code]

    URL1:a
    URL3:c
    URL2:b
    
[/code]

■文面.txt

[code]

    文面1:a
    文面2:b
    文面3:c
    
[/code]

そこで、検索結果を1個の出力ファイルに集約しようとしたのですが、  
下記ソースで実行した結果こうなってしまいました。

【出力結果のイメージ】

■まとめ.txt

[code]

    タイトル1:a
    タイトル2:b
    タイトル3:c
    URL1:a
    URL2:b
    URL3:c
    文面1:a
    文面2:b
    文面3:c
    
[/code]

完成系としては、以下の様にしたいと思ってます。

■まとめ完成系.txt

[code]

    タイトル1:a,URL1:a,文面1:a
    タイトル2:b,URL2:b,文面2:b
    タイトル3:c,URL3:c,文面3:c
    
[/code]

いくらやってもうまくいかないので、ご質問させていただきました。  
初心者的な質問で恐縮ですが、どなたかアドバイスをいただけないでしょうか？

作成したソースを記載いたします。  
ご教示の程、よろしくお願いいたします  
※rubyのバージョンは2.1.5p273です。

★ソース

[code]

    rec = 'https://www.google.com/search?q=google&oe=utf-8&hl=ja'
    count = 0
    ST = "&start="
    
    #ファイルクリア
    File.open("/src/out/all","w")
    
    #検索処理
    for i in 1..2
    
            #String型に変換して結合
            search = rec.to_s + ST.to_s + count.to_s
    
            #google検索する文字コードを変換
            escaped_url = URI.escape(search)
            count += 10
            doc = Nokogiri::HTML(open(escaped_url))
    
    
            #タイトル取得
            doc.xpath('//h3/a').each do |link|
                    $cont = []
                    $cont.push
                    $cont.push(link.content)
                    $stdout = File.open("/src/out/all","a")
                    puts $cont[0]
                    $stdout = STDOUT
            end
    
            #URLの取得
            doc.xpath('//div[1]/cite').each do |url|
                    $ul = []
                    $ul.push
                    $ul.push(url.content)
                    $stdout = File.open("/src/out/all","a")
                    puts $ul[0]
                    $stdout = STDOUT
            end
    
            #文章の取得
            doc.xpath('//div/span').each do |link|
                    $body= []
                    $body.push(link.content)
                    $stdout = File.open("/src/out/all","a")
                    puts $body[0]
                    $stdout = STDOUT
            end
    
    end
    
[/code]

以上 よろしくお願いいたします。


CC BY-SA 3.0
2015-01-12T17:24:02.593
4458
2015-01-17T15:59:06.677
2015-01-17T10:36:34.780
4114
7332
question
ruby web-scraping
rubyのeachを使用したファイルの結合方法について


以下の様に書いてみました。

[code]

    require 'open-uri'
    require 'nokogiri'
    require 'csv'
    
    search_word = 'google'
    search_url  = 'https://www.google.com/search?'
    search_url += 'q=' + search_word
    search_url += '&oe=utf-8&hl=ja&start=0&num=20'
    escaped_url = URI.escape(search_url)
    output_csv  = 'search.csv'
    
    nth = 1
    doc = Nokogiri::HTML(open(escaped_url))
    CSV.open(output_csv, "wb") do |csv|
      doc.xpath('//li[@class="g"]').each do |li|
        title = li.xpath('h3/a').text
          url = li.xpath('div/div/cite').text
          exp = li.xpath('div/span').text.gsub(/\r?\n/, '')
    
        if title != '' then
          title = sprintf("タイトル%d:%s", nth, title)
            url = sprintf("URL%d:%s", nth, url)
            exp = sprintf("文面%d:%s", nth, exp)
          csv << [title, url, exp]
          nth += 1
        end
      end
    end
    
[/code]

お望みの物とは細かい部分で異なっているかと思いますが、そこは適宜変更して下さい。  
検索結果は一件ごとに `<li class="g">` タグで囲まれています(※)。一件ごとに抽出して、さらに内部のノード(`cite` や
`span`)を取り出しています。そして、Ruby の CSV パッケージを利用して抽出結果を CSV 形式でファイルに保存しています。

※ 検索結果には「ニューストピック」などの広告？が差し込まれますが、それらは除外しています。


CC BY-SA 3.0
2015-01-12T19:48:57.533
4460
2015-01-12T20:05:11.943
2015-01-12T20:05:11.943
4458
answer


まず。  
Google の検索結果はかなり複雑な構成になっているので、例外処理を潰すのがすこし大変でした。

続いて、作成なさったスクリプトについて。

  * for 文があるのに別途 count があったりして、無駄が多いと思います。また count の増加位置も違和感を感じます。末尾で行うべきでしょう（それ以前に count は省けます）。
  * グローバル変数を多用しているのが気になります。グローバル変数を使わないのが Ruby 流だと思います。 Perl と同じように書こうとしておられるのであれば、スカラー変数でも `$` は不要ですので外してください。（1/18 1:00追記： PHP も変数が `$` から始まりますね。 Ruby では不要です。）
  * 標準出力に `File.open` を置き換えて、また戻して、という処理をしているのは非常にもどかしいです。 `File.open` の後ろにブロックを付けて処理してください。
  * `URI.escape` メソッドは古いメソッドで非推奨です。 `CGI.escape` メソッドを使いましょう。

では、私が作成したスクリプトについて簡単に方針説明します。

  * Nokogiri::HTML オブジェクトを xpath で切り出すのは同様ですが、タイトルやリンクなどのひとまとまりの要素を大きく取り出してから、それをさらに xpath で切り分けています。
  * まず出力ファイルの形式をバッファに入れ、スクリプトの末尾でファイルへ書き出しています。
  * CSV での出力をご希望とのことなので次のように出力するように作成しました。スペースなどが含まれることがあるので引用符で括りました。

"タイトル1","URL1","文面1"  
"タイトル2","URL2","文面2"  
"タイトル3","URL3","文面3"

ではスクリプトを示しておきます。全面的に書き換えました。

[code]

    require 'nokogiri'
    require 'open-uri'
    require 'cgi/util'
    
    # 検索キーワード
    keyword = "google"
    base_url = "https://www.google.com/search?q=#{CGI.escape(keyword)}&oe=utf-8&hl=ja&start="
    # 出力先
    file_path = "./output.csv" 
    # 出力バッファ
    output = ""
    
    2.times.each do |i|
      search_url = base_url + (i * 10).to_s
      doc = Nokogiri::HTML.parse(open(search_url).read)
      doc.xpath('//li[@class="g"]').each do |item|
        # タイトル取得
        title = item.xpath('.//h3/a').first.content
        # 画像検索及びニュース検索の結果の項目の場合は飛ばす
        next if title[/の画像検索結果/] || title[/のニュース検索結果/]
        # URLを取得（ただし地図リンクは特殊）
        # cite タグだと item.xpath('.//cite').first.content だが、返値がリンク先URLとは限らない
        anchor_include_map = item.xpath('.//h3/a').first["href"]
        anchor = if anchor_include_map[%r!^/url!]
          anchor_include_map[%r!(?<=/url\?q=)[^&]+!]
        else
          anchor_include_map
        end
        link = CGI.unescape(anchor)
        # 文面取得
        source_text = item.xpath('.//span[@class="st"]')
        # 地図リンクだと文面が無いのでその対策
        # 作成日表記が鬱陶しいので除去
        # 改行も除去
        text = source_text.empty? ? "" : source_text.first.content.tr("\n", "").gsub(/\d{4}年\d{1,2}月\d{1,2}日 ... /, "")
        # 出力バッファに一旦溜め込む
        output << [title, link, text].map{ |a| '"' + a + '"' }.join(",") + "\n"
      end
    end
    
    # ファイルへ出力（上書き）
    open(file_path,"w") { |f| f.write(output) }
    
[/code]

XPath も正規表現もかなりややこしいことをしていたりするので、不明点があればコメントください。


CC BY-SA 3.0
2015-01-17T10:21:54.557
4738
2015-01-17T15:59:06.677
2015-01-17T15:59:06.677
4114
4114
4458
answer