使用Ruby解析XLS和XLSX(MS Excel)文件?

Dan*_*iel 72 ruby excel

有没有能够解析XLS和XLSX文件的宝石?我找到了Spreadsheet和ParseExcel,但他们都不懂XLSX格式.

mat*_*ich 96

我最近需要用Ruby解析一些Excel文件.丰富的库和选项变得令人困惑,所以我写了一篇关于它的博客文章.

这是一个包含不同Ruby库的表以及它们支持的内容:

在此输入图像描述

如果您关心性能,那么xlsx库是如何比较的: 在此输入图像描述

我的示例代码读取与每个支持库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
Run Code Online (Sandbox Code Playgroud)

袋鼠

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

电子表格

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
Run Code Online (Sandbox Code Playgroud)

  • 我发现了这个问题.生成文件的源是将它们保存为.xls,但内容是HTML.谢谢你的意见. (3认同)
  • Thnks @ guero64 roo的xls功能实际上保留在另一个名为roo-xls https://github.com/roo-rb/roo-xls的项目中。你尝试过那个图书馆吗? (2认同)
  • 我也试过了roo-xls并得到了同样的错误 (2认同)

Chr*_*ton 55

刚刚找到roo,可能会完成这项工作 - 符合我的要求,阅读基本的电子表格.

  • roo当然有效,但令人沮丧的是不像Ruby那样(对我来说,无论如何)非常令人惊讶:无法使用每个行迭代行?无法迭代床单?"默认工作表"的概念,然后通过工作簿对象访问单元格? (12认同)
  • 我花了一段时间才找到,但是[这个现在正式的roo fork](https://github.com/Empact/roo),你必须明确指出,修复了我对roo的抱怨.它具有#each,#to_a,合理的工作表访问权限,并且不会通过要求ruby-spreadsheet来使用"Spreadsheet"污染全局命名空间. (7认同)

Bru*_*olo 43

袋鼠宝石的伟大工程为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
Run Code Online (Sandbox Code Playgroud)

  • 仔细考虑这个命名约定 - Spreadsheet是一个引用模块的现有常量:`Spreadsheet.class#=> Module`将类重命名为类似"Roobook"的东西解决了这个问题.不过,干得好! (2认同)
  • 最新的roo(在你指向的empact fork上)不会污染命名空间,并且带有#each等.最后!yay empact. (2认同)

the*_*ted 25

我正在使用使用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+上工作.每行的输出格式是一个数组:["a cell","another cell"] https://github.com/thirtyseven/dullard

提到的simple_xlsx_reader很棒,有点慢.在Macbook Air上的21x11250 xlsx表上使用了91秒.得到它在ruby 1.9.3+上工作.每行的输出格式是一个数组:["a cell","another cell"] https://github.com/woahdae/simple_xlsx_reader

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

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

删除dullard的建议,因为它已经过时,人们遇到错误/有问题.

  • 这篇文章应该是第一名 (2认同)
  • 谢谢你的分享.我发现使用Dullard gem可以快速,高效地从XLSX文件中流式传输100K +行. (2认同)

Lin*_*ios 7

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

在这一点上,看起来roo是一个很好的选择.它支持XLSX,只需使用times单元访问即可进行(某些)迭代.我承认,它并不漂亮.

此外,RubyXL现在可以使用他们的extract_data方法为您提供一种迭代,它为您提供了一个二维数据数组,可以轻松迭代.

或者,如果您尝试在Windows上使用XLSX文件,则可以使用Ruby的Win32OLE库,该库允许您与OLE对象(如Word和Excel提供的对象)进行交互.但是,正如@PanagiotisKanavos在评论中提到的,这有一些主要的缺点:

  • 必须安装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++.

  • 使用OLE for XLSX是一个糟糕的想法 - XLSX只是压缩的XML,具有众所周知的格式.它绝对不是**黑盒子 - Open XML格式定义很好,Open XML SDK提供所有手动创建XML所需的信息*和*有很多库可以大大简化使用XLSX的过程. (2认同)

Anc*_*nia 6

在过去的两周中,我一直在与Spreadsheet和rubyXL进行大量的工作,我必须说两者都是很好的工具。但是,两者都遭受的一个方面是缺少实际实施任何有用的示例。目前,我正在构建一个搜寻器,并使用rubyXL解析任何xls的xlsx文件和Spreadsheet。我希望下面的代码可以作为一个有用的示例,并说明这些工具的有效性。

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"
Run Code Online (Sandbox Code Playgroud)
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"
Run Code Online (Sandbox Code Playgroud)