【问题标题】:Parsing XLS and XLSX (MS Excel) files with Ruby?用 Ruby 解析 XLS 和 XLSX (MS Excel) 文件?
【发布时间】:2018-12-21 20:05:13
【问题描述】:

是否有任何 gem 能够解析 XLS 和 XLSX 文件?我找到了电子表格和 ParseExcel,但它们都不懂 XLSX 格式。

【问题讨论】:

    标签: ruby excel


    【解决方案1】:

    我最近需要用 Ruby 解析一些 Excel 文件。丰富的库和选项结果令人困惑,所以我写了一个 blog post 来讨论它。

    下表列出了不同的 Ruby 库及其支持的内容:

    如果您关心性能,以下是 xlsx 库的比较:

    我有使用每个支持的库here 读取 xlsx 文件的示例代码

    以下是一些使用不同库读取 xlsx 文件的示例:

    rubyXL

    require 'rubyXL'
    
    workbook = RubyXL::Parser.parse './sample_excel_files/xlsx_500_rows.xlsx'
    worksheets = workbook.worksheets
    puts "Found #{worksheets.count} worksheets"
    
    worksheets.each do |worksheet|
      puts "Reading: #{worksheet.sheet_name}"
      num_rows = 0
      worksheet.each do |row|
        row_cells = row.cells.map{ |cell| cell.value }
        num_rows += 1
      end
      puts "Read #{num_rows} rows"
    end
    

    roo

    require 'roo'
    
    workbook = Roo::Spreadsheet.open './sample_excel_files/xlsx_500_rows.xlsx'
    worksheets = workbook.sheets
    puts "Found #{worksheets.count} worksheets"
    
    worksheets.each do |worksheet|
      puts "Reading: #{worksheet}"
      num_rows = 0
      workbook.sheet(worksheet).each_row_streaming do |row|
        row_cells = row.map { |cell| cell.value }
        num_rows += 1
      end
      puts "Read #{num_rows} rows" 
    end
    

    小溪

    require 'creek'
    
    workbook = Creek::Book.new './sample_excel_files/xlsx_500_rows.xlsx'
    worksheets = workbook.sheets
    puts "Found #{worksheets.count} worksheets"
    
    worksheets.each do |worksheet|
      puts "Reading: #{worksheet.name}"
      num_rows = 0
      worksheet.rows.each do |row|
        row_cells = row.values
        num_rows += 1
      end
      puts "Read #{num_rows} rows"
    end
    

    simple_xlsx_reader

    require 'simple_xlsx_reader'
    
    workbook = SimpleXlsxReader.open './sample_excel_files/xlsx_500000_rows.xlsx'
    worksheets = workbook.sheets
    puts "Found #{worksheets.count} worksheets"
    
    worksheets.each do |worksheet|
      puts "Reading: #{worksheet.name}"
      num_rows = 0
      worksheet.rows.each do |row|
        row_cells = row
        num_rows += 1
      end
      puts "Read #{num_rows} rows"
    end
    

    以下是使用spreadsheet 库读取旧版xls 文件的示例:

    电子表格

    require 'spreadsheet'
    
    # Note: spreadsheet only supports .xls files (not .xlsx)
    workbook = Spreadsheet.open './sample_excel_files/xls_500_rows.xls'
    worksheets = workbook.worksheets
    puts "Found #{worksheets.count} worksheets"
    
    worksheets.each do |worksheet|
      puts "Reading: #{worksheet.name}"
      num_rows = 0
      worksheet.rows.each do |row|
        row_cells = row.to_a.map{ |v| v.methods.include?(:value) ? v.value : v }
        num_rows += 1
      end
      puts "Read #{num_rows} rows"
    end
    

    【讨论】:

    • 这篇文章很棒,我投了赞成票,但不幸的是,我发现 roo 和电子表格都不能处理我的 .xls 数据。
    • Thnks @guero64 roo 的 xls 功能实际上保存在另一个名为 roo-xls github.com/roo-rb/roo-xls 的项目中。你试过那个库吗?
    • 我发现了问题。生成文件的源将它们保存为 .xls,但内容是 HTML。不过,感谢您的意见。
    • 你有没有发现任何一个将名称定义为范围的合理工作?例如。使用 openpyxl:gist.github.com/empiricalthought/…
    • 伟大的比较工作!我正在尝试使用 roo,如果您设法从单元格中提取 cmets,我正在徘徊,以及如何使用提供的函数来实现这一点?
    【解决方案2】:

    刚刚找到 roo,它可能会完成这项工作 - 可以满足我的要求,阅读基本的电子表格。

    【讨论】:

    • roo 确实有效,但令人沮丧的是,它不像 Ruby,而且(对我来说,无论如何)非常令人惊讶:不能使用 each 来迭代行?无法迭代工作表? “默认工作表”的概念,然后通过工作簿对象访问单元格?
    • 我花了一段时间才找到,但是this now-official roo fork,你必须明确地固定,解决了我对 roo 的抱怨。它具有#each、#to_a、合理的工作表访问权限,并且不会因需要 ruby​​-spreadsheet 而使用Spreadsheet 污染全局命名空间。
    • @woahdae 太棒了!很高兴看到具有这些新功能的示例。有没有可用的文档?我对能够遍历工作簿的每个工作表的每一行特别感兴趣。
    • 该分叉的自述文件有一个额外的部分,介绍了分叉中的新内容。但是,在实现需要良好类型转换的 xlsx 上传之后,我发现 roo 类型转换有很多不足之处。当试图将“2”(格式化为数字)解析为日期时,它会窒息。我自己写了一个我更喜欢的解析器,今晚我会上传到 github 并回复你。
    • @woahdae 好东西。期待看到你的作品。请尽可能发送链接。
    【解决方案3】:

    roo gem 非常适用于 Excel(.xls 和 .xlsx),并且正在积极开发中。

    我同意语法不是很好,也不像 ruby​​。但这可以通过以下方式轻松实现:

    class Spreadsheet
      def initialize(file_path)
        @xls = Roo::Spreadsheet.open(file_path)
      end
    
      def each_sheet
        @xls.sheets.each do |sheet|
          @xls.default_sheet = sheet
          yield sheet
        end
      end
    
      def each_row
        0.upto(@xls.last_row) do |index|
          yield @xls.row(index)
        end
      end
    
      def each_column
        0.upto(@xls.last_column) do |index|
          yield @xls.column(index)
        end
      end
    end
    

    【讨论】:

    • 小心这个命名约定 - 电子表格是一个现有的常量,引用一个模块:Spreadsheet.class # => Module 将类重命名为“Roobook”之类的东西可以解决这个问题。不过,干得好!
    • 最新的 roo(在您指向的 empact fork 上)不会污染命名空间,并且带有 #each 等。最后!耶 empact。
    • Roo gem 对于大文件很糟糕。打开一个 5MB XLSx 文件可能需要 30-60 秒,这没有任何意义。
    • Roo 需要很长时间,因为它必须将所有内容加载到内存中。它似乎也将电子表格解析成一个可用的数据结构,这可能很慢。
    • 请@Bruno Buccolo 在rails 项目中将这样的文件保存在哪里。
    【解决方案4】:

    我正在使用使用 nokogiri 的小溪。它很快。在我的 Macbook Air 上的 21x11250 xlsx 表上使用了 8.3 秒。让它在 ruby​​ 1.9.3+ 上工作。每行的输出格式是行和列名到单元格内容的哈希: {"A1"=>"一个单元格", "B1"=>"另一个单元格"} 散列不保证键将按照原始列顺序。 https://github.com/pythonicrubyist/creek

    dullard 是另一个使用 nokogiri 的好方法。它超级快。在我的 Macbook Air 上的 21x11250 xlsx 表上使用了 6.7 秒。让它在 ruby​​ 2.0.0+ 上工作。每行的输出格式是一个数组: [“一个单元格”,“另一个单元格”] https://github.com/thirtyseven/dullard

    刚才提到的simple_xlsx_reader 不错,就是有点慢。在我的 Macbook Air 上的 21x11250 xlsx 表上使用了 91 秒。让它在 ruby​​ 1.9.3+ 上工作。每行的输出格式是一个数组: [“一个单元格”,“另一个单元格”] https://github.com/woahdae/simple_xlsx_reader

    另一个有趣的是oxcelix。它使用 ox 的 SAX 解析器,据说比 nokogiri 的 DOM 和 SAX 解析器都快。它应该输出一个矩阵。我无法让它工作。此外,rubyzip 存在一些依赖性问题。不推荐。

    总之,小溪似乎是一个不错的选择。其他帖子推荐 simple_xlsx_parser,因为它具有类似的性能。

    按照建议删除了 dullard,因为它已经过时并且人们会遇到错误/遇到问题。

    【讨论】:

    • 这篇文章应该是第一
    • 谢谢分享。我发现使用 Dullard gem 从 XLSX 文件流式传输 100K+ 行快速且内存高效。
    • dullard 对我来说充满了错误(非拉丁数据)。 creek 给了我需要的东西
    • okliv,如果您能在此处指定哪些排版不适用于 dullard,那就太棒了。另外,在 github 上向愚蠢的问题跟踪器发送帖子! :)
    • 这个答案提到只读取'xlsx'文件,'xls'文件呢?
    【解决方案5】:

    如果您正在寻找更现代的库,请查看电子表格:http://spreadsheet.rubyforge.org/GUIDE_txt.html。 我不知道它是否支持 XLSX 文件,但考虑到它正在积极开发,我猜它支持(我不在 Windows 上,也没有 Office,所以我无法测试)。

    此时,roo 似乎又是一个不错的选择。它支持 XLSX,仅通过使用 times 和单元格访问来允许(某些)迭代。我承认,虽然它并不漂亮。

    此外,RubyXL 现在可以使用他们的extract_data 方法为您提供一种迭代,该方法为您提供一个二维数据数组,可以轻松地对其进行迭代。

    或者,如果您尝试在 Windows 上使用 XLSX 文件,您可以使用 Ruby 的 Win32OLE 库,该库允许您与 OLE 对象进行交互,例如 Word 和 Excel 提供的对象。 但是,正如 @PanagiotisKanavos 在 cmets 中提到的那样,这有几个主要缺点:

    • 必须安装 Excel
    • 为每个文档启动一个新的 Excel 实例
    • 内存和其他资源消耗远远超过简单的 XLSX 文档操作所需的资源。

    但如果你选择使用它,你可以选择不显示 Excel,加载你的 XLSX 文件,然后通过它访问它。我不确定它是否支持迭代,但是,我认为围绕提供的方法构建不会太难,因为它是用于 Excel 的完整 Microsoft OLE API。 这是文档:http://support.microsoft.com/kb/222101 这是宝石:http://www.ruby-doc.org/stdlib-1.9.3/libdoc/win32ole/rdoc/WIN32OLE.html

    同样,选项看起来并没有好多少,但恐怕没有太多其他选择。很难解析作为黑盒的文件格式。而那些设法打破它的少数人并没有那么明显地做到这一点。 Google Docs 是闭源的,LibreOffice 是几千行的 harry C++。

    【讨论】:

    • 非常有用的信息!我目前正在构建一个 excel 爬虫并适合它(stackoverflow.com/questions/14044357/…)。我已经放弃了 roo,因为迭代相当痛苦。但是,我很想用 RubyXL 尝试 extract_data
    • 将 OLE 用于 XLSX 是一个 的想法 - XLSX 只是具有众所周知的格式的压缩 XML。它绝对是 不是一个黑盒子- Open XML 格式定义得非常好,Open XML SDK 提供了手动创建 XML 所需的所有信息并且有很多库可以大大简化 XLSX 的使用。
    • @PanagiotisKanavos:有趣的一点。虽然我当然明白为什么这样做会更好,但是否有任何理由(再次出于好奇)为什么使用 OLE 如此糟糕?几年来我没有使用或开发过 Windows,所以我可能遗漏了一些明显的东西。
    • 你所说的 OLE 是 Excel 的自动化接口 - 它需要在服务器上安装 Excel 并实际为 每个 文件请求启动它。它很慢 - 每个调用都是对 Excel 的进程外调用。这也很危险,因为忘记关闭实例意味着 Excel 实例将保留在内存中。这会很快耗尽服务器资源。事实上,创建 XLSX 是为了让任何应用程序都可以创建有效的 Excel 文件,而无需服务器上的 Excel。开销很小 - 它只是 XML 处理
    • @PanagiotisKanavos:对。我忘记了 OLE 更像是 IPC,而不是库。谢谢!我会在答案中添加注释。
    【解决方案6】:

    在过去的几周里,我一直在大量使用电子表格和 ruby​​XL,我必须说它们都是很棒的工具。然而,两者都受到影响的一个领域是缺乏实际实施任何有用的例子。目前我正在构建一个爬虫并使用 ruby​​XL 解析 xlsx 文件和电子表格以获取任何 xls。我希望下面的代码可以作为一个有用的示例,并展示这些工具的有效性。

    require 'find'
    require 'rubyXL'
    
    count = 0
    
    Find.find('/Users/Anconia/crawler/') do |file|             # begin iteration of each file of a specified directory
      if file =~ /\b.xlsx$\b/                                  # check if file is xlsx format
        workbook = RubyXL::Parser.parse(file).worksheets       # creates an object containing all worksheets of an excel workbook
        workbook.each do |worksheet|                           # begin iteration over each worksheet
          data = worksheet.extract_data.to_s                   # extract data of a given worksheet - must be converted to a string in order to match a regex
          if data =~ /regex/
            puts file
            count += 1
          end      
        end
      end
    end
    
    puts "#{count} files were found"
    

    require 'find'
    require 'spreadsheet'
    Spreadsheet.client_encoding = 'UTF-8'
    
    count = 0
    
    Find.find('/Users/Anconia/crawler/') do |file|             # begin iteration of each file of a specified directory
      if file =~ /\b.xls$\b/                                   # check if a given file is xls format
        workbook =  Spreadsheet.open(file).worksheets          # creates an object containing all worksheets of an excel workbook
        workbook.each do |worksheet|                           # begin iteration over each worksheet
          worksheet.each do |row|                              # begin iteration over each row of a worksheet
            if row.to_s =~ /regex/                             # rows must be converted to strings in order to match the regex
              puts file
              count += 1
            end
          end
        end
      end
    end
    
    puts "#{count} files were found"
    

    【讨论】:

    • 如何遍历行?可能吗?
    【解决方案7】:

    rubyXL gem 可以很好地解析 XLSX 文件。

    【讨论】:

    • rubyXL(如上面的 roo)在您实际访问工作表中的数据时也会变得很奇怪。电子表格的数据模型是否有一些基本的东西不能简单地提供对行和列的迭代?
    • RubyXL 一团糟。我不推荐它。
    【解决方案8】:

    我找不到令人满意的 xlsx 解析器。 RubyXL 不做日期类型转换,Roo 尝试将数字类型转换为日期,两者在 api 和代码中都是一团糟。

    所以,我写了simple_xlsx_reader。但是,您必须为 xls 使用其他东西,所以这可能不是您要寻找的完整答案。

    【讨论】:

    • 期待尝试一下。希望以后能看到更多功能。伟大的开始!
    【解决方案9】:

    大多数在线示例(包括电子表格 gem 的作者网站)都演示了将 Excel 文件的全部内容读取到 RAM 中。如果您的电子表格很小,那很好。

    xls = Spreadsheet.open(file_path)
    

    对于处理非常大文件的任何人,更好的方法是流式读取文件的内容。电子表格 gem 支持这一点——尽管目前没有很好的记录(大约 3/2015)。

    Spreadsheet.open(file_path).worksheets.first.rows do |row|
      # do something with the array of CSV data
    end
    

    引用:https://github.com/zdavatz/spreadsheet

    【讨论】:

    • 如果你想让某事发生,你必须添加 .each
    【解决方案10】:

    RemoteTable library 在内部使用roo。它可以轻松阅读不同格式的电子表格(XLS、XLSX、CSV 等。可能是远程的,可能存储在 zip、gz 等中):

    require 'remote_table'
    r = RemoteTable.new 'http://www.fueleconomy.gov/FEG/epadata/02data.zip', :filename => 'guide_jan28.xls'
    r.each do |row|
      puts row.inspect
    end
    

    输出:

    {"Class"=>"TWO SEATERS", "Manufacturer"=>"ACURA", "carline name"=>"NSX", "displ"=>"3.0", "cyl"=>"6.0", "trans"=>"Auto(S4)", "drv"=>"R", "bidx"=>"60.0", "cty"=>"17.0", "hwy"=>"24.0", "cmb"=>"20.0", "ucty"=>"19.1342", "uhwy"=>"30.2", "ucmb"=>"22.9121", "fl"=>"P", "G"=>"", "T"=>"", "S"=>"", "2pv"=>"", "2lv"=>"", "4pv"=>"", "4lv"=>"", "hpv"=>"", "hlv"=>"", "fcost"=>"1238.0", "eng dscr"=>"DOHC-VTEC", "trans dscr"=>"2MODE", "vpc"=>"4.0", "cls"=>"1.0"}
    {"Class"=>"TWO SEATERS", "Manufacturer"=>"ACURA", "carline name"=>"NSX", "displ"=>"3.2", "cyl"=>"6.0", "trans"=>"Manual(M6)", "drv"=>"R", "bidx"=>"65.0", "cty"=>"17.0", "hwy"=>"24.0", "cmb"=>"19.0", "ucty"=>"18.7", "uhwy"=>"30.4", "ucmb"=>"22.6171", "fl"=>"P", "G"=>"", "T"=>"", "S"=>"", "2pv"=>"", "2lv"=>"", "4pv"=>"", "4lv"=>"", "hpv"=>"", "hlv"=>"", "fcost"=>"1302.0", "eng dscr"=>"DOHC-VTEC", "trans dscr"=>"", "vpc"=>"4.0", "cls"=>"1.0"}
    {"Class"=>"TWO SEATERS", "Manufacturer"=>"ASTON MARTIN", "carline name"=>"ASTON MARTIN VANQUISH", "displ"=>"5.9", "cyl"=>"12.0", "trans"=>"Auto(S6)", "drv"=>"R", "bidx"=>"1.0", "cty"=>"12.0", "hwy"=>"19.0", "cmb"=>"14.0", "ucty"=>"13.55", "uhwy"=>"24.7", "ucmb"=>"17.015", "fl"=>"P", "G"=>"G", "T"=>"", "S"=>"", "2pv"=>"", "2lv"=>"", "4pv"=>"", "4lv"=>"", "hpv"=>"", "hlv"=>"", "fcost"=>"1651.0", "eng dscr"=>"GUZZLER", "trans dscr"=>"CLKUP", "vpc"=>"4.0", "cls"=>"1.0"}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-16
      • 1970-01-01
      • 1970-01-01
      • 2018-05-29
      • 1970-01-01
      • 2012-01-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多