2013-06-25 130 views
9

我正在寻找使用openpyxl将行插入电子表格的最佳方法。在Python中使用openpyxl将行插入Excel电子表格

实际上,我有一个电子表格(Excel 2007),它有一个标题行,后面是(最多)几千行数据。我正在寻找插入行作为第一行的实际数据,所以在标题后面。我的理解是,追加功能适用于向文件的末尾添加内容到

阅读openpyxl和xlrd(以及xlwt)的文档,除了手动循环内容并插入新工作表(插入所需的行之后)之外,我找不到任何明确的方法来完成此操作。由于我迄今为止的Python使用经验有限,我试图理解这是否是最好的选择(最pythonic!),如果有的话可以提供一个明确的例子。具体而言,我可以使用openpyxl读取和写入行,还是必须访问单元格?此外,我可以(通过)写入相同的文件(名称)?

+0

您是否可以选择导出为CSV?这是一次性过程吗?或者你需要重复这样做? –

+0

@Srini不,我们坚持这种格式,这是一个可重复的过程:该文件实际上FTP'd到另一台服务器,并由SSIS包处理。 – Nick

回答

8

用我现在用来实现所需结果的代码回答此问题。请注意,我手动插入位置1的行,但应该很容易调整以满足特定需求。你也可以很容易地调整这个插入多行,并从相关位置开始填充其余的数据。

此外,请注意,由于下游依赖关系,我们手动指定'Sheet1'中的数据,并且将数据复制到插入到工作簿开头的新工作表中,同时将原始工作表重命名为' Sheet1.5' 。

编辑:我还添加了(稍后)对format_code的更改,以解决缺省复制操作在此删除所有格式的问题:new_cell.style.number_format.format_code = 'mm/dd/yyyy'。我无法找到任何可以设置的文档,这更多的是反复试验的情况!

最后,不要忘记这个例子是保存在原来的。在适用的情况下,您可以更改保存路径以避免此情况。

import openpyxl 

    wb = openpyxl.load_workbook(file) 
    old_sheet = wb.get_sheet_by_name('Sheet1') 
    old_sheet.title = 'Sheet1.5' 
    max_row = old_sheet.get_highest_row() 
    max_col = old_sheet.get_highest_column() 
    wb.create_sheet(0, 'Sheet1') 

    new_sheet = wb.get_sheet_by_name('Sheet1') 

    # Do the header. 
    for col_num in range(0, max_col): 
     new_sheet.cell(row=0, column=col_num).value = old_sheet.cell(row=0, column=col_num).value 

    # The row to be inserted. We're manually populating each cell. 
    new_sheet.cell(row=1, column=0).value = 'DUMMY' 
    new_sheet.cell(row=1, column=1).value = 'DUMMY' 

    # Now do the rest of it. Note the row offset. 
    for row_num in range(1, max_row): 
     for col_num in range (0, max_col): 
      new_sheet.cell(row = (row_num + 1), column = col_num).value = old_sheet.cell(row = row_num, column = col_num).value 

    wb.save(file) 
+0

虽然我觉得我应该补充一点,我们现在决定实现一个CSV导出,但它会按预期操作文件。这是因为openpyxl库的编码问题与这个问题无关,但我觉得我应该提及它! – Nick

0

不幸的是,在读取文件时没有更好的方法来做,并且使用像xlwt这样的库来写出一个新的excel文件(将新行插入顶部)。 Excel不能像您可以读取和追加的数据库一样工作。不幸的是,你必须阅读信息并在内存中操作,并写出本质上是新文件的内容。

5

当进行行或列级操作时,Openpyxl Worksheets的功能有限。 Worksheet具有的与行/列相关的唯一属性是属性row_dimensionscolumn_dimensions,它们分别为每个行和列存储“RowDimensions”和“ColumnDimensions”对象。这些字典也用于功能,如get_highest_row()get_highest_column()

其他所有操作都在单元级别上进行,单元对象在字典中被跟踪,_cells(以及它们在字典_styles中跟踪的样式)。大多数看起来像是在行或列级别上执行任何操作的函数实际上都在一系列单元格上运行(例如上述的append())。

要做的最简单的事情就是建议:创建一个新工作表,追加标题行,附加新数据行,附加旧数据行,删除旧工作表,然后将新工作表重新命名为旧一。这种方法可能出现的问题是行/列维度属性和单元格样式的丢失,除非您专门复制它们。

或者,您可以创建自己的插入行或列的函数。

我有很多我需要删除列的简单工作表。既然你问了明确的例子,我会提供我赶紧扔在一起,要做到这一点的功能:

from openpyxl.cell import get_column_letter 

def ws_delete_column(sheet, del_column): 

    for row_num in range(1, sheet.get_highest_row()+1): 
     for col_num in range(del_column, sheet.get_highest_column()+1): 

      coordinate = '%s%s' % (get_column_letter(col_num), 
            row_num) 
      adj_coordinate = '%s%s' % (get_column_letter(col_num + 1), 
             row_num) 

      # Handle Styles. 
      # This is important to do if you have any differing 
      # 'types' of data being stored, as you may otherwise get 
      # an output Worksheet that's got improperly formatted cells. 
      # Or worse, an error gets thrown because you tried to copy 
      # a string value into a cell that's styled as a date. 

      if adj_coordinate in sheet._styles: 
       sheet._styles[coordinate] = sheet._styles[adj_coordinate] 
       sheet._styles.pop(adj_coordinate, None) 
      else: 
       sheet._styles.pop(coordinate, None) 

      if adj_coordinate in sheet._cells: 
       sheet._cells[coordinate] = sheet._cells[adj_coordinate] 
       sheet._cells[coordinate].column = get_column_letter(col_num) 
       sheet._cells[coordinate].row = row_num 
       sheet._cells[coordinate].coordinate = coordinate 

       sheet._cells.pop(adj_coordinate, None) 
      else: 
       sheet._cells.pop(coordinate, None) 

     # sheet.garbage_collect() 

我将它传递的是我的工作,工作表,列号我想删除,并远离它走了。我知道这不是你想要的,但我希望这些信息能够帮助你!

编辑:注意到有人给了这个另一票,并认为我应该更新它。 Openpyxl的坐标系统在过去几年中经历了一些变化,为_cell中的项目引入了coordinate属性。这也需要进行编辑,或者将行保留为空白(而不是删除),Excel将抛出关于该文件问题的错误。这适用于Openpyxl 2.2.3(未经测试与更高版本)

+1

+1,用于突出显示使用get_highest函数以用于循环以及提供示例。 – Nick

15

==更新到全功能版本,基于此反馈:groups.google.com/forum/#!topic/openpyxl-users/wHGecdQg3Iw。 ==

正如其他人已经指出的,openpyxl不提供此功能,但我已经扩展了Worksheet类以实现插入行。希望这证明对他人有用。

def insert_rows(self, row_idx, cnt, above=False, copy_style=True, fill_formulae=True): 
    """Inserts new (empty) rows into worksheet at specified row index. 

    :param row_idx: Row index specifying where to insert new rows. 
    :param cnt: Number of rows to insert. 
    :param above: Set True to insert rows above specified row index. 
    :param copy_style: Set True if new rows should copy style of immediately above row. 
    :param fill_formulae: Set True if new rows should take on formula from immediately above row, filled with references new to rows. 

    Usage: 

    * insert_rows(2, 10, above=True, copy_style=False) 

    """ 
    CELL_RE = re.compile("(?P<col>\$?[A-Z]+)(?P<row>\$?\d+)") 

    row_idx = row_idx - 1 if above else row_idx 

    def replace(m): 
     row = m.group('row') 
     prefix = "$" if row.find("$") != -1 else "" 
     row = int(row.replace("$","")) 
     row += cnt if row > row_idx else 0 
     return m.group('col') + prefix + str(row) 

    # First, we shift all cells down cnt rows... 
    old_cells = set() 
    old_fas = set() 
    new_cells = dict() 
    new_fas = dict() 
    for c in self._cells.values(): 

     old_coor = c.coordinate 

     # Shift all references to anything below row_idx 
     if c.data_type == Cell.TYPE_FORMULA: 
      c.value = CELL_RE.sub(
       replace, 
       c.value 
      ) 
      # Here, we need to properly update the formula references to reflect new row indices 
      if old_coor in self.formula_attributes and 'ref' in self.formula_attributes[old_coor]: 
       self.formula_attributes[old_coor]['ref'] = CELL_RE.sub(
        replace, 
        self.formula_attributes[old_coor]['ref'] 
       ) 

     # Do the magic to set up our actual shift  
     if c.row > row_idx: 
      old_coor = c.coordinate 
      old_cells.add((c.row,c.col_idx)) 
      c.row += cnt 
      new_cells[(c.row,c.col_idx)] = c 
      if old_coor in self.formula_attributes: 
       old_fas.add(old_coor) 
       fa = self.formula_attributes[old_coor].copy() 
       new_fas[c.coordinate] = fa 

    for coor in old_cells: 
     del self._cells[coor] 
    self._cells.update(new_cells) 

    for fa in old_fas: 
     del self.formula_attributes[fa] 
    self.formula_attributes.update(new_fas) 

    # Next, we need to shift all the Row Dimensions below our new rows down by cnt... 
    for row in range(len(self.row_dimensions)-1+cnt,row_idx+cnt,-1): 
     new_rd = copy.copy(self.row_dimensions[row-cnt]) 
     new_rd.index = row 
     self.row_dimensions[row] = new_rd 
     del self.row_dimensions[row-cnt] 

    # Now, create our new rows, with all the pretty cells 
    row_idx += 1 
    for row in range(row_idx,row_idx+cnt): 
     # Create a Row Dimension for our new row 
     new_rd = copy.copy(self.row_dimensions[row-1]) 
     new_rd.index = row 
     self.row_dimensions[row] = new_rd 
     for col in range(1,self.max_column): 
      col = get_column_letter(col) 
      cell = self.cell('%s%d'%(col,row)) 
      cell.value = None 
      source = self.cell('%s%d'%(col,row-1)) 
      if copy_style: 
       cell.number_format = source.number_format 
       cell.font  = source.font.copy() 
       cell.alignment = source.alignment.copy() 
       cell.border = source.border.copy() 
       cell.fill  = source.fill.copy() 
      if fill_formulae and source.data_type == Cell.TYPE_FORMULA: 
       s_coor = source.coordinate 
       if s_coor in self.formula_attributes and 'ref' not in self.formula_attributes[s_coor]: 
        fa = self.formula_attributes[s_coor].copy() 
        self.formula_attributes[cell.coordinate] = fa 
       # print("Copying formula from cell %s%d to %s%d"%(col,row-1,col,row)) 
       cell.value = re.sub(
        "(\$?[A-Z]{1,3}\$?)%d"%(row - 1), 
        lambda m: m.group(1) + str(row), 
        source.value 
       ) 
       cell.data_type = Cell.TYPE_FORMULA 

    # Check for Merged Cell Ranges that need to be expanded to contain new cells 
    for cr_idx, cr in enumerate(self.merged_cell_ranges): 
     self.merged_cell_ranges[cr_idx] = CELL_RE.sub(
      replace, 
      cr 
     ) 

Worksheet.insert_rows = insert_rows 
+0

复制声音就像矫枉过正。您可能想尝试一些类似于我在邮件列表中为插入列提供的建议https://groups.google.com/forum/#!topic/openpyxl-users/wHGecdQg3Iw它未经测试,仅供2.2参考,因为它依赖于内部,但它提供了总体推力。 –

+0

我同意修改坐标数据是理想的,但是这是在没有查看内部的情况下完成的。不知道为什么团队不会仅仅添加这个功能,因为这是Excel中经常使用的功能。 – Dallas

+0

没有人提交相关的代码。 openpyxl是开源的,没有团队。 –

1

我把达拉斯水溶液,并添加了合并单元格的支持:

def insert_rows(self, row_idx, cnt, above=False, copy_style=True, fill_formulae=True): 
     skip_list = [] 
     try: 
      idx = row_idx - 1 if above else row_idx 
      for (new, old) in zip(range(self.max_row+cnt,idx+cnt,-1),range(self.max_row,idx,-1)): 
       for c_idx in range(1,self.max_column): 
        col = self.cell(row=1, column=c_idx).column #get_column_letter(c_idx) 
        print("Copying %s%d to %s%d."%(col,old,col,new)) 
        source = self["%s%d"%(col,old)] 
        target = self["%s%d"%(col,new)] 
        if source.coordinate in skip_list: 
         continue 

        if source.coordinate in self.merged_cells: 
         # This is a merged cell 
         for _range in self.merged_cell_ranges: 
          merged_cells_list = [x for x in cells_from_range(_range)][0] 
          if source.coordinate in merged_cells_list: 
           skip_list = merged_cells_list 
           self.unmerge_cells(_range) 
           new_range = re.sub(str(old),str(new),_range) 
           self.merge_cells(new_range) 
           break 

        if source.data_type == Cell.TYPE_FORMULA: 
        target.value = re.sub(
         "(\$?[A-Z]{1,3})%d"%(old), 
         lambda m: m.group(1) + str(new), 
         source.value 
        ) 
        else: 
        target.value = source.value 
        target.number_format = source.number_format 
        target.font = source.font.copy() 
        target.alignment = source.alignment.copy() 
        target.border = source.border.copy() 
        target.fill = source.fill.copy() 
      idx = idx + 1 
      for row in range(idx,idx+cnt): 
       for c_idx in range(1,self.max_column): 
        col = self.cell(row=1, column=c_idx).column #get_column_letter(c_idx) 
        #print("Clearing value in cell %s%d"%(col,row)) 
        cell = self["%s%d"%(col,row)] 
        cell.value = None 
        source = self["%s%d"%(col,row-1)] 
        if copy_style: 
        cell.number_format = source.number_format 
        cell.font  = source.font.copy() 
        cell.alignment = source.alignment.copy() 
        cell.border = source.border.copy() 
        cell.fill  = source.fill.copy() 
        if fill_formulae and source.data_type == Cell.TYPE_FORMULA: 
        #print("Copying formula from cell %s%d to %s%d"%(col,row-1,col,row)) 
        cell.value = re.sub(
         "(\$?[A-Z]{1,3})%d"%(row - 1), 
         lambda m: m.group(1) + str(row), 
         source.value 
        ) 
+0

请参阅以下代码片段获得更全面的功能:https://bitbucket.org/snippets/openpyxl/qyzKn – Dallas

0

编辑尼克的解决方案,这个版本需要一个起始行,行插入的数目,和一个文件名,并插入必要数量的空白行。

#! python 3 

import openpyxl, sys 

my_start = int(sys.argv[1]) 
my_rows = int(sys.argv[2]) 
str_wb = str(sys.argv[3]) 

wb = openpyxl.load_workbook(str_wb) 
old_sheet = wb.get_sheet_by_name('Sheet') 
mcol = old_sheet.max_column 
mrow = old_sheet.max_row 
old_sheet.title = 'Sheet1.5' 
wb.create_sheet(index=0, title='Sheet') 

new_sheet = wb.get_sheet_by_name('Sheet') 

for row_num in range(1, my_start): 
    for col_num in range(1, mcol + 1): 
     new_sheet.cell(row = row_num, column = col_num).value = old_sheet.cell(row = row_num, column = col_num).value 

for row_num in range(my_start + my_rows, mrow + my_rows): 
    for col_num in range(1, mcol + 1): 
     new_sheet.cell(row = (row_num + my_rows), column = col_num).value = old_sheet.cell(row = row_num, column = col_num).value 

wb.save(str_wb) 
相关问题