当前位置:   article > 正文

【Python实现调查问卷数据统计】_python程序员问卷调查

python程序员问卷调查

一、背景

系统发出调查问卷,问卷的填写记录入了数据库,现在需要对用户的调查问卷填写情况进行统计。

这里使用Python对数据进行简单的处理,并将统计结果保存为excel文件。

二、连接MySQL查询数据

因为数据存储在MySQL,我使用pymysql库连接数据库。

这里编写了三条查询SQL语句,将数据分为三部分:调查问卷基本信息、调查问卷填写详情、调查问卷问题和选项。

将这三部分数据保存在一个列表中,方便后续处理。

  1. import traceback
  2. from pprint import pprint
  3. import pymysql
  4. import yaml
  5. def data_detail(dbinfo, survey_name):
  6. """
  7. 连接数据库查询详细数据,保存为excel
  8. :param dbinfo: 数据库连接信息
  9. :param survey_name: 调查名称
  10. :return: 数据库查询结果
  11. """
  12. # SQL列表示例
  13. sql_list = [f'调查问卷基本信息查询SQL:select * from table1 where name={survey_name}', '调查问卷填写详情查询SQL', '调查问卷问题和选项查询SQL']
  14. data = [execute_sql(dbinfo, sql) for sql in sql_list]
  15. return data
  16. def execute_sql(dbinfo, sql):
  17. """
  18. 连接数据库,执行sql查询语句,返回查询结果
  19. :param dbinfo: 数据库配置信息
  20. :param sql: 数据库查询语句
  21. :return:
  22. """
  23. try:
  24. conn = pymysql.connect(**dbinfo) # 连接数据库
  25. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 数据库游标
  26. cursor.execute(sql) # 执行sql语句
  27. results = cursor.fetchall() # 查询结果
  28. conn.close() # 关闭数据库连接
  29. return results
  30. except Exception as err:
  31. info = f"出了点小问题!\n{repr(err)}\n{traceback.format_exc()}"
  32. print(info)
  33. def yaml_read(filename):
  34. """
  35. 读取yaml文件
  36. """
  37. with open(file=filename, mode="r", encoding="utf-8") as f:
  38. data = yaml.safe_load(f.read())
  39. return data
  40. if __name__ == "__main__":
  41. db_info = yaml_read('cfg_mysql_test.yaml')
  42. datas = data_detail(db_info, '调查问卷数据统计测试')
  43. pprint(datas)

运行结果: 

三、查询结果保存为excel

将查询的数据结果保存到同一个excel表格中,用于后续分析统计。

先看一下查询的数据结果示例:[[{k:v,k:v},{k:v,k:v}],[{k:v,k:v},{k:v,k:v}],[{k:v,k:v},{k:v,k:v}]]

从内到外:字典-列表-字典

这里我做了如下处理:

        1.使用xlwt模块写入数据

        2.查询结果写入到同一个表格的多个工作表

        3.xlwt.xfstyle()设置表格的样式:表头、表格、时间数据

  1. import datetime
  2. import os
  3. import time
  4. import traceback
  5. import xlwt
  6. def to_excel(data, sheet_name=None, file_name=None):
  7. """
  8. 数据写入excel
  9. :param data: 数据查询结果
  10. :param sheet_name: 工作表名称
  11. :param file_name: excel文件名称
  12. :return:
  13. """
  14. try:
  15. book = xlwt.Workbook(encoding='utf-8') # 新建工作簿
  16. new_datas = data_pre_handle(data) # 预处理数据
  17. if sheet_name is None:
  18. sheet_name = ["new_sheet" + str(i + 1) for i in range(len(new_datas))]
  19. write_data(book, new_datas, sheet_name) # 数据写入工作表
  20. save_excel(book, file_name) # 保存工作簿
  21. except Exception as err:
  22. info = f"出了点小问题!\n{repr(err)}\n{traceback.format_exc()}"
  23. print(info)
  24. def save_excel(workbook, file_name=None):
  25. """
  26. 保存工作簿
  27. :param workbook: 工作簿
  28. :param file_name: excel文件名称
  29. :return:
  30. """
  31. if file_name is None:
  32. sj = datetime.datetime.now().strftime("%Y-%m-%d %H%M%S")
  33. name = sj + ".xlsx"
  34. else:
  35. name = file_name + ".xlsx"
  36. workbook.save(name)
  37. cur_file = os.path.dirname(os.path.abspath(__file__)) + os.sep + name # 当前excel的保存路径
  38. print(f"excel保存成功!【{cur_file}】")
  39. def data_pre_handle(data):
  40. """
  41. 预处理数据库查询到的数据
  42. :param data: 数据查询结果
  43. :return:
  44. """
  45. return [[list(v[0].keys())] + [list(val.values()) for index, val in enumerate(v)] for i, v in enumerate(data)]
  46. def write_data(workbook, data, sheet_name):
  47. """
  48. 数据写入sheet
  49. :param workbook: 工作簿
  50. :param data: 要写入的数据
  51. :param sheet_name: 工作表名称
  52. :return:
  53. """
  54. try:
  55. for index, value in enumerate(data):
  56. sheet = workbook.add_sheet(sheet_name[index], cell_overwrite_ok=True) # 新建sheet
  57. size = [12 for i in value[0]]
  58. head_style = style_head(sheet, size, wrap=1, is_bg=1) # 定义表头样式
  59. time_style = style_table_time() # 时间内容样式
  60. normal_style = style_table_normal({"horz": "CENTER", "vert": "CENTER"}) # 普通内容样式
  61. for col, val in enumerate(value[0]):
  62. sheet.write(0, col, val, head_style)
  63. print(f"写入表头成功!{value[0]}")
  64. for i, v in enumerate(value[1:]):
  65. for co, va in enumerate(v):
  66. val = str(va)
  67. if is_date(val):
  68. if len(val) > 19:
  69. new_val = datetime.datetime.strptime(val, "%Y-%m-%d %H:%M:%S.%f")
  70. elif 10 < len(val) <= 19:
  71. new_val = datetime.datetime.strptime(val, "%Y-%m-%d %H:%M:%S")
  72. else:
  73. new_val = datetime.datetime.strptime(val, '%Y-%m-%d')
  74. sheet.write(i + 1, co, new_val, time_style)
  75. elif is_number(val) == 1 and len(val) < 11:
  76. sheet.write(i + 1, co, int(val), normal_style)
  77. else:
  78. sheet.write(i + 1, co, val, normal_style)
  79. print(f"写入表格内容成功!共{len(value) - 1}行")
  80. except Exception as err:
  81. info = f"出了点小问题!\n{repr(err)}\n{traceback.format_exc()}"
  82. print(info)
  83. def is_date(date_str):
  84. """
  85. 判断date_str是否为日期格式
  86. :param date_str:
  87. :return:
  88. """
  89. date_format = "%Y-%m-%d"
  90. try:
  91. if isinstance(date_str, str):
  92. new_str = date_str[:10]
  93. valid_date = time.strptime(new_str, date_format)
  94. return True
  95. else:
  96. return False
  97. except ValueError or TypeError as e:
  98. return False
  99. def is_number(val: str):
  100. """
  101. 判断字符串的数字类型
  102. :param val: 字符串
  103. :return: 1--整数,2--小数,0--非数字
  104. """
  105. try:
  106. float(val)
  107. if val.isdigit() or val.split('-')[-1].isdigit():
  108. return 1
  109. elif val.split('.')[0].isdigit() or val.split('-')[-1].split('.')[-1].isdigit():
  110. return 2
  111. else:
  112. return 0
  113. except ValueError:
  114. pass
  115. return 0
  116. def style_head(worksheet, size, wrap=0, is_bg=0, color=22):
  117. """
  118. 表头的样式
  119. :param worksheet: 表格
  120. :param size: 表格列宽
  121. :param wrap: 1--自动换行,默认不换行
  122. :param is_bg: 1--设置背景色,默认不设置
  123. :param color: 默认浅灰色背景
  124. :return:
  125. """
  126. style = xlwt.XFStyle()
  127. set_font(style, height=14)
  128. set_border(style)
  129. set_widths(worksheet, size)
  130. dicts = {"horz": "CENTER", "vert": "CENTER"}
  131. set_alignments(style, wrap=wrap, **dicts)
  132. if is_bg == 1:
  133. set_pattern(style, color=color)
  134. return style
  135. def style_table_normal(dicts: dict):
  136. """
  137. 普通表格内容样式
  138. :param dicts: 对齐方式
  139. :return:
  140. """
  141. style = xlwt.XFStyle()
  142. set_font(style, bold=False)
  143. set_border(style)
  144. set_alignments(style, wrap=1, **dicts)
  145. return style
  146. def style_table_time():
  147. """
  148. 时间格式表格内容样式
  149. :return:
  150. """
  151. style = xlwt.XFStyle()
  152. set_border(style)
  153. style.num_format_str = "yyyy/MM/dd HH:MM:SS"
  154. dicts = {"horz": "LEFT", "vert": "CENTER"}
  155. set_alignments(style, wrap=1, **dicts)
  156. return style
  157. def set_font(style, bold=True, name='宋体', height=11):
  158. """
  159. 设置字体,默认宋体加粗,高度11
  160. :param style:
  161. :param bold:
  162. :param name:
  163. :param height:
  164. :return:
  165. """
  166. style.font.bold = bold
  167. style.font.name = name
  168. style.font.height = 20 * height
  169. def set_border(style, status=1):
  170. """
  171. 设置边框
  172. :param style:
  173. :param status:
  174. :return:
  175. """
  176. style.borders.left = status
  177. style.borders.right = status
  178. style.borders.top = status
  179. style.borders.bottom = status
  180. def set_pattern(style, color=23):
  181. """
  182. 设置表格背景颜色,默认深灰
  183. 0 = 黑, 1 = 白, 2 = 红, 3 = 绿, 4 = 蓝, 5 = 黄, 6 = 品红, 7 = 蓝绿,
  184. 16 = 褐红, 17 = 深绿, 18 = 深蓝, 19 = 棕色, 20 = 暗洋红, 21 = 蓝绿色, 22 = 浅灰, 23 = 深灰......
  185. :param style:
  186. :param color:
  187. :return:
  188. """
  189. style.pattern.pattern = xlwt.Pattern.SOLID_PATTERN
  190. style.pattern.pattern_fore_colour = color
  191. def set_widths(worksheet, size):
  192. """
  193. 设置宽度
  194. :param worksheet:
  195. :param size:
  196. :return:
  197. """
  198. for i, v in enumerate(size):
  199. worksheet.col(i).width = v * 256
  200. def set_alignments(style, wrap=1, **kwargs):
  201. """
  202. 设置对齐方式,默认自动换行
  203. 中心对齐参数:{"horz": "CENTER", "vert": "CENTER"}
  204. horz(水平):CENTER(居中),DISTRIBUTED(两端),GENERAL,CENTER_ACROSS_SEL(分散),RIGHT(右),LEFT(左)
  205. vert(垂直):CENTER(居中),DISTRIBUTED(两端),BOTTOM(下),TOP(上)
  206. """
  207. if "horz" in kwargs.keys():
  208. style.alignment.horz = eval(f"xlwt.Alignment.HORZ_{kwargs['horz'].upper()}")
  209. if "vert" in kwargs.keys():
  210. style.alignment.vert = eval(f"xlwt.Alignment.VERT_{kwargs['vert'].upper()}")
  211. style.alignment.wrap = wrap # 设置自动换行
  212. if __name__ == "__main__":
  213. db_info = yaml_read('cfg_mysql_test.yaml')
  214. datas = data_detail(db_info, '调查问卷数据统计测试')
  215. sheets_name = ['调查问卷基本信息', '调查问卷填写详情', '调查问卷问题和选项']
  216. to_excel(datas, sheets_name)

 运行结果:

四、统计查询结果到excel

同样使用xlwt模块,统计我们想要的数据,写入excel,并设置单元格的样式

  1. import datetime
  2. import os
  3. import time
  4. import traceback
  5. import xlwt
  6. def to_excel(data, sheet_name=None, file_name=None):
  7. """
  8. 数据写入excel
  9. :param data: 数据查询结果
  10. :param sheet_name: 工作表名称
  11. :param file_name: excel文件名称
  12. :return:
  13. """
  14. try:
  15. book = xlwt.Workbook(encoding='utf-8') # 新建工作簿
  16. new_datas = data_pre_handle(data) # 预处理数据
  17. if sheet_name is None:
  18. sheet_name = ["new_sheet" + str(i + 1) for i in range(len(new_datas))]
  19. write_data(book, new_datas, sheet_name) # 数据写入工作表
  20. save_excel(book, file_name) # 保存工作簿
  21. except Exception as err:
  22. info = f"出了点小问题!\n{repr(err)}\n{traceback.format_exc()}"
  23. print(info)
  24. def save_excel(workbook, file_name=None):
  25. """
  26. 保存工作簿
  27. :param workbook: 工作簿
  28. :param file_name: excel文件名称
  29. :return:
  30. """
  31. if file_name is None:
  32. sj = datetime.datetime.now().strftime("%Y-%m-%d %H%M%S")
  33. name = sj + ".xlsx"
  34. else:
  35. name = file_name + ".xlsx"
  36. workbook.save(name)
  37. cur_file = os.path.dirname(os.path.abspath(__file__)) + os.sep + name # 当前excel的保存路径
  38. print(f"excel保存成功!【{cur_file}】")
  39. def data_pre_handle(data):
  40. """
  41. 预处理数据库查询到的数据
  42. :param data: 数据查询结果
  43. :return:
  44. """
  45. return [[list(v[0].keys())] + [list(val.values()) for index, val in enumerate(v)] for i, v in enumerate(data)]
  46. def write_data(workbook, data, sheet_name):
  47. """
  48. 数据写入sheet
  49. :param workbook: 工作簿
  50. :param data: 要写入的数据
  51. :param sheet_name: 工作表名称
  52. :return:
  53. """
  54. try:
  55. for index, value in enumerate(data):
  56. sheet = workbook.add_sheet(sheet_name[index], cell_overwrite_ok=True) # 新建sheet
  57. size = [12 for i in value[0]]
  58. head_style = style_head(sheet, size, wrap=1, is_bg=1) # 定义表头样式
  59. time_style = style_table_time() # 时间内容样式
  60. normal_style = style_table_normal({"horz": "CENTER", "vert": "CENTER"}) # 普通内容样式
  61. for col, val in enumerate(value[0]):
  62. sheet.write(0, col, val, head_style)
  63. print(f"写入表头成功!{value[0]}")
  64. for i, v in enumerate(value[1:]):
  65. for co, va in enumerate(v):
  66. val = str(va)
  67. if is_date(val):
  68. if len(val) > 19:
  69. new_val = datetime.datetime.strptime(val, "%Y-%m-%d %H:%M:%S.%f")
  70. elif 10 < len(val) <= 19:
  71. new_val = datetime.datetime.strptime(val, "%Y-%m-%d %H:%M:%S")
  72. else:
  73. new_val = datetime.datetime.strptime(val, '%Y-%m-%d')
  74. sheet.write(i + 1, co, new_val, time_style)
  75. elif is_number(val) == 1 and len(val) < 11:
  76. sheet.write(i + 1, co, int(val), normal_style)
  77. else:
  78. sheet.write(i + 1, co, val, normal_style)
  79. print(f"写入表格内容成功!共{len(value) - 1}行")
  80. except Exception as err:
  81. info = f"出了点小问题!\n{repr(err)}\n{traceback.format_exc()}"
  82. print(info)
  83. def is_date(date_str):
  84. """
  85. 判断date_str是否为日期格式
  86. :param date_str:
  87. :return:
  88. """
  89. date_format = "%Y-%m-%d"
  90. try:
  91. if isinstance(date_str, str):
  92. new_str = date_str[:10]
  93. valid_date = time.strptime(new_str, date_format)
  94. return True
  95. else:
  96. return False
  97. except ValueError or TypeError as e:
  98. return False
  99. def is_number(val: str):
  100. """
  101. 判断字符串的数字类型
  102. :param val: 字符串
  103. :return: 1--整数,2--小数,0--非数字
  104. """
  105. try:
  106. float(val)
  107. if val.isdigit() or val.split('-')[-1].isdigit():
  108. return 1
  109. elif val.split('.')[0].isdigit() or val.split('-')[-1].split('.')[-1].isdigit():
  110. return 2
  111. else:
  112. return 0
  113. except ValueError:
  114. pass
  115. return 0
  116. def style_label():
  117. """
  118. 主表头的样式
  119. :return:
  120. """
  121. style = xlwt.XFStyle()
  122. set_font(style, height=16)
  123. set_border(style)
  124. set_pattern(style)
  125. dicts = {"horz": "CENTER", "vert": "CENTER"}
  126. set_alignments(style, wrap=0, **dicts)
  127. return style
  128. def style_head(worksheet, size, wrap=0, is_bg=0, color=22):
  129. """
  130. 表头的样式
  131. :param worksheet: 表格
  132. :param size: 表格列宽
  133. :param wrap: 1--自动换行,默认不换行
  134. :param is_bg: 1--设置背景色,默认不设置
  135. :param color: 默认浅灰色背景
  136. :return:
  137. """
  138. style = xlwt.XFStyle()
  139. set_font(style, height=14)
  140. set_border(style)
  141. set_widths(worksheet, size)
  142. dicts = {"horz": "CENTER", "vert": "CENTER"}
  143. set_alignments(style, wrap=wrap, **dicts)
  144. if is_bg == 1:
  145. set_pattern(style, color=color)
  146. return style
  147. def style_table_normal(dicts: dict):
  148. """
  149. 普通表格内容样式
  150. :param dicts: 对齐方式
  151. :return:
  152. """
  153. style = xlwt.XFStyle()
  154. set_font(style, bold=False)
  155. set_border(style)
  156. set_alignments(style, wrap=1, **dicts)
  157. return style
  158. def style_table_time():
  159. """
  160. 时间格式表格内容样式
  161. :return:
  162. """
  163. style = xlwt.XFStyle()
  164. set_border(style)
  165. style.num_format_str = "yyyy/MM/dd HH:MM:SS"
  166. dicts = {"horz": "LEFT", "vert": "CENTER"}
  167. set_alignments(style, wrap=1, **dicts)
  168. return style
  169. def style_table_percent():
  170. """
  171. 百分比格式表格内容样式
  172. :return:
  173. """
  174. style = xlwt.XFStyle()
  175. set_border(style)
  176. style.num_format_str = '0%'
  177. dicts = {"horz": "CENTER", "vert": "CENTER"}
  178. set_alignments(style, **dicts)
  179. return style
  180. def style_table_separator():
  181. """
  182. 分隔列表格内容样式
  183. :return:
  184. """
  185. style = xlwt.XFStyle()
  186. set_border(style)
  187. set_pattern(style, color=22)
  188. return style
  189. def set_font(style, bold=True, name='宋体', height=11):
  190. """
  191. 设置字体,默认宋体加粗,高度11
  192. :param style:
  193. :param bold:
  194. :param name:
  195. :param height:
  196. :return:
  197. """
  198. style.font.bold = bold
  199. style.font.name = name
  200. style.font.height = 20 * height
  201. def set_border(style, status=1):
  202. """
  203. 设置边框
  204. :param style:
  205. :param status:
  206. :return:
  207. """
  208. style.borders.left = status
  209. style.borders.right = status
  210. style.borders.top = status
  211. style.borders.bottom = status
  212. def set_pattern(style, color=23):
  213. """
  214. 设置表格背景颜色,默认深灰
  215. 0 = 黑, 1 = 白, 2 = 红, 3 = 绿, 4 = 蓝, 5 = 黄, 6 = 品红, 7 = 蓝绿,
  216. 16 = 褐红, 17 = 深绿, 18 = 深蓝, 19 = 棕色, 20 = 暗洋红, 21 = 蓝绿色, 22 = 浅灰, 23 = 深灰......
  217. :param style:
  218. :param color:
  219. :return:
  220. """
  221. style.pattern.pattern = xlwt.Pattern.SOLID_PATTERN
  222. style.pattern.pattern_fore_colour = color
  223. def set_widths(worksheet, size):
  224. """
  225. 设置宽度
  226. :param worksheet:
  227. :param size:
  228. :return:
  229. """
  230. for i, v in enumerate(size):
  231. worksheet.col(i).width = v * 256
  232. def set_alignments(style, wrap=1, **kwargs):
  233. """
  234. 设置对齐方式,默认自动换行
  235. 中心对齐参数:{"horz": "CENTER", "vert": "CENTER"}
  236. horz(水平):CENTER(居中),DISTRIBUTED(两端),GENERAL,CENTER_ACROSS_SEL(分散),RIGHT(右),LEFT(左)
  237. vert(垂直):CENTER(居中),DISTRIBUTED(两端),BOTTOM(下),TOP(上)
  238. """
  239. if "horz" in kwargs.keys():
  240. style.alignment.horz = eval(f"xlwt.Alignment.HORZ_{kwargs['horz'].upper()}")
  241. if "vert" in kwargs.keys():
  242. style.alignment.vert = eval(f"xlwt.Alignment.VERT_{kwargs['vert'].upper()}")
  243. style.alignment.wrap = wrap # 设置自动换行
  244. def data_statistics(data):
  245. """
  246. 统计详细数据,保存为excel
  247. :param data: 数据库查询结果
  248. :return:
  249. """
  250. wb = xlwt.Workbook(encoding='utf-8') # 新建工作簿
  251. ws = wb.add_sheet('活动问卷数据汇总', cell_overwrite_ok=True) # 新建sheet
  252. label_style = style_label()
  253. head_style = style_head(ws, size=[20, 20, 2, 10, 20, 20, 25, 2, 10, 25, 20, 2, 25, 20, 8, 25, 25])
  254. dicts1 = {"horz": "CENTER", "vert": "CENTER"}
  255. dicts2 = {"horz": "LEFT", "vert": "CENTER"}
  256. normal_table_style_center = style_table_normal(dicts1)
  257. normal_table_style_left = style_table_normal(dicts2)
  258. separator_table_style = style_table_separator()
  259. time_table_style = style_table_time()
  260. percent_table_style = style_table_percent()
  261. # 基本信息
  262. ws.write_merge(0, 0, 0, 1, '基本信息', label_style)
  263. ws.write(1, 0, '调查名称', head_style)
  264. ws.write(1, 1, data[0][0]['调查名称'], normal_table_style_center)
  265. ws.write(2, 0, '调查问卷名称', head_style)
  266. ws.write(2, 1, data[0][0]['调查问卷名称'], normal_table_style_center)
  267. ws.write(3, 0, '问卷发送人数', head_style)
  268. result1 = [v['姓名'] for v in data[0] if v['目前卡片数'] == 18]
  269. ws.write(3, 1, len(result1), normal_table_style_center)
  270. ws.write(4, 0, '问卷提交人数', head_style)
  271. result2 = [v['姓名'] for v in data[0] if v['填写时长'] is not None]
  272. ws.write(4, 1, len(result2), normal_table_style_center)
  273. # 分隔
  274. ws.write_merge(0, 142, 2, 2, '', separator_table_style)
  275. # 用户问卷发送及提交信息
  276. ws.write_merge(0, 0, 3, 6, '用户问卷发送及提交信息', label_style)
  277. head1 = ['姓名', '问卷发送时间', '问卷提交时间', '填写时长(s)']
  278. for i, v in enumerate(head1):
  279. ws.write(1, i + 3, v, head_style)
  280. for row, v in enumerate(data[0]):
  281. ws.write(row + 2, 3, v['姓名'], normal_table_style_left)
  282. if v['目前卡片数'] != 18:
  283. ws.write(row + 2, 4, '', normal_table_style_left)
  284. else:
  285. ws.write(row + 2, 4, v['最后卡片时间'], time_table_style)
  286. for j in data[1]:
  287. if v['会员id'] == j['会员id']:
  288. ws.write(row + 2, 5, j['问卷提交时间'], time_table_style)
  289. break
  290. if v['会员id'] not in [i['会员id'] for i in data[1]]:
  291. ws.write(row + 2, 5, '', normal_table_style_left)
  292. if v['填写时长'] is None:
  293. ws.write(row + 2, 6, '', normal_table_style_left)
  294. else:
  295. ws.write(row + 2, 6, v['填写时长'], normal_table_style_center)
  296. # 分隔
  297. ws.write_merge(0, 142, 7, 7, '', separator_table_style)
  298. # 用户问卷填写内容详情
  299. ws.write_merge(0, 0, 8, 10, '用户问卷填写内容详情', label_style)
  300. head2 = ['姓名', '问题', '答案']
  301. for i, v in enumerate(head2):
  302. ws.write(1, i + 8, v, head_style)
  303. for row, val in enumerate(data[1]):
  304. ws.write(row + 2, 8, val['姓名'], normal_table_style_left)
  305. ws.write(row + 2, 9, val['问题'], normal_table_style_left)
  306. ws.write(row + 2, 10, val['答案'], normal_table_style_left)
  307. # 分隔
  308. ws.write_merge(0, 142, 11, 11, '', separator_table_style)
  309. # 问卷问题选项信息
  310. ws.write_merge(0, 0, 12, 16, '问卷问题选项信息', label_style)
  311. head3 = ['问题', '问题选项', '题型', '选项被选择的人数', '选项被选择的占比']
  312. for i, v in enumerate(head3):
  313. ws.write(1, i + 12, v, head_style)
  314. for row, val in enumerate(data[2]):
  315. ws.write(row + 2, 12, val['题目'], normal_table_style_left)
  316. ws.write(row + 2, 13, val['选项'], normal_table_style_left)
  317. ws.write(row + 2, 14, val['题型'], normal_table_style_center)
  318. result3 = [v['答案'] for v in data[1] if val['题目'] in v['问题'] if val['选项'] in v['答案']]
  319. ws.write(row + 2, 15, len(result3), normal_table_style_center)
  320. ws.write(row + 2, 16, len(result3) / len(result2), percent_table_style)
  321. sj = datetime.datetime.now().strftime('%Y-%m-%d %H%M%S')
  322. wb.save('调查问卷数据统计' + sj + '.xlsx') # 保存excel
  323. if __name__ == "__main__":
  324. db_info = yaml_read('cfg_mysql_test.yaml')
  325. datas = data_detail(db_info, '调查问卷数据统计测试')
  326. sheets_name = ['调查问卷基本信息', '调查问卷填写详情', '调查问卷问题和选项']
  327. # to_excel(datas, sheets_name)
  328. data_statistics(datas)

 运行结果:

五、封装形成完整代码

把上面的方法封装成类,这里使用静态方法装饰器@staticmethod:不需self参数,通过类或者类实例来调用

  1. import datetime
  2. import os
  3. import time
  4. import traceback
  5. import pymysql
  6. import xlwt
  7. import yaml
  8. class ExcelStyle:
  9. @staticmethod
  10. def style_label():
  11. """
  12. 主表头的样式
  13. :return:
  14. """
  15. se = ExcelStyle()
  16. style = se.styles()
  17. se.set_font(style, height=16)
  18. se.set_border(style)
  19. se.set_pattern(style)
  20. dicts = {"horz": "CENTER", "vert": "CENTER"}
  21. se.set_alignments(style, wrap=0, **dicts)
  22. return style
  23. @staticmethod
  24. def style_head(worksheet, size, wrap=0, is_bg=0, color=22):
  25. """
  26. 表头的样式
  27. :param worksheet: 表格
  28. :param size: 表格列宽
  29. :param wrap: 1--自动换行,默认不换行
  30. :param is_bg: 1--设置背景色,默认不设置
  31. :param color: 默认浅灰色背景
  32. :return:
  33. """
  34. se = ExcelStyle()
  35. style = se.styles()
  36. se.set_font(style, height=14)
  37. se.set_border(style)
  38. se.set_widths(worksheet, size)
  39. dicts = {"horz": "CENTER", "vert": "CENTER"}
  40. se.set_alignments(style, wrap=wrap, **dicts)
  41. if is_bg == 1:
  42. se.set_pattern(style, color=color)
  43. return style
  44. @staticmethod
  45. def style_table_normal(dicts: dict):
  46. """
  47. 普通表格内容样式
  48. :param dicts: 对齐方式
  49. :return:
  50. """
  51. se = ExcelStyle()
  52. style = se.styles()
  53. se.set_font(style, bold=False)
  54. se.set_border(style)
  55. se.set_alignments(style, wrap=1, **dicts)
  56. return style
  57. @staticmethod
  58. def style_table_time():
  59. """
  60. 时间格式表格内容样式
  61. :return:
  62. """
  63. se = ExcelStyle()
  64. style = se.styles()
  65. se.set_border(style)
  66. style.num_format_str = "yyyy/MM/dd HH:MM:SS"
  67. dicts = {"horz": "LEFT", "vert": "CENTER"}
  68. se.set_alignments(style, wrap=1, **dicts)
  69. return style
  70. @staticmethod
  71. def style_table_percent():
  72. """
  73. 百分比格式表格内容样式
  74. :return:
  75. """
  76. se = ExcelStyle()
  77. style = se.styles()
  78. se.set_border(style)
  79. style.num_format_str = '0%'
  80. dicts = {"horz": "CENTER", "vert": "CENTER"}
  81. se.set_alignments(style, **dicts)
  82. return style
  83. @staticmethod
  84. def style_table_separator():
  85. """
  86. 分隔列表格内容样式
  87. :return:
  88. """
  89. se = ExcelStyle()
  90. style = se.styles()
  91. se.set_border(style)
  92. se.set_pattern(style, color=22)
  93. return style
  94. @staticmethod
  95. def styles():
  96. """设置单元格的样式的基础方法"""
  97. style = xlwt.XFStyle()
  98. return style
  99. @staticmethod
  100. def set_font(style, bold=True, name='宋体', height=11):
  101. """
  102. 设置字体,默认宋体加粗,高度11
  103. :param style:
  104. :param bold:
  105. :param name:
  106. :param height:
  107. :return:
  108. """
  109. style.font.bold = bold
  110. style.font.name = name
  111. style.font.height = 20 * height
  112. @staticmethod
  113. def set_border(style, status=1):
  114. """
  115. 设置边框
  116. :param style:
  117. :param status:
  118. :return:
  119. """
  120. style.borders.left = status
  121. style.borders.right = status
  122. style.borders.top = status
  123. style.borders.bottom = status
  124. @staticmethod
  125. def set_pattern(style, color=23):
  126. """
  127. 设置表格背景颜色,默认深灰
  128. 0 = 黑, 1 = 白, 2 = 红, 3 = 绿, 4 = 蓝, 5 = 黄, 6 = 品红, 7 = 蓝绿,
  129. 16 = 褐红, 17 = 深绿, 18 = 深蓝, 19 = 棕色, 20 = 暗洋红, 21 = 蓝绿色, 22 = 浅灰, 23 = 深灰......
  130. :param style:
  131. :param color:
  132. :return:
  133. """
  134. style.pattern.pattern = xlwt.Pattern.SOLID_PATTERN
  135. style.pattern.pattern_fore_colour = color
  136. @staticmethod
  137. def set_widths(worksheet, size):
  138. """
  139. 设置宽度
  140. :param worksheet:
  141. :param size:
  142. :return:
  143. """
  144. for i, v in enumerate(size):
  145. worksheet.col(i).width = v * 256
  146. @staticmethod
  147. def set_alignments(style, wrap=1, **kwargs):
  148. """
  149. 设置对齐方式,默认自动换行
  150. 中心对齐参数:{"horz": "CENTER", "vert": "CENTER"}
  151. horz(水平):CENTER(居中),DISTRIBUTED(两端),GENERAL,CENTER_ACROSS_SEL(分散),RIGHT(右),LEFT(左)
  152. vert(垂直):CENTER(居中),DISTRIBUTED(两端),BOTTOM(下),TOP(上)
  153. """
  154. if "horz" in kwargs.keys():
  155. style.alignment.horz = eval(f"xlwt.Alignment.HORZ_{kwargs['horz'].upper()}")
  156. if "vert" in kwargs.keys():
  157. style.alignment.vert = eval(f"xlwt.Alignment.VERT_{kwargs['vert'].upper()}")
  158. style.alignment.wrap = wrap # 设置自动换行
  159. class DataStatistics(ExcelStyle):
  160. @staticmethod
  161. def data_detail(dbinfo, survey_name):
  162. """
  163. 连接数据库查询详细数据,保存为excel
  164. :param dbinfo: 数据库连接信息
  165. :param survey_name: 调查名称
  166. :return: 数据库查询结果
  167. """
  168. se = DataStatistics()
  169. sql_name = ['调查问卷基本信息', '调查问卷填写详情', '调查问卷问题和选项']
  170. sql_list = [f'调查问卷基本信息查询SQL:select * from table1 where name={survey_name}', '调查问卷填写详情查询SQL', '调查问卷问题和选项查询SQL']
  171. data = [se.execute_sql(dbinfo, sql) for sql in sql_list]
  172. se.to_excel(data, sql_name)
  173. return data
  174. @staticmethod
  175. def execute_sql(dbinfo, sql):
  176. """
  177. 连接数据库,执行sql查询语句,返回查询结果
  178. :param dbinfo: 数据库配置信息
  179. :param sql: 数据库查询语句
  180. :return:
  181. """
  182. try:
  183. conn = pymysql.connect(**dbinfo) # 连接数据库
  184. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 数据库游标
  185. cursor.execute(sql) # 执行sql语句
  186. results = cursor.fetchall() # 查询结果
  187. conn.close() # 关闭数据库连接
  188. return results
  189. except Exception as err:
  190. info = f"出了点小问题!\n{repr(err)}\n{traceback.format_exc()}"
  191. print(info)
  192. @staticmethod
  193. def to_excel(data, sheet_name=None, file_name=None):
  194. """
  195. 数据写入excel
  196. :param data: 数据查询结果
  197. :param sheet_name: 工作表名称
  198. :param file_name: excel文件名称
  199. :return:
  200. """
  201. se = DataStatistics()
  202. try:
  203. book = xlwt.Workbook(encoding='utf-8') # 新建工作簿
  204. new_datas = se.data_pre_handle(data) # 预处理数据
  205. if sheet_name is None:
  206. sheet_name = ["new_sheet" + str(i + 1) for i in range(len(new_datas))]
  207. se.write_data(book, new_datas, sheet_name) # 数据写入工作表
  208. se.save_excel(book, file_name) # 保存工作簿
  209. except Exception as err:
  210. info = f"出了点小问题!\n{repr(err)}\n{traceback.format_exc()}"
  211. print(info)
  212. @staticmethod
  213. def save_excel(workbook, file_name=None):
  214. """
  215. 保存工作簿
  216. :param workbook: 工作簿
  217. :param file_name: excel文件名称
  218. :return:
  219. """
  220. if file_name is None:
  221. sj = datetime.datetime.now().strftime("%Y-%m-%d %H%M%S")
  222. name = sj + ".xlsx"
  223. else:
  224. name = file_name + ".xlsx"
  225. workbook.save(name)
  226. cur_file = os.path.dirname(os.path.abspath(__file__)) + os.sep + name # 当前excel的保存路径
  227. print(f"excel保存成功!【{cur_file}】")
  228. @staticmethod
  229. def data_pre_handle(data):
  230. """
  231. 预处理数据库查询到的数据
  232. :param data: 数据查询结果
  233. :return:
  234. """
  235. return [[list(v[0].keys())] + [list(val.values()) for index, val in enumerate(v)] for i, v in enumerate(data)]
  236. @staticmethod
  237. def write_data(workbook, data, sheet_name):
  238. """
  239. 数据写入sheet
  240. :param workbook: 工作簿
  241. :param data: 要写入的数据
  242. :param sheet_name: 工作表名称
  243. :return:
  244. """
  245. se = DataStatistics()
  246. try:
  247. for index, value in enumerate(data):
  248. sheet = workbook.add_sheet(sheet_name[index], cell_overwrite_ok=True) # 新建sheet
  249. size = [12 for i in value[0]]
  250. head_style = se.style_head(sheet, size, wrap=1, is_bg=1) # 定义表头样式
  251. time_style = se.style_table_time() # 时间内容样式
  252. normal_style = se.style_table_normal({"horz": "CENTER", "vert": "CENTER"}) # 普通内容样式
  253. for col, val in enumerate(value[0]):
  254. sheet.write(0, col, val, head_style)
  255. print(f"写入表头成功!{value[0]}")
  256. for i, v in enumerate(value[1:]):
  257. for co, va in enumerate(v):
  258. val = str(va)
  259. if se.is_date(val):
  260. if len(val) > 19:
  261. new_val = datetime.datetime.strptime(val, "%Y-%m-%d %H:%M:%S.%f")
  262. elif 10 < len(val) <= 19:
  263. new_val = datetime.datetime.strptime(val, "%Y-%m-%d %H:%M:%S")
  264. else:
  265. new_val = datetime.datetime.strptime(val, '%Y-%m-%d')
  266. sheet.write(i + 1, co, new_val, time_style)
  267. elif se.is_number(val) == 1 and len(val) < 11:
  268. sheet.write(i + 1, co, int(val), normal_style)
  269. else:
  270. sheet.write(i + 1, co, val, normal_style)
  271. print(f"写入表格内容成功!共{len(value) - 1}行")
  272. except Exception as err:
  273. info = f"出了点小问题!\n{repr(err)}\n{traceback.format_exc()}"
  274. print(info)
  275. @staticmethod
  276. def is_date(date_str):
  277. """
  278. 判断date_str是否为日期格式
  279. :param date_str:
  280. :return:
  281. """
  282. date_format = "%Y-%m-%d"
  283. try:
  284. if isinstance(date_str, str):
  285. new_str = date_str[:10]
  286. valid_date = time.strptime(new_str, date_format)
  287. return True
  288. else:
  289. return False
  290. except ValueError or TypeError as e:
  291. return False
  292. @staticmethod
  293. def is_number(val: str):
  294. """
  295. 判断字符串的数字类型
  296. :param val: 字符串
  297. :return: 1--整数,2--小数,0--非数字
  298. """
  299. try:
  300. float(val)
  301. if val.isdigit() or val.split('-')[-1].isdigit():
  302. return 1
  303. elif val.split('.')[0].isdigit() or val.split('-')[-1].split('.')[-1].isdigit():
  304. return 2
  305. else:
  306. return 0
  307. except ValueError:
  308. pass
  309. return 0
  310. @staticmethod
  311. def data_statistics(data):
  312. """
  313. 统计详细数据,保存为excel
  314. :param data: 数据库查询结果
  315. :return:
  316. """
  317. se = DataStatistics()
  318. wb = xlwt.Workbook(encoding='utf-8') # 新建工作簿
  319. ws = wb.add_sheet('活动问卷数据汇总', cell_overwrite_ok=True) # 新建sheet
  320. label_style = se.style_label()
  321. head_style = se.style_head(ws, size=[20, 20, 2, 10, 20, 20, 25, 2, 10, 25, 20, 2, 25, 20, 8, 25, 25])
  322. dicts1 = {"horz": "CENTER", "vert": "CENTER"}
  323. dicts2 = {"horz": "LEFT", "vert": "CENTER"}
  324. normal_table_style_center = se.style_table_normal(dicts1)
  325. normal_table_style_left = se.style_table_normal(dicts2)
  326. separator_table_style = se.style_table_separator()
  327. time_table_style = se.style_table_time()
  328. percent_table_style = se.style_table_percent()
  329. # 基本信息
  330. ws.write_merge(0, 0, 0, 1, '基本信息', label_style)
  331. ws.write(1, 0, '调查名称', head_style)
  332. ws.write(1, 1, data[0][0]['调查名称'], normal_table_style_center)
  333. ws.write(2, 0, '调查问卷名称', head_style)
  334. ws.write(2, 1, data[0][0]['调查问卷名称'], normal_table_style_center)
  335. ws.write(3, 0, '问卷发送人数', head_style)
  336. result1 = [v['姓名'] for v in data[0] if v['目前卡片数'] == 18]
  337. ws.write(3, 1, len(result1), normal_table_style_center)
  338. ws.write(4, 0, '问卷提交人数', head_style)
  339. result2 = [v['姓名'] for v in data[0] if v['填写时长'] is not None]
  340. ws.write(4, 1, len(result2), normal_table_style_center)
  341. # 分隔
  342. ws.write_merge(0, 142, 2, 2, '', separator_table_style)
  343. # 用户问卷发送及提交信息
  344. ws.write_merge(0, 0, 3, 6, '用户问卷发送及提交信息', label_style)
  345. head1 = ['姓名', '问卷发送时间', '问卷提交时间', '填写时长(s)']
  346. for i, v in enumerate(head1):
  347. ws.write(1, i + 3, v, head_style)
  348. for row, v in enumerate(data[0]):
  349. ws.write(row + 2, 3, v['姓名'], normal_table_style_left)
  350. if v['目前卡片数'] != 18:
  351. ws.write(row + 2, 4, '', normal_table_style_left)
  352. else:
  353. ws.write(row + 2, 4, v['最后卡片时间'], time_table_style)
  354. for j in data[1]:
  355. if v['会员id'] == j['会员id']:
  356. ws.write(row + 2, 5, j['问卷提交时间'], time_table_style)
  357. break
  358. if v['会员id'] not in [i['会员id'] for i in data[1]]:
  359. ws.write(row + 2, 5, '', normal_table_style_left)
  360. if v['填写时长'] is None:
  361. ws.write(row + 2, 6, '', normal_table_style_left)
  362. else:
  363. ws.write(row + 2, 6, v['填写时长'], normal_table_style_center)
  364. # 分隔
  365. ws.write_merge(0, 142, 7, 7, '', separator_table_style)
  366. # 用户问卷填写内容详情
  367. ws.write_merge(0, 0, 8, 10, '用户问卷填写内容详情', label_style)
  368. head2 = ['姓名', '问题', '答案']
  369. for i, v in enumerate(head2):
  370. ws.write(1, i + 8, v, head_style)
  371. for row, val in enumerate(data[1]):
  372. ws.write(row + 2, 8, val['姓名'], normal_table_style_left)
  373. ws.write(row + 2, 9, val['问题'], normal_table_style_left)
  374. ws.write(row + 2, 10, val['答案'], normal_table_style_left)
  375. # 分隔
  376. ws.write_merge(0, 142, 11, 11, '', separator_table_style)
  377. # 问卷问题选项信息
  378. ws.write_merge(0, 0, 12, 16, '问卷问题选项信息', label_style)
  379. head3 = ['问题', '问题选项', '题型', '选项被选择的人数', '选项被选择的占比']
  380. for i, v in enumerate(head3):
  381. ws.write(1, i + 12, v, head_style)
  382. for row, val in enumerate(data[2]):
  383. ws.write(row + 2, 12, val['题目'], normal_table_style_left)
  384. ws.write(row + 2, 13, val['选项'], normal_table_style_left)
  385. ws.write(row + 2, 14, val['题型'], normal_table_style_center)
  386. result3 = [v['答案'] for v in data[1] if val['题目'] in v['问题'] if val['选项'] in v['答案']]
  387. ws.write(row + 2, 15, len(result3), normal_table_style_center)
  388. ws.write(row + 2, 16, len(result3) / len(result2), percent_table_style)
  389. sj = datetime.datetime.now().strftime('%Y-%m-%d %H%M%S')
  390. wb.save('调查问卷数据统计' + sj + '.xlsx') # 保存excel
  391. def yaml_read(filename):
  392. """
  393. 读取yaml文件
  394. """
  395. with open(file=filename, mode="r", encoding="utf-8") as f:
  396. data = yaml.safe_load(f.read())
  397. return data
  398. if __name__ == "__main__":
  399. db_info = yaml_read('cfg_mysql_test.yaml')
  400. ds = DataStatistics()
  401. datas = ds.data_detail(db_info, '调查问卷数据统计测试')
  402. ds.data_statistics(datas)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/605940
推荐阅读
相关标签
  

闽ICP备14008679号