【问题标题】:How do I loop my ruby / nokogiri parser through several local HTML files and output the results to one CSV file?如何通过多个本地 HTML 文件循环我的 ruby​​ / nokogiri 解析器并将结果输出到一个 CSV 文件?
【发布时间】:2014-03-30 00:01:09
【问题描述】:

我刚刚编写了我的第一个 ruby​​ 程序,它是一个简单的解析器。我打算用 ruby​​ 和 nokogiri 解析一组 200 个左右的本地 .htm 文件,并将所有内容输出到一个 .csv 文件中。

本地文件的组织方式如下:

root\region_name1\city_name1.htm
root\region_name1\city_name2.htm
root\region_name1\city_name3.htm
root\region_name2\city_name1.htm
...

上述 .htm 文件中的相关 html 源代码如下所示:

<div class="media-body">
    <h4 class="list-group-item-heading"><a ng-href="#/clubs/2001103" class="ng-binding" href="http://www.vereinssuche-nrw.de/#/clubs/2001103">DJK Arminia Eilendorf 1919 e. V.</a> <small ng-show="item.distance > 0" class="ng-binding" style="display: none;">0 km</small></h4>
        <div class="row">
            <div class="col-12 col-lg-6 ng-binding">
                <span ng-show="item.geoadresse.strasse" class="ng-binding">Ulmenstraße 12<br></span>52080 Aachen<br>
                <a ng-href="tel:0241 551424" ng-show="item.telefon" class="ng-binding" href="unsafe:tel:0241 551424">Tel.: 0241 551424<br></a>
                <a ng-href="http://www.DJK-Arminia-Eilendorf.de" ng-show="item.webseite" target="_blank" class="ng-binding" href="http://www.djk-arminia-eilendorf.de/">http://www.DJK-Arminia-Eilendorf.de</a>
            </div>
                <div class="col-lg-6 col-12 visible-lg event-list">
                    <b>Veranstaltungen</b>
                    <!-- ngRepeat: event in item.veranstaltungen | limitTo:3 -->
                <div ng-show="item.veranstaltungen.length == 0" class="text-muted">Keine Veranstaltungen angekündigt.</div>
            <div>
        </div>
</div>

我的 ruby​​ 代码适用于单个 .htm 文件,并通过 XPath 解析/提取我需要的数据。我不想为所有 200 个 .htm 文件解析每个文件并手动合并 output.csv 文件,而是想自动化整个过程,但我真的不知道该怎么做。

这是我的 ruby​​ 代码:

require 'rubygems'
require 'nokogiri'
require 'csv'

# define arrays including a dummy array which is needed for reasons i do not yet know :P
# remember that you can easily adapt this parser to suit your needs by defining additional variables
# and by adding additional xpath steps (doc.xpath...) below
name = Array.new
strasse = Array.new
plzort = Array.new
tel = Array.new
website = Array.new
dummy = Array.new

doc = Nokogiri::HTML(open("aachen.htm"))
puts doc.class   # => Nokogiri::HTML::Document

# search elements via xpath and collect contents in arrays
name = doc.xpath("//div/h4/a").collect {|node| node.text.strip}
strasse = doc.xpath("//div/span[contains(@ng-show,'item.geoadresse.strasse')]").collect {|node| node.text.strip}
plzort = doc.xpath("//div[@id='searchResults']/div/div/div/div/div[1]/text()").collect {|node| node.text.strip}
tel = doc.xpath("//div/a[contains(@ng-show,'item.telefon')]").collect {|node| node.text.strip}
website = doc.xpath("//div/a[contains(@ng-show,'item.webseite')]").collect {|node| node.text.strip}
dummy = doc.xpath("//*[@id='searchResults']/div[39]/div/div/div/div[1]/br").collect {|node| node.text.strip}
plzort.delete("")

# generate CSV file output.csv and force UTF-8
CSV.open("output.csv", "wb:UTF-8") do |csv|
    # prepopulate CSV file with column headings
    csv << ["name", "strasse", "plzort", "tel", "website", "dummy"]
    # repeat extraction process until name array returns nothing i.e. no more elements on page
    until name.empty?
        # write everything to CSV file
        csv << [name.shift, strasse.shift, plzort.shift, tel.shift, website.shift, dummy.shift]
  end
end

我已经阅读了 ruby​​ 和 nokogiri 文档,但是我不知道如何继续。

【问题讨论】:

    标签: ruby parsing csv xpath nokogiri


    【解决方案1】:

    这是我编写代码部分的方式:

    name = Array.new
    strasse = Array.new
    plzort = Array.new
    tel = Array.new
    website = Array.new
    dummy = Array.new
    

    可以更清楚地写成:

    name = []
    strasse = []
    plzort = []
    tel = []
    website = []
    dummy = []
    

    但是,没有必要在 Ruby 中初始化变量。相反,直接分配给他们...

    name = doc.xpath("//div/h4/a").collect {|node| node.text.strip}
    strasse = doc.xpath("//div/span[contains(@ng-show,'item.geoadresse.strasse')]").collect {|node| node.text.strip}
    plzort = doc.xpath("//div[@id='searchResults']/div/div/div/div/div[1]/text()").collect {|node| node.text.strip}
    tel = doc.xpath("//div/a[contains(@ng-show,'item.telefon')]").collect {|node| node.text.strip}
    website = doc.xpath("//div/a[contains(@ng-show,'item.webseite')]").collect {|node| node.text.strip}
    dummy = doc.xpath("//*[@id='searchResults']/div[39]/div/div/div/div[1]/br").collect {|node| node.text.strip}
    

    会这样做,但它不雅且浪费。相反,使用这样的东西:

    name, strasse, plzort, tel, website, dummy = [
      "//div/h4/a"
      "//div/span[contains(@ng-show,'item.geoadresse.strasse')]"
      "//div[@id='searchResults']/div/div/div/div/div[1]/text()"
      "//div/a[contains(@ng-show,'item.telefon')]"
      "//div/a[contains(@ng-show,'item.webseite')]"
      "//*[@id='searchResults']/div[39]/div/div/div/div[1]/br"
    ].map { |s|
      doc.xpath(s).collect {|node| node.text.strip}
    }
    

    您的 XPath 成为您迭代的数组中的数据,每次都执行相同的操作。它使代码更易于理解和维护。

    plzort.delete("")
    

    不会做你认为会做的事。当plzort被赋值时,它会是一个NodeSet,它不知道如何delete("")

    plzort = doc.xpath('//bar')
    plzort.delete("") # => 
    # ~> -:9:in `delete': node must be a Nokogiri::XML::Node or Nokogiri::XML::Namespace (ArgumentError)
    # ~>  from -:9:in `<main>'
    

    【讨论】:

      【解决方案2】:

      可能最简单的方法是将所有文件移动到一个目录中。然后,您可以使用Dir.foreach 遍历该目录中的条目,并稍微更改当前脚本以将结果附加到输出文件中。

      假设您的脚本现在适用于一个文件,一旦您循环遍历目录中的所有文件,请将硬编码的文件名替换为迭代器变量名,并将输出文件的模式从 "wb" (写)到"ab"(追加)

      Dir.foreach('root\region_name1') do |file|
         name = Array.new
         strasse = Array.new
         plzort = Array.new
         tel = Array.new
         website = Array.new
         dummy = Array.new
      
         doc = Nokogiri::HTML(open("#{file}"))   #Instead of hardcoding filename use iterator variable.
         puts doc.class   # => Nokogiri::HTML::Document
      
         # search elements via xpath and collect contents in arrays
         name = doc.xpath("//div/h4/a").collect {|node| node.text.strip}
         strasse = doc.xpath("//div/span[contains(@ng-show,'item.geoadresse.strasse')]").collect {|node|      node.text.strip}
         plzort = doc.xpath("//div[@id='searchResults']/div/div/div/div/div[1]/text()").collect {|node|   node.text.strip}
         tel = doc.xpath("//div/a[contains(@ng-show,'item.telefon')]").collect {|node| node.text.strip}
         website = doc.xpath("//div/a[contains(@ng-show,'item.webseite')]").collect {|node| node.text.strip}
         dummy = doc.xpath("//*[@id='searchResults']/div[39]/div/div/div/div[1]/br").collect {|node| node.text.strip}
         plzort.delete("")
      
        # generate CSV file output.csv and force UTF-8
        CSV.open("output.csv", "ab:UTF-8") do |csv|          #Change to ab to append to output file instead of overwrite
        # prepopulate CSV file with column headings
        csv << ["name", "strasse", "plzort", "tel", "website", "dummy"]
        # repeat extraction process until name array returns nothing i.e. no more elements on page
        until name.empty?
          # write everything to CSV file
          csv << [name.shift, strasse.shift, plzort.shift, tel.shift, website.shift, dummy.shift]
        end
       end
      end
      

      如果您有很多目录并且无法将所有 .htm 文件移动到一个位置,则可以应用相同的逻辑,但您首先必须遍历它们的父目录,然后遍历每个 .htm 文件。每个子目录中的.htm 文件:

      Dir.foreach("parent_directory") do |folder|
          Dir.foreach("#{folder}"} do |file|
             # insert script here
          end
      end
      

      DirFileUtils 模块对于遍历文件和文件夹非常有用。

      【讨论】:

      • 感谢您的提示!我想我明白这应该如何工作。但是使用您的代码并给出上述“root\region_name1”结构并将所有 .htm 文件放在“region_name1”文件夹中会在我的控制台中引发以下错误:nokoparse.rb:20:in 'initialize':Permission denied - . (Errno::EACCES) from nokoparse.rb:20:in 'open' from nokoparse.rb:20:in 'block in &lt;main&gt;' from nokoparse.rb:20:in 'foreach' from nokoparse.rb:20:in '&lt;main&gt;'
      • 如果需要递归地进入目录,我建议考虑使用 Ruby 的内置 Find 类。此外,Pathname 很好地将 File、Dir、Find 和 FileUtils 封装到一个类中,从而实现更简洁的实现。
      猜你喜欢
      • 2020-01-24
      • 2023-03-12
      • 2020-08-27
      • 1970-01-01
      • 2020-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-06
      相关资源
      最近更新 更多