直接上代码:(代码有注释,代码下方有解释)
1.可读取excel中的xlsx格式文件
2.可读取excel中的xls格式文件
3.可直接读取网页上的excel的超链接
4.解决有列合并的情况以及行合并的问题,由合并的情况,分别对应的填充
5.返回数据为 {"Sheet1":[{},{},{}....], "Sheet2":[{},{},{}....]} 注意,有的工作表中的名字是自己工作表的名字,这里只举例
import os from datetime import datetime from pprint import pprint import requests import win32com.client as win32 import xlrd from collections import Counter from fake_useragent import UserAgent from xlrd import xldate_as_tuple class ReadExcel: """ 1.可读取excel中的xlsx格式文件 2.可读取excel中的xls格式文件 3.可直接读取网页上的excel的超链接 读出的格式为[{},{},{}]。。。 每一个字典为,一行对应列名的一个字典 """ def __init__(self, file_path=None, index=None, header=None, auto_header=False, url=None, headers=None): """ 传入的参数的解释 :param file_path: (str)如果要读取的是文件,传入路径参数,绝对路径和相对路径都可以,兼容性强 :param index: (int)选择第几行为列名,索引从0开始 :param header: (list)传入列名,要与数据列对应,不建议用,除非表中没有头 :param auto_header: (bool)自动识别列名 :param url: (str)如果要直接读取url, 直接传入网页中excel中的超链接 :param headers: (dict)如果读取的超链接,有的网站的文件有反爬,所以为了防止 需要注意的是,file_path和url只能二选一,意为读取文件或者超链接 index、header、auto_header 只需要三选一即可 headers 可传可不传,不传时,请求头只有User-Agent,一般的文件都可以 如果你的文件也有反爬的话,建议传入headers """ self.file_path = file_path self.url = url self.headers = headers self.index = index self.header = header self.auto_header = auto_header # 判断文件类型 if file_path is not None: # 传入的是文件 if 'xlsx' in file_path: # 如果传入的文件是 xlsx 文件类型,将xlsx文件类型转化为xlsx # 将原来的文件名,替换成同名不同类型的文件,即xls结尾的文件(注意这块直接得到的是文件名,虽然传入的完整路径) fileName = os.path.basename(file_path).replace("xlsx", 'xls') # 文件导出的目录路径和文件名称,文件名称如上 export_name = os.path.join(os.path.dirname(os.path.abspath(file_path)), fileName) self.xlsx_to_xls(file_path, export_name) # 开始进行转换 self.file_path = export_name # 设置文件路径为转换后的文件路径 else: # 如果是xls直接将目录进行拼接,绝对路径,并且设置文件路径 self.file_path = os.path.join(os.path.dirname(os.path.abspath(file_path)), os.path.basename(file_path)) else: # 超链接 if self.headers is None: # 如果没有传入请求头,默认的只有随机的User-Agent self.headers = { "User-Agent": UserAgent().random } else: # 如果传入了请求头,就用该请求头,用随机的User-Agent,有就更新,无则加入进去 self.headers.update({ "User-Agent": UserAgent().random }) # 设置文件路径为读取excel超链接的文件之后的文件路径,当前路径,读取后会删除文件 self.file_path = self.read_url_excel() # formatting_info=True, 防止merged_cells获取合并的行或者列时为空列表 self.workbook = xlrd.open_workbook(self.file_path, formatting_info=True) # 读取文件 self.sheet_names = self.workbook.sheet_names() # 获取该文件所有的工作表名 # 如果index和header都没有传入,则设置index为0,即就是从第一行开始读取 if self.index is None or self.header is not None: self.index = 0 def merge_cell(self, sheet_info): """ :param sheet_info: 传入表格的对象 :return:返回,有合并的行和列的键值对 """ merge = {} merge_cells = sheet_info.merged_cells for (rlow, rhigh, clow, chigh) in merge_cells: value_mg_cell = sheet_info.cell_value(rlow, clow) if sheet_info.cell(rlow, clow).ctype == 3: # 对读出来的时间格式进行转换 date = datetime(*xldate_as_tuple(value_mg_cell, 0)) value_mg_cell = date.strftime('%Y-%m-%d') if rhigh - rlow == 1: # Merge transverse cells for n in range(chigh - clow - 1): merge[(rlow, clow + n + 1)] = value_mg_cell elif chigh - clow == 1: for n in range(rhigh - rlow - 1): merge[(rlow + n + 1, clow)] = value_mg_cell return merge def xlsx_to_xls(self, fname, export_name, delete_flag=False): """ 将xlsx文件转化为xls文件 :param fname: 传入待转换的文件路径(可传绝对路径,也可传入相对路径,都可以) :param export_name: 传入转换后到哪个目录下的路径(可传绝对路径,也可传入相对路径,都可以) :param delete_flag: 转换成功后,是否删除原来的xlsx的文件,默认删除 布尔类型 :return: 无返回值 """ excel = win32.gencache.EnsureDispatch('Excel.Application') excel.Visible = False excel.DisplayAlerts = False absolute_path = os.path.join(os.path.dirname(os.path.abspath(fname)), os.path.basename(fname)) save_path = os.path.join(os.path.dirname(os.path.abspath(fname)), os.path.basename(export_name)) wb = excel.Workbooks.Open(absolute_path) wb.SaveAs(save_path, FileFormat=56) # FileFormat = 51 is for .xlsx extension wb.Close() # FileFormat = 56 is for .xls extension excel.Application.Quit() # 是否删除原来的数据文件 if delete_flag: os.remove(absolute_path) def list_dic(self, key_list, value_list): """ 两个列表组成字典 :param key_list: 键名列表 :param value_list: 值列表 :return: 返回组成好的字典 """ dic = dict(map(lambda x, y: [x, y], key_list, value_list)) return dic def read_url_excel(self): # 请求 r = requests.get(url=self.url, headers=self.headers) file_b = r.content # 判断文件类型, 确定文件类型和名称 if 'xlsx' in self.url: name = 'temp.xlsx' else: name = 'temp.xls' # 保存文件 with open(name, 'wb') as f: f.write(file_b) # 判断是否需要转换,并且确定文件路径 if name == 'temp.xlsx': export_name = 'temp.xls' self.xlsx_to_xls(name, export_name, delete_flag=True) else: export_name = name return export_name def read_excel(self): # 取出所有的工作表的名称 sheet_name_list = self.sheet_names dic_data = {} # 所有的工作表字典,对应该工作表中的数据,键值对形式 for sheet_name in sheet_name_list: sheet_simple = [] # 每张工作表 # 某个工作表的名称 sheet = self.workbook.sheet_by_name(sheet_name) if sheet.nrows != 0: auto_index = 0 # 自动识别头在第几行 # 根据这些数据确定,行索引, 可以自己去设置,更改,按自己的需求 flag_list = ['项目名称', '工程名称', '许可证号', '证号', '许可证编号', '建设规模', '施工单位', '勘察单位', '投资额', '勘察单位', '设计单位', '工程地址', '工程地址', '层数'] # 不要包含这些数据的行, 可以自己去设置,更改,按自己的需求 del_list = ['表', '明细', '合计', '总计'] if self.auto_header: # 自动识别头 for row_i in range(0, sheet.nrows): # 循环每一行 # 每行所有数据连起来的字符串 row_data = ''.join([str(r_v) for r_v in sheet.row_values(row_i)]).strip() for del_str in del_list: # 跳过表名 if del_str not in row_data: for flag in flag_list: if flag in row_data: # 判断表头 auto_index = row_i header = [str(row_value).replace("\n", "").replace(" ", "") for row_value in sheet.row_values(auto_index) if row_value != ''] row_index = auto_index + 1 else: # 手动头或者自定义头 if self.header is None: # 如果没有传入表头,则用index取表头 header = [row_value.replace("\n", "").replace(" ", "") for row_value in sheet.row_values(self.index) if row_value != ''] row_index = self.index else: # 传入了头,就用 header = self.header row_index = 0 values_merge_cell = self.merge_cell(sheet) # 这里是调用处理合并单元格的函数 # row_index 从第几行开始读取 for i in range(row_index, sheet.nrows): # 开始为组成字典准备数据 other_line = [] # 处理类型 # 可参考 https://www.cnblogs.com/xxiong1031/p/7069006.html for j in range(sheet.ncols): ctype = sheet.cell(i, j).ctype # 表格的数据类型 cell = sheet.cell_value(i, j) if ctype == 3: # 转成datetime对象 date = datetime(*xldate_as_tuple(cell, 0)) cell = date.strftime('%Y-%m-%d') other_line.append(cell) # other_line = sheet.row_values(i) # print(other_line) # print(other_line) for key in values_merge_cell.keys(): if key[0] == i: if key[1] != '': other_line[key[1]] = str(values_merge_cell[key]).replace("\xa0", "") dic = self.list_dic(header, other_line) # 调用组合字典的函数,传入key和value,字典生成 # print(self.url) # # pprint(dic) # print(other_line) value_list = [str(v).strip() for v in dic.values()] value_str = ''.join(value_list).strip() pass_list = del_list + flag_list pass_flag = True for pass_str in pass_list: if pass_str in value_str: pass_flag = False break if pass_flag and len(Counter(value_list)) > 1 and value_str != '': try: del dic['序号'] except: pass # pprint(dic) sheet_simple.append(dic) dic_data[str(sheet_name)] = sheet_simple os.remove(self.file_path) return dic_data if __name__ == '__main__': # 读取文件,自动识别头 readexcel = ReadExcel(file_path='./abcd/W020200220406735094729 (1).xls', auto_header=True) pprint(readexcel.read_excel()) # 读取超链接,自动识别头,里面有表格合并 readexcel_1 = ReadExcel(url='http://zjj.laibin.gov.cn/xxcx/bjgb/P020211122537271486773.xls', auto_header=True) pprint(readexcel_1.read_excel())
注意的几点问题:
1. 创建类,传入参数是,如果是文件格式,就用file_path=‘文件路径’ 的方式,如果是超链接的方式 就用 url='超链接'
2. 里面的flag_list 和del_list 根据需要可以去修改,如果没有该需求,可以把这个两个列表设置我空列表
3.如果你读出来的excel数据有问题,可以随时联系我,私聊或者评论,我会第一时间进行修改,进行更正
谢谢大家