前言在完成 UA 随机轮换、请求延时、代理 IP、异常重试与日志体系搭建后爬虫已具备稳定单线程抓取能力但是单线程串行采集模式在大批量数据抓取场景中效率短板突出逐条串行请求受网络空闲等待损耗制约海量分页、多目标页面采集耗时成倍增加。同时此前项目仅采用 MySQL 入库、本地 TXT 文本两种存储方案在轻量化数据归档、离线数据分析、简易数据分发场景中存在使用不便的问题CSV、Excel 作为通用表格格式适配办公软件直接打开编辑是中小型爬虫项目轻量化存储首选。除此之外重复采集冗余数据长期占用数据库与本地存储空间数据去重逻辑成为工程化爬虫不可或缺的组成模块。本文依托前面文库、百科、技术社区、天气 API 四大项目源码进行改造升级新增 openpyxl、csv 内置模块、threading 多线程组件从文件存储实操、多线程并发抓取、基于链接 / 字段的数据去重三个核心模块展开完整案例开发配套对应数据表优化、原有爬虫代码重构。 本篇新增依赖组件官方文档链接汇总openpyxlExcel 读写专用第三方库支持.xlsx 格式新建、写入、追加数据Python 内置 csv标准库无需额外安装轻量化 CSV 文件读写threadingPython 内置多线程标准库实现 IO 密集型爬虫并发抓取hashlib内置哈希算法库用于字段加密生成指纹实现数据去重一、爬虫轻量化文件存储方案选型与原理对比1.1 CSV 与 Excel 存储特性对照表表格存储格式依赖条件优势适用场景短板CSVPython 原生 csv 模块零安装体积小巧、读写速度快、全平台软件兼容、占用资源极低海量明细数据落地、定时采集日志归档、接口批量数据存储不支持复杂单元格样式、无法插入图片与多工作表、纯文本结构XLSX(Excel)openpyxl 第三方库多工作表分类存储、单元格格式自定义、便于人工整理统计小体量样本数据、结果汇总报表、学习演示数据导出大批量数据写入内存占用偏高十万级以上数据性能弱于 CSV1.2 CSV 通用读写工具类开发CSV 依托内置模块实现分为文件新建表头、单行追加写入、批量多行写入三种常用逻辑工具类可全项目复用。python运行import csv import os from spider_log import spider_log class CsvSaveUtil: def __init__(self, file_path, header_list): self.file_path file_path self.header header_list # 判断文件是否存在不存在则写入表头 if not os.path.exists(self.file_path): self.write_header() def write_header(self): 写入CSV表头信息 try: with open(self.file_path, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow(self.header) spider_log.info(fCSV文件{self.file_path}表头创建成功) except Exception as e: spider_log.error(f表头写入失败:{str(e)}) def write_row(self, data_row): 单行数据追加写入 try: with open(self.file_path, a, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow(data_row) except Exception as e: spider_log.error(f单行写入异常:{str(e)}) def write_rows(self, data_list): 批量多行数据写入 try: with open(self.file_path, a, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerows(data_list) spider_log.info(f批量{len(data_list)}条数据写入CSV完成) except Exception as e: spider_log.error(f批量写入异常:{str(e)})1.3 CSV 工具原理说明encodingutf-8-sig规避 Windows 系统 Excel 打开 CSV 出现中文乱码问题区别于普通 utf-8 编码newline消除不同操作系统写入 CSV 时自动多出空行的问题文件存在性判断逻辑首次创建文件写入表头后续采集仅追加内容避免重复覆盖表头。1.4 技术社区爬虫接入 CSV 存储改造示例python运行# 在爬虫初始化中定义CSV配置 def __init__(self): self.headers {User-Agent: ua_tool.get_random_ua()} self.result_list [] header [帖子标题,作者,发布时间,阅读量,帖子链接] self.csv_util CsvSaveUtil(tech_post.csv, header) # 采集结束后批量写入 def save_to_file(self): data_arr [] for item in self.result_list: row [item[title],item[author],item[publish_time],item[view_count],item[url]] data_arr.append(row) self.csv_util.write_rows(data_arr)1.5 Excel 通用写入工具类实现bash运行pip install openpyxlpython运行from openpyxl import Workbook, load_workbook import os from spider_log import spider_log class ExcelSaveUtil: def __init__(self, file_path, sheet_namedata, headerNone): self.file_path file_path self.sheet_name sheet_name self.header header if os.path.exists(self.file_path): self.wb load_workbook(self.file_path) if self.sheet_name in self.wb.sheetnames: self.ws self.wb[self.sheet_name] else: self.ws self.wb.create_sheet(self.sheet_name) if self.header: self.ws.append(self.header) else: self.wb Workbook() self.ws self.wb.active self.ws.title self.sheet_name if self.header: self.ws.append(self.header) self.wb.save(self.file_path) def append_row(self, row_data): 单行追加数据 self.ws.append(row_data) self.wb.save(self.file_path) def append_batch(self, batch_data): 批量追加多行 for row in batch_data: self.ws.append(row) self.wb.save(self.file_path) spider_log.info(fExcel批量写入{len(batch_data)}条数据)1.6 Excel 存储核心原理Workbook代表整个 Excel 文件对象worksheet对应表格内工作表一个 Excel 可创建多张 sheet 分类存储不同维度数据load_workbook用于读取已存在的 Excel 文件避免每次写入覆盖原有全部数据每次写入后执行 save 落地磁盘防止程序异常退出导致数据丢失。二、基于 threading 的 IO 密集型多线程爬虫开发2.1 爬虫选用多线程的底层逻辑爬虫绝大多数运行时间消耗在网络请求 IO 等待阶段CPU 空闲闲置Python 多线程受 GIL 全局解释器锁限制不适合 CPU 密集运算但完美适配 IO 密集场景通过多线程并行发起网络请求把串行等待时间转为并发执行采集效率提升 3~8 倍。爬虫禁止无限制创建线程海量任务需搭配线程池控制并发上限避免瞬间海量请求触发反爬。2.2 简易线程池封装控制并发数量python运行import threading from queue import Queue from spider_log import spider_log class CrawlThreadPool: def __init__(self, max_thread5): self.max_thread max_thread self.task_queue Queue() self.result_queue Queue() self.thread_list [] def put_task(self, task_data): 存入待采集任务 self.task_queue.put(task_data) def thread_func(self, crawl_func): 子线程循环取任务执行 while not self.task_queue.empty(): try: task self.task_queue.get(timeout2) res crawl_func(task) if res: self.result_queue.put(res) self.task_queue.task_done() except Exception as e: spider_log.warning(f子线程任务异常:{str(e)}) def start_run(self, crawl_func): 启动线程池执行任务 for _ in range(self.max_thread): t threading.Thread(targetself.thread_func, args(crawl_func,)) t.daemon True t.start() self.thread_list.append(t) self.task_queue.join() spider_log.info(全部任务执行完毕)2.3 文库文摘爬虫接入多线程实战原有单条 URL 逐个抓取改造为批量 URL 入队、多线程并发抓取python运行# 待采集详情链接列表 url_list [ https://example.com/lib/1, https://example.com/lib/2, https://example.com/lib/3 ] def single_crawl_task(url): 单个任务抓取函数传入URL返回解析数据 spider LibrarySpider() html spider.get_page_html(url) data spider.parse_abstract(html) if data: spider.save_to_db(data, url) return data return None # 初始化线程池最大并发3个线程 pool CrawlThreadPool(max_thread3) for item_url in url_list: pool.put_task(item_url) pool.start_run(single_crawl_task)2.4 线程池关键参数配置参考表格站点风控等级最大并发线程数配套延时策略宽松公开 API (天气接口)5~8随机 0.3~1 秒延时资讯文库、百科站点3~5随机 1~2.5 秒延时风控严格技术社区1~2随机 2~4 秒延时三、数据去重实现URL 指纹 哈希字段双重去重方案重复采集分为两类同源 URL 重复抓取、同一标题内容重复生成采用 MD5 哈希生成数据指纹是工业级爬虫主流去重手段将字符串转为固定长度密文对比密文即可快速判定重复。3.1 MD5 指纹生成工具类python运行import hashlib class DataHashUtil: staticmethod def get_md5(content: str): 生成字符串MD5指纹 md5_obj hashlib.md5(content.encode(utf-8)) return md5_obj.hexdigest()3.2 三种落地去重方案3.2.1 内存集合临时去重单次运行去重爬虫单次运行中使用 set 集合存储已采集 URL 指纹请求前校验适合临时批量抓取python运行already_spider_set set() def check_url_duplicate(url): md5_str DataHashUtil.get_md5(url) if md5_str in already_spider_set: return True already_spider_set.add(md5_str) return False3.2.2 MySQL 数据库唯一索引永久去重推荐长期项目修改此前文库数据表对 source_url 增加唯一索引重复 URL 插入时数据库直接抛出异常捕获异常跳过入库sqlALTER TABLE library_abstract ADD UNIQUE INDEX idx_source_url(source_url);入库代码捕获唯一索引冲突异常python运行from pymysql.err import IntegrityError try: self.db_cursor.execute(sql, params) self.db_conn.commit() except IntegrityError: spider_log.info(f数据重复跳过入库:{data[title]}) self.db_conn.rollback()3.2.3 本地指纹文件持久化去重无数据库环境将所有 URL 指纹存入 txt 文件每次采集前读取指纹列表比对适合轻量化离线爬虫。3.3 标题内容去重补充部分站点 URL 不同但文摘内容高度一致提取标题 摘要拼接字符串生成 MD5实现内容维度去重。四、原有项目全量适配优化汇总表格项目名称新增改造项优化收益基础异常爬虫CSV 结果导出 内存去重测试数据快速落地表格避免重复测试采集开源文库入库爬虫Excel 备份 多线程并发 数据库唯一索引去重批量文摘抓取效率提升 4 倍彻底杜绝重复入库天气定时采集爬虫CSV 按日分文件存储每日天气数据单独归档便于按月统计分析百科词条爬虫多线程批量词条抓取 URL 指纹去重批量词条快速采集过滤重复词条链接技术社区爬虫ExcelCSV 双备份 分页去重多页采集自动过滤重复帖子双格式留存数据五、开发拓展方向与并发合规提示5.1 后续拓展方向引入 ThreadPoolExecutor 内置线程池替代原生 threading简化任务调度代码使用 Redis 集合持久化存储指纹实现爬虫重启后依然保留历史去重记录基于 pandas 实现 CSV、Excel 大数据清洗、筛选、格式转换。5.2 多线程合规提醒并发不等于无节制高频请求即便多线程 代理 IP仍需要依托随机延时控制整体请求速率多线程场景适当拉长单请求休眠时间规避站点短时间瞬时大量请求触发封禁所有并发采集仅针对完全公开无权限限制的网络资源。
Python 爬虫实战:CSV/Excel 文件存储、多线程爬虫开发与数据去重落地实战
前言在完成 UA 随机轮换、请求延时、代理 IP、异常重试与日志体系搭建后爬虫已具备稳定单线程抓取能力但是单线程串行采集模式在大批量数据抓取场景中效率短板突出逐条串行请求受网络空闲等待损耗制约海量分页、多目标页面采集耗时成倍增加。同时此前项目仅采用 MySQL 入库、本地 TXT 文本两种存储方案在轻量化数据归档、离线数据分析、简易数据分发场景中存在使用不便的问题CSV、Excel 作为通用表格格式适配办公软件直接打开编辑是中小型爬虫项目轻量化存储首选。除此之外重复采集冗余数据长期占用数据库与本地存储空间数据去重逻辑成为工程化爬虫不可或缺的组成模块。本文依托前面文库、百科、技术社区、天气 API 四大项目源码进行改造升级新增 openpyxl、csv 内置模块、threading 多线程组件从文件存储实操、多线程并发抓取、基于链接 / 字段的数据去重三个核心模块展开完整案例开发配套对应数据表优化、原有爬虫代码重构。 本篇新增依赖组件官方文档链接汇总openpyxlExcel 读写专用第三方库支持.xlsx 格式新建、写入、追加数据Python 内置 csv标准库无需额外安装轻量化 CSV 文件读写threadingPython 内置多线程标准库实现 IO 密集型爬虫并发抓取hashlib内置哈希算法库用于字段加密生成指纹实现数据去重一、爬虫轻量化文件存储方案选型与原理对比1.1 CSV 与 Excel 存储特性对照表表格存储格式依赖条件优势适用场景短板CSVPython 原生 csv 模块零安装体积小巧、读写速度快、全平台软件兼容、占用资源极低海量明细数据落地、定时采集日志归档、接口批量数据存储不支持复杂单元格样式、无法插入图片与多工作表、纯文本结构XLSX(Excel)openpyxl 第三方库多工作表分类存储、单元格格式自定义、便于人工整理统计小体量样本数据、结果汇总报表、学习演示数据导出大批量数据写入内存占用偏高十万级以上数据性能弱于 CSV1.2 CSV 通用读写工具类开发CSV 依托内置模块实现分为文件新建表头、单行追加写入、批量多行写入三种常用逻辑工具类可全项目复用。python运行import csv import os from spider_log import spider_log class CsvSaveUtil: def __init__(self, file_path, header_list): self.file_path file_path self.header header_list # 判断文件是否存在不存在则写入表头 if not os.path.exists(self.file_path): self.write_header() def write_header(self): 写入CSV表头信息 try: with open(self.file_path, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow(self.header) spider_log.info(fCSV文件{self.file_path}表头创建成功) except Exception as e: spider_log.error(f表头写入失败:{str(e)}) def write_row(self, data_row): 单行数据追加写入 try: with open(self.file_path, a, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow(data_row) except Exception as e: spider_log.error(f单行写入异常:{str(e)}) def write_rows(self, data_list): 批量多行数据写入 try: with open(self.file_path, a, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerows(data_list) spider_log.info(f批量{len(data_list)}条数据写入CSV完成) except Exception as e: spider_log.error(f批量写入异常:{str(e)})1.3 CSV 工具原理说明encodingutf-8-sig规避 Windows 系统 Excel 打开 CSV 出现中文乱码问题区别于普通 utf-8 编码newline消除不同操作系统写入 CSV 时自动多出空行的问题文件存在性判断逻辑首次创建文件写入表头后续采集仅追加内容避免重复覆盖表头。1.4 技术社区爬虫接入 CSV 存储改造示例python运行# 在爬虫初始化中定义CSV配置 def __init__(self): self.headers {User-Agent: ua_tool.get_random_ua()} self.result_list [] header [帖子标题,作者,发布时间,阅读量,帖子链接] self.csv_util CsvSaveUtil(tech_post.csv, header) # 采集结束后批量写入 def save_to_file(self): data_arr [] for item in self.result_list: row [item[title],item[author],item[publish_time],item[view_count],item[url]] data_arr.append(row) self.csv_util.write_rows(data_arr)1.5 Excel 通用写入工具类实现bash运行pip install openpyxlpython运行from openpyxl import Workbook, load_workbook import os from spider_log import spider_log class ExcelSaveUtil: def __init__(self, file_path, sheet_namedata, headerNone): self.file_path file_path self.sheet_name sheet_name self.header header if os.path.exists(self.file_path): self.wb load_workbook(self.file_path) if self.sheet_name in self.wb.sheetnames: self.ws self.wb[self.sheet_name] else: self.ws self.wb.create_sheet(self.sheet_name) if self.header: self.ws.append(self.header) else: self.wb Workbook() self.ws self.wb.active self.ws.title self.sheet_name if self.header: self.ws.append(self.header) self.wb.save(self.file_path) def append_row(self, row_data): 单行追加数据 self.ws.append(row_data) self.wb.save(self.file_path) def append_batch(self, batch_data): 批量追加多行 for row in batch_data: self.ws.append(row) self.wb.save(self.file_path) spider_log.info(fExcel批量写入{len(batch_data)}条数据)1.6 Excel 存储核心原理Workbook代表整个 Excel 文件对象worksheet对应表格内工作表一个 Excel 可创建多张 sheet 分类存储不同维度数据load_workbook用于读取已存在的 Excel 文件避免每次写入覆盖原有全部数据每次写入后执行 save 落地磁盘防止程序异常退出导致数据丢失。二、基于 threading 的 IO 密集型多线程爬虫开发2.1 爬虫选用多线程的底层逻辑爬虫绝大多数运行时间消耗在网络请求 IO 等待阶段CPU 空闲闲置Python 多线程受 GIL 全局解释器锁限制不适合 CPU 密集运算但完美适配 IO 密集场景通过多线程并行发起网络请求把串行等待时间转为并发执行采集效率提升 3~8 倍。爬虫禁止无限制创建线程海量任务需搭配线程池控制并发上限避免瞬间海量请求触发反爬。2.2 简易线程池封装控制并发数量python运行import threading from queue import Queue from spider_log import spider_log class CrawlThreadPool: def __init__(self, max_thread5): self.max_thread max_thread self.task_queue Queue() self.result_queue Queue() self.thread_list [] def put_task(self, task_data): 存入待采集任务 self.task_queue.put(task_data) def thread_func(self, crawl_func): 子线程循环取任务执行 while not self.task_queue.empty(): try: task self.task_queue.get(timeout2) res crawl_func(task) if res: self.result_queue.put(res) self.task_queue.task_done() except Exception as e: spider_log.warning(f子线程任务异常:{str(e)}) def start_run(self, crawl_func): 启动线程池执行任务 for _ in range(self.max_thread): t threading.Thread(targetself.thread_func, args(crawl_func,)) t.daemon True t.start() self.thread_list.append(t) self.task_queue.join() spider_log.info(全部任务执行完毕)2.3 文库文摘爬虫接入多线程实战原有单条 URL 逐个抓取改造为批量 URL 入队、多线程并发抓取python运行# 待采集详情链接列表 url_list [ https://example.com/lib/1, https://example.com/lib/2, https://example.com/lib/3 ] def single_crawl_task(url): 单个任务抓取函数传入URL返回解析数据 spider LibrarySpider() html spider.get_page_html(url) data spider.parse_abstract(html) if data: spider.save_to_db(data, url) return data return None # 初始化线程池最大并发3个线程 pool CrawlThreadPool(max_thread3) for item_url in url_list: pool.put_task(item_url) pool.start_run(single_crawl_task)2.4 线程池关键参数配置参考表格站点风控等级最大并发线程数配套延时策略宽松公开 API (天气接口)5~8随机 0.3~1 秒延时资讯文库、百科站点3~5随机 1~2.5 秒延时风控严格技术社区1~2随机 2~4 秒延时三、数据去重实现URL 指纹 哈希字段双重去重方案重复采集分为两类同源 URL 重复抓取、同一标题内容重复生成采用 MD5 哈希生成数据指纹是工业级爬虫主流去重手段将字符串转为固定长度密文对比密文即可快速判定重复。3.1 MD5 指纹生成工具类python运行import hashlib class DataHashUtil: staticmethod def get_md5(content: str): 生成字符串MD5指纹 md5_obj hashlib.md5(content.encode(utf-8)) return md5_obj.hexdigest()3.2 三种落地去重方案3.2.1 内存集合临时去重单次运行去重爬虫单次运行中使用 set 集合存储已采集 URL 指纹请求前校验适合临时批量抓取python运行already_spider_set set() def check_url_duplicate(url): md5_str DataHashUtil.get_md5(url) if md5_str in already_spider_set: return True already_spider_set.add(md5_str) return False3.2.2 MySQL 数据库唯一索引永久去重推荐长期项目修改此前文库数据表对 source_url 增加唯一索引重复 URL 插入时数据库直接抛出异常捕获异常跳过入库sqlALTER TABLE library_abstract ADD UNIQUE INDEX idx_source_url(source_url);入库代码捕获唯一索引冲突异常python运行from pymysql.err import IntegrityError try: self.db_cursor.execute(sql, params) self.db_conn.commit() except IntegrityError: spider_log.info(f数据重复跳过入库:{data[title]}) self.db_conn.rollback()3.2.3 本地指纹文件持久化去重无数据库环境将所有 URL 指纹存入 txt 文件每次采集前读取指纹列表比对适合轻量化离线爬虫。3.3 标题内容去重补充部分站点 URL 不同但文摘内容高度一致提取标题 摘要拼接字符串生成 MD5实现内容维度去重。四、原有项目全量适配优化汇总表格项目名称新增改造项优化收益基础异常爬虫CSV 结果导出 内存去重测试数据快速落地表格避免重复测试采集开源文库入库爬虫Excel 备份 多线程并发 数据库唯一索引去重批量文摘抓取效率提升 4 倍彻底杜绝重复入库天气定时采集爬虫CSV 按日分文件存储每日天气数据单独归档便于按月统计分析百科词条爬虫多线程批量词条抓取 URL 指纹去重批量词条快速采集过滤重复词条链接技术社区爬虫ExcelCSV 双备份 分页去重多页采集自动过滤重复帖子双格式留存数据五、开发拓展方向与并发合规提示5.1 后续拓展方向引入 ThreadPoolExecutor 内置线程池替代原生 threading简化任务调度代码使用 Redis 集合持久化存储指纹实现爬虫重启后依然保留历史去重记录基于 pandas 实现 CSV、Excel 大数据清洗、筛选、格式转换。5.2 多线程合规提醒并发不等于无节制高频请求即便多线程 代理 IP仍需要依托随机延时控制整体请求速率多线程场景适当拉长单请求休眠时间规避站点短时间瞬时大量请求触发封禁所有并发采集仅针对完全公开无权限限制的网络资源。