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


76

有没有可以解析XLS和XLSX文件的gem?我已经找到Spreadsheet和ParseExcel,但它们都不了解XLSX格式。

Answers:


55

刚发现roo可能会胜任工作-可以满足我的要求,阅读基本的电子表格。


12
roo确实可以工作,但是它令人沮丧地不像Ruby,并且(无论如何对我而言)非常令人惊讶:无法使用每个对象对行进行迭代?无法迭代工作表?一个“默认工作表”的概念,然后通过工作簿对象访问单元格?
M.安东尼·艾洛

7
我花了一段时间才找到,但是这个必须正式固定的现在正式的roo前叉,解决了我对roo的抱怨。它具有#each,#to_a,合理的工作表访问权限,并且不会Spreadsheet通过要求ruby-spreadsheet污染全局名称空间。
Woahdae

@woahdae太棒了!很高兴看到具有这些新功能的示例。有没有可用的文档?我对能够迭代工作簿的每个工作表的每一行特别感兴趣。
Anconia 2013年

该fork的自述文件有一个额外的部分,介绍了fork中的新增功能。但是,在实现要求良好的类型转换的xlsx上传之后,我发现roo类型转换有很多需要。尝试将“ 2”(格式为数字)解析为日期时,它感到窒息。我写了我自己喜欢的解析器,今晚我会把它上传到github,然后再回头给你。
Woahdae

@woahdae好东西。期待看到您的工作。请尽可能发送链接。
Anconia 2013年

116

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

这是不同的Ruby库及其支持的表:

在此处输入图片说明

如果您关心性能,请按以下方法xlsx比较库: 在此处输入图片说明

我的示例代码读取XLSX与每个支持库文件在这里

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

红宝石XL

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

o

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

这是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

1
这是一篇很棒的文章,我对此表示赞同,但不幸的是,我发现roo和电子表格都无法处理我的.xls数据。
guero64 '17

2
Thnks @ guero64 roo的xls功能实际上保留在另一个名为roo-xls的项目github.com/roo-rb/roo-xls中。您尝试过那个图书馆吗?
mattnedrich'7

4
我发现了问题。生成文件的源将其另存为.xls,但内容为HTML。不过,感谢您的输入。
guero64 '17

您是否找到任何将名称定义为范围的合理工作?例如用openpyxl:gist.github.com/empiricalthought/...
史蒂芬Huwig

比较工作很棒!我正在尝试使用roo,如果您设法从单元格中提取注释,我会在徘徊,如何使用提供的功能实现此目标?
user1185081

43

袋鼠宝石的伟大工程为Excel(.xls和.xlsx)格式和它正在积极地开发。

我同意语法不太好,也不像红宝石一样。但这可以通过以下方式轻松实现:

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

2
小心使用此命名约定Spreadsheet.class # => Module-Spreadsheet是一个引用模块的现有常量:将类重命名为“ Roobook”之类即可解决此问题。但是,太好了!
Anconia

2
最新的Roo(在您指向的empact分支上)不会污染名称空间,并带有#each等。最后!是的。
Woahdae

1
Roo gem大文件可怕。打开5MB XLSx文件可能需要30-60秒,但这根本没有任何意义。
Yura Omelchuk 2014年

1
Roo需要很长时间,因为它必须将所有内容加载到内存中。它还似乎将电子表格解析为可用的数据结构,这可能会很慢。
Carlosfocker 2014年

请在Rails项目的何处可以将此类文件保存在@Bruno Buccolo中。
wokoro douye samuel '18

25

我正在使用使用nokogiri的小河。很快 在Macbook Air的21x11250 xlsx桌上使用了8.3秒。可以在Ruby 1.9.3+上运行。每行的输出格式是相对于单元格内容的行和列名称的哈希:{“ A1” =>“ a cell”,“ B1” =>“另一个单元格”}哈希不能保证键将位于原始列顺序。 https://github.com/pythonicrubyist/creek

呆呆的家伙是另一个使用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,因为它具有类似的性能。

根据建议,删除了过时的功能,因为它已经过时,并且人们遇到了错误/问题。


2
这篇文章应该是第一名
Carlosfocker 2014年

2
感谢你的分享。我发现使用Dullard gem可以快速,高效地从XLSX文件流式传输10万多个行。
scarver2'3

1
dullard对我来说充满了错误(使用非拉丁数据)。creek给出了我所需要的
okliv 2015年

okliv,如果您可以在此处指定哪些类型集不适用于dillard,那就太好了。另外,在github上的愚蠢问题跟踪器上发布帖子!:)
the_minted 2015年

1
这个答案只提到读取“ xlsx”文件,“ xls”文件呢?
anshul410

7

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

在这一点上,roo似乎又是一个不错的选择。它支持XLSX,仅通过times单元访问即可允许(某些)迭代。我承认,虽然不是很漂亮。

而且,RubyXL现在可以使用其extract_data方法为您提供某种迭代,从而为您提供2d数据数组,可以轻松地对其进行迭代。

另外,如果要在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文档是封闭源代码,而LibreOffice是数千行Carry C ++。


非常有用的信息!我目前正在构建一个excel搜寻器,并且已经适合它(stackoverflow.com/questions/14044357/…)。我已经放弃了roo,因为迭代非常痛苦。但是,我急于尝试extract_data使用RubyXL。
Anconia

2
对XLSX使用OLE是一个主意-XLSX只是具有众所周知格式的压缩XML。它绝对不是黑匣子-Open XML格式定义非常明确,Open XML SDK提供了创建XML所需的所有信息。手工编写XML,并且有许多库极大地简化了XLSX的使用。
Panagiotis Kanavos '16

@PanagiotisKanavos:有趣的一点。我当然知道为什么这样做会更好,但是是否出于任何原因(再次出于好奇)使用OLE是如此糟糕?几年来我一直没有为Windows使用或开发Windows,所以我可能会遗漏一些明显的东西。
Linuxios 2016年

您所谓的OLE是Excel的自动化接口-它要求将Excel安装在服务器上,并针对每个文件请求启动它。它很慢-每个调用都是对Excel的进程外调用。这也很危险,因为忘记关闭实例意味着Excel实例将保留在内存中。这样会很快耗尽服务器资源。实际上,创建XLSX的目的是使任何应用程序都可以创建有效的Excel文件,而无需在服务器上使用Excel。开销是最小的-仅仅是XML处理
Panagiotis Kanavos 2016年

@PanagiotisKanavos:对。我忘记了OLE比IPC更像是一个IPC。谢谢!我会在答案中添加注释。
Linuxios

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"

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"

如何遍历行?可能?
some_other_guy 2014年


3

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

所以,我写了simple_xlsx_reader。但是,您必须为xls使用其他功能,所以也许这不是您要查找的完整答案。


期待尝试一下。希望将来能看到更多功能。伟大的开始!
Anconia

3

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

xls = Spreadsheet.open(file_path)

对于使用超大文件的任何人,更好的方法是流式读取文件的内容。Spreadsheet gem支持此功能-尽管目前尚无充分记录(大约2015年3月3日)。

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

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


如果您希望发生某些事情,则必须添加.each-
彼得,

2

RemoteTable库用途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"}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.