前言
数据采集完成后,持久化存储是爬虫开发流程中不可或缺的关键环节。本地文本文件凭借调用简单、无需额外部署环境、兼容性极强、读写开销低等优势,成为爬虫入门阶段最常用的数据存储载体。无论是临时存储测试数据、日志记录,还是小规模结构化、非结构化数据归档,TXT 纯文本文件都能满足基础业务需求。
爬虫运行过程中会产生网页源码、解析后的文本内容、临时缓存数据、运行日志等各类信息,若仅将数据打印至控制台,程序终止后数据便会直接丢失,无法完成二次使用、数据分析与内容回溯。依托 Python 内置文件操作能力实现本地文本存储,无需引入复杂第三方组件,上手门槛低,同时能够快速完成数据落地。
本文围绕爬虫场景下的文本存储需求,系统讲解 Python 文件读写核心语法、不同数据格式适配方案、编码处理、大文件读写、追加写入、异常防护以及爬虫专属日志存储等实战内容,结合多组可直接运行的代码案例拆解底层运行逻辑,同时梳理文件操作过程中的常见问题与优化方案,构建完整的本地文本存储技术体系。
本文开发运行依赖工具与库文件地址如下:
- os(内置文件路径处理库):Python 标准库,无需额外安装
- time(内置时间处理库,用于日志时间戳):Python 标准库,无需额外安装
- requests(网络请求库,承接上一分页爬虫案例):https://pypi.org/project/requests/
- bs4(BeautifulSoup)(网页解析库):https://pypi.org/project/beautifulsoup4/
- lxml(HTML 解析引擎):https://pypi.org/project/lxml/
一、Python 文件操作基础体系
1.1 文件打开模式完整说明
Python 内置open()函数是实现本地文件读写的核心接口,该函数为语言原生功能,不需要安装第三方依赖,支持 Windows、Linux、macOS 全平台运行。文件操作的行为逻辑由打开模式决定,不同模式对应创建、读取、覆盖、追加、二进制读写等不同功能,结合爬虫实际使用场景,将常用打开模式整理如下表:
表格
| 打开模式 | 功能描述 | 文件不存在时行为 | 文件存在时行为 | 爬虫适用场景 |
|---|---|---|---|---|
| r | 只读模式,默认模式 | 抛出文件不存在异常 | 正常读取文件内容 | 读取本地缓存配置、历史采集数据 |
| w | 只写模式 | 自动创建空白新文件 | 清空原有全部内容,重新写入 | 单次采集、覆盖更新数据、存储网页源码 |
| a | 追加写入模式 | 自动创建空白新文件 | 在文件末尾续写内容,不改动原有数据 | 分页爬虫分批存数据、实时日志记录 |
| r+ | 读写模式 | 抛出文件不存在异常 | 可读可写,默认从文件头部开始写入 | 读取历史数据并补充新内容 |
| w+ | 读写模式 | 自动创建空白新文件 | 清空原有内容,支持读写操作 | 临时文件创建与读写测试 |
| a+ | 追加读写模式 | 自动创建空白新文件 | 可读可追加写入,写入始终在末尾 | 日志文件读取与续写、动态数据缓存 |
| b | 二进制模式(可与上述模式组合) | 遵循对应基础模式规则 | 遵循对应基础模式规则 | 存储网页二进制源码、图片二进制流 |
在爬虫项目中,r、w、a、a+四种模式使用频率最高。分页爬虫逐页采集数据、长时间运行的爬虫日志,优先选择a追加模式;单次完整采集、需要刷新旧数据的场景,选择w覆盖写入模式;读取本地配置文件、历史数据则使用r只读模式。
1.2 字符编码核心配置
中文乱码是爬虫文本存储最频发的问题,根源在于操作系统默认编码、网页编码、文件存储编码三者不统一。Windows 系统默认编码为gbk,Linux 与 macOS 系统默认编码为utf-8,而绝大多数中文网站页面编码为utf-8。
open()函数提供encoding参数手动指定文件编码,爬虫项目中统一设置 encoding="utf-8"是行业通用规范,能够彻底规避跨平台、跨页面带来的中文乱码问题。若未手动指定编码,程序会调用系统默认编码,在不同设备上运行时极易出现字符解析失败、内容变成乱码方块等故障。
1.3 文件操作标准语法与结构
Python 提供两种主流文件操作写法:基础语法写法与with上下文管理器写法。两种写法底层逻辑一致,但资源管理能力存在明显差异,也是爬虫项目选型的核心依据。
1.3.1 基础读写语法
基础语法分为三步:打开文件、执行读写操作、手动关闭文件。核心语法格式:
python
运行
# 打开文件 file_object = open(file_path, mode="模式", encoding="编码格式") # 读取/写入内容 file_object.read() / file_object.write("内容") # 关闭文件,释放系统资源 file_object.close()文件属于操作系统硬件资源,程序打开文件后会占用文件句柄,若不执行close()关闭操作,大量并发文件读写会造成句柄泄漏,严重时导致程序卡死、系统资源耗尽。该写法适用于简单测试场景,正式爬虫项目不推荐单独使用。
1.3.2 with 上下文管理器写法
with是 Python 专属的资源管理语法,也是爬虫、后端开发等工程场景的标准写法。其核心优势为:代码块执行完毕后,会自动调用 close () 方法关闭文件,无需手动管理资源,即便代码运行过程中出现异常、程序中断,文件资源依然可以正常释放,安全性与稳定性远高于基础写法。
标准语法格式:
python
运行
with open(file_path, mode="模式", encoding="utf-8") as file_object: # 读写逻辑,缩进范围内执行文件操作 file_object.write("写入内容") content = file_object.read() # 代码跳出缩进块,文件自动关闭下文所有实战案例,均统一采用with上下文管理器写法,贴合工业级爬虫开发规范。
二、基础文本写入实战案例
2.1 单次覆盖写入(w 模式)
2.1.1 场景应用分析
w只写模式的核心特征是文件存在则清空原有内容,文件不存在则新建文件。该模式适用于单次完整数据采集场景,例如单页爬虫、一次性数据爬取、网页源码临时存储、数据重置更新等。当重复运行爬虫程序时,旧数据会被新数据完全覆盖,保证文件内始终为最新采集结果。
2.1.2 完整代码实现
本案例承接前文单页爬虫逻辑,抓取网页标题、正文内容后,将结构化文本写入本地 TXT 文件,实现数据落地存储。
python
运行
import requests from bs4 import BeautifulSoup # 基础请求配置 HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" } # 目标采集地址与本地存储路径 TARGET_URL = "https://demo-static-page.com/list/1.html" SAVE_PATH = "./single_page_data.txt" def crawl_and_save(): # 发起网络请求 response = requests.get(TARGET_URL, headers=HEADERS, timeout=10) response.encoding = "utf-8" soup = BeautifulSoup(response.text, "lxml") # 解析页面数据 news_title = soup.find("h1", class_="main-title").get_text(strip=True) news_content = soup.find("div", class_="content").get_text(strip=True) # 拼接格式化文本内容,增加换行区分字段 save_text = f"文章标题:{news_title}\n文章正文:{news_content}\n" # w模式写入本地文本文件 with open(SAVE_PATH, mode="w", encoding="utf-8") as f: f.write(save_text) print(f"数据写入完成,文件保存路径:{SAVE_PATH}") if __name__ == "__main__": crawl_and_save()2.1.3 代码原理解析
路径规则原理代码中
./single_page_data.txt为相对路径,./代表当前程序运行所在目录,无需手动填写完整磁盘路径,具备跨设备迁移能力。爬虫项目中优先使用相对路径,避免绝对路径因设备文件夹结构不同导致文件创建失败。换行符 \n 原理
\n是通用换行转义字符,作用是在文本中强制换行。网页解析后的纯文本为连续字符串,直接写入文件会造成所有内容挤在同一行,可读性极差。通过\n分割标题、正文等不同字段,模拟人工排版格式,提升文本文件可读性。Windows 系统兼容\n换行符,无需额外使用\r\n。w 模式执行逻辑程序执行
open()函数时,操作系统会先检测SAVE_PATH对应的文件:若文件不存在,在指定路径创建全新空白 TXT 文件;若文件已存在,系统会先清空文件内部所有历史内容,再执行write()写入新数据。重复运行程序会持续覆盖旧数据,这也是该模式的核心特性。write () 方法限制
write()方法仅支持字符串类型数据写入,无法直接写入列表、字典、数字等 Python 复合数据类型。若需要存储非字符串数据,必须先通过字符串拼接、类型转换将数据转为文本格式,这也是文本文件存储的基础限制。
2.2 追加写入(a 模式)
2.2.1 场景应用分析
a追加模式是分页爬虫、多任务爬虫、日志存储的核心选择。该模式不会清空原有文件内容,所有新数据都会追加到文件末尾,完美适配分页爬虫逐页采集、分批存储的业务场景。分页爬虫每抓取一页数据,就向文件中追加一段内容,多轮循环完成后,所有分页数据完整保存在同一个文本文件中。
2.2.2 完整代码实现
基于上一章分页爬虫逻辑改造,实现多页数据逐行追加写入本地文本,模拟真实分页采集存储流程。
python
运行
import requests from bs4 import BeautifulSoup HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" } BASE_URL = "https://demo-static-page.com/list/" SAVE_PATH = "./multi_page_data.txt" # 定义分页范围 START_PAGE = 1 END_PAGE = 5 def parse_page(page_num): """解析单页数据,返回格式化字符串""" url = f"{BASE_URL}{page_num}.html" response = requests.get(url, headers=HEADERS, timeout=10) response.encoding = "utf-8" soup = BeautifulSoup(response.text, "lxml") item_list = soup.find_all("div", class_="news-item") page_text = f"========== 第{page_num}页数据 ==========\n" for index, item in enumerate(item_list, 1): title = item.find("h3").get_text(strip=True) time_info = item.find("span", class_="publish-time").get_text(strip=True) page_text += f"{index}. 标题:{title} | 发布时间:{time_info}\n" return page_text if __name__ == "__main__": for page in range(START_PAGE, END_PAGE + 1): print(f"正在采集并存储第 {page} 页数据") page_content = parse_page(page) # a模式追加写入,保留历史数据 with open(SAVE_PATH, mode="a", encoding="utf-8") as f: f.write(page_content) print("所有分页数据存储完成")2.2.3 代码原理解析
a 模式核心逻辑追加模式打开文件时,文件指针默认定位在文件末尾,所有
write()操作都会在原有内容之后续写,不会修改、删除历史数据。即便程序多次重启、分段运行,新数据依然持续追加,保证分页数据连续性。分页循环与数据拼接原理外层
for循环遍历所有页码,每一页单独解析数据并拼接为格式化字符串,再执行一次文件写入操作。该逻辑将分页采集与文本存储深度结合,是分页爬虫标准落地方案。代码中增加分页分割标识========== 第{page_num}页数据 ==========,用于区分不同页码的数据,便于后期人工查阅与数据筛选。重复运行测试逻辑多次执行本代码,文件内的数据会持续累加,不会出现覆盖现象。该特性对于长时间运行的爬虫、断点续爬场景十分友好,也是日志文件选择
a模式的核心原因。
2.3 读写组合模式(a+)
2.3.1 场景应用分析
a+模式同时具备读取内容和追加写入能力,文件不存在时自动创建,文件存在时可读可写,写入操作依旧固定在文件末尾。该模式适用于需要读取历史数据进行校验,再补充新数据的场景,例如爬虫数据去重、读取本地缓存配置、比对新旧数据差异等。
2.3.2 完整代码实现
读取已有文本文件中的历史数据,判断新采集内容是否重复,无重复则追加写入,实现简单的本地去重逻辑。
python
运行
import requests from bs4 import BeautifulSoup HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" } TARGET_URL = "https://demo-static-page.com/list/1.html" SAVE_PATH = "./data_check.txt" def get_exist_content(): """读取文件中已存在的所有内容""" try: with open(SAVE_PATH, mode="r", encoding="utf-8") as f: return f.read() except FileNotFoundError: # 文件不存在返回空字符串 return "" if __name__ == "__main__": # 采集新数据 res = requests.get(TARGET_URL, headers=HEADERS, timeout=10) res.encoding = "utf-8" soup = BeautifulSoup(res.text, "lxml") new_title = soup.find("h3").get_text(strip=True) new_text = f"最新采集标题:{new_title}\n" # 读取历史数据并比对 old_content = get_exist_content() if new_title not in old_content: with open(SAVE_PATH, mode="a+", encoding="utf-8") as f: f.write(new_text) print("数据无重复,已成功追加写入") else: print("数据已存在,跳过写入操作")2.3.3 代码原理解析
异常捕获 FileNotFoundError使用
r只读模式读取文件时,若文件尚未创建,Python 会抛出FileNotFoundError异常。代码中通过try...except捕获该异常,当文件不存在时直接返回空字符串,保证程序正常运行,无需人工提前创建文件。a+ 模式指针规则
a+模式下,文件读取指针默认在文件头部,可正常读取全部内容;写入指针固定在文件末尾,无论读取到哪个位置,执行write()都会在文末追加内容,这是该模式区别于r+的核心特征。本地简易去重原理将历史文件内容读取为完整字符串,利用字符串成员判断
new_title not in old_content检测数据重复,适用于小规模数据校验。该方案逻辑简单、无需额外组件,适合入门级爬虫使用。
三、复合数据类型存储方案
爬虫解析后的数据大多为列表、字典等复合类型,无法直接使用write()写入文本文件,本节针对不同复合数据类型提供专属转换与存储方案。
3.1 列表数据格式化存储
列表是爬虫批量存储数据最常用的容器,内部通常存储多条文本数据。核心思路为:遍历列表,逐条拼接字符串后写入文件。
3.1.1 代码实现
python
运行
# 模拟爬虫采集得到的列表数据 data_list = ["Python爬虫入门", "分页抓取技巧", "文本数据存储", "CSV持久化方案"] SAVE_PATH = "./list_data.txt" with open(SAVE_PATH, mode="a", encoding="utf-8") as f: for index, text in enumerate(data_list, 1): line = f"{index}. {text}\n" f.write(line) print("列表数据存储完成")3.1.2 原理解析
通过for循环遍历列表每一个元素,将序号与元素内容拼接为标准字符串,配合换行符分行写入,让列表数据在文本文件中结构化展示。该方案通用性极强,支持任意长度的一维列表存储。
3.2 字典数据格式化存储
字典用于存储单条数据的多字段信息,例如标题、作者、时间、内容等键值对数据。存储思路分为两种:手动拼接键值对、使用str()强制类型转换。
3.2.1 手动拼接键值对(推荐,可读性高)
python
运行
# 模拟单条字典数据 news_dict = { "title": "爬虫文本存储教程", "author": "技术博主", "publish_time": "2026-06-11", "content": "本地TXT文件是爬虫基础存储方案" } SAVE_PATH = "./dict_data.txt" # 拼接键值对字符串 save_str = "" for key, value in news_dict.items(): save_str += f"{key}:{value}\n" with open(SAVE_PATH, mode="a", encoding="utf-8") as f: f.write(save_str + "-------------------------\n") print("字典数据存储完成")3.2.2 原理解析
调用字典内置items()方法遍历所有键值对,将字段名与字段值拼接为人类可读的文本格式。增加分割线区分多条字典数据,适合多条结构化字典数据批量存储。该方式的优势是文本格式清晰,后期人工查看、二次提取数据难度低。
3.3 嵌套数据存储(列表嵌套字典)
真实爬虫项目中,列表嵌套字典是最主流的数据结构:列表存储多条数据,每个元素为字典,对应单条数据的多个字段。结合前文分页爬虫场景,实现该类复杂数据的文本存储。
3.3.1 完整代码实现
python
运行
import requests from bs4 import BeautifulSoup HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" } BASE_URL = "https://demo-static-page.com/list/1.html" SAVE_PATH = "./nest_data.txt" def get_nest_data(): res = requests.get(BASE_URL, headers=HEADERS, timeout=10) res.encoding = "utf-8" soup = BeautifulSoup(res.text, "lxml") item_list = soup.find_all("div", class_="news-item") result = [] for item in item_list: data = { "title": item.find("h3").get_text(strip=True), "time": item.find("span", class_="publish-time").get_text(strip=True) } result.append(data) return result if __name__ == "__main__": total_data = get_nest_data() with open(SAVE_PATH, mode="a", encoding="utf-8") as f: for idx, data_dict in enumerate(total_data, 1): f.write(f"第{idx}条数据:\n") for k, v in data_dict.items(): f.write(f" {k}:{v}\n") f.write("-------------------------\n") print("嵌套数据存储完成")3.3.2 原理解析
采用双层循环处理嵌套结构:外层循环遍历列表中的每一条字典数据,内层循环遍历字典的键值对。通过缩进、序号、分割线优化文本排版,让复杂的嵌套数据在 TXT 文件中层次分明。该逻辑可直接复用到绝大多数常规爬虫项目中。
四、文件路径高级处理(os 库)
在复杂爬虫项目中,手动书写文件路径容易出现路径错误、文件夹不存在等问题,Python 内置os库专门用于操作系统文件、文件夹与路径,是爬虫路径管理的必备工具。
4.1 自动创建文件夹
爬虫通常会按分类创建多个文件夹,例如按日期分文件夹存储每日采集数据。若目标文件夹不存在,直接写入文件会抛出路径异常,os.makedirs()可自动创建多级文件夹。
4.1.1 代码实现
python
运行
import os # 定义文件夹路径与文件完整路径 folder_path = "./crawl_data/20260611" file_path = os.path.join(folder_path, "today_data.txt") # 判断文件夹是否存在,不存在则创建 if not os.path.exists(folder_path): os.makedirs(folder_path) print("文件夹创建成功") # 写入数据 with open(file_path, mode="a", encoding="utf-8") as f: f.write("今日爬虫采集数据\n") print("数据写入完成")4.1.2 原理解析
os.path.exists():检测指定路径是否存在,返回布尔值,可判断文件夹或文件。os.makedirs():创建多级目录,支持嵌套文件夹创建,区别于只能创建单级目录的os.mkdir()。os.path.join():智能拼接路径,自动适配 Windows、Linux 不同系统的路径分隔符,是跨平台路径拼接标准写法。
4.2 动态生成文件名
结合时间戳动态生成文件名,实现每日、每小时自动生成独立文本文件,避免文件覆盖。
4.2.1 代码实现
python
运行
import os import time folder_path = "./log_data" if not os.path.exists(folder_path): os.makedirs(folder_path) # 获取当前时间作为文件名 time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime()) file_name = f"crawl_log_{time_str}.txt" file_path = os.path.join(folder_path, file_name) with open(file_path, mode="w", encoding="utf-8") as f: f.write(f"爬虫启动时间:{time_str}\n程序运行正常\n") print(f"动态文件创建完成,路径:{file_path}")4.2.2 原理解析
time.strftime()将系统本地时间格式化为指定字符串,利用时间唯一性保证文件名不重复。该方案广泛应用于爬虫日志文件、分时数据存储场景。
五、爬虫日志专属文本存储
日志是爬虫运维、故障排查的核心依据,将爬虫运行状态、请求结果、异常信息存入文本日志文件,是工程化爬虫的标准配置。日志文件统一使用a追加模式,保证运行记录完整留存。
5.1 基础日志存储代码
python
运行
import time import requests HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" } LOG_PATH = "./crawl_log.txt" TEST_URL = "https://demo-static-page.com/list/1.html" def write_log(log_content): """通用日志写入函数""" now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) log_text = f"[{now_time}] {log_content}\n" with open(LOG_PATH, mode="a", encoding="utf-8") as f: f.write(log_text) if __name__ == "__main__": write_log("爬虫程序启动") try: res = requests.get(TEST_URL, headers=HEADERS, timeout=10) write_log(f"请求目标地址:{TEST_URL},响应状态码:{res.status_code}") write_log("数据采集成功") except Exception as e: write_log(f"请求异常,错误信息:{str(e)}") write_log("爬虫程序结束")5.2 日志原理解析
封装独立的write_log函数统一管理日志写入逻辑,每条日志前置时间戳,精准记录事件发生时间。日志内容包含程序状态、请求地址、状态码、异常信息,当爬虫运行出错时,可直接通过日志文件定位问题根源。日志文件永久使用a追加模式,确保全量运行记录不会丢失。
六、文本存储常见问题与优化方案
6.1 中文乱码问题
问题现象:写入中文后,文件内显示问号、乱码字符。根本原因:未指定encoding="utf-8",使用系统默认编码。解决方案:所有open()函数强制添加encoding="utf-8"参数,全项目统一编码标准。
6.2 文件句柄占用与性能问题
问题现象:高频率循环读写文件,程序运行缓慢、卡顿。原因分析:循环内反复打开、关闭文件,频繁操作系统 IO,产生性能损耗。优化方案:将文件打开操作移至循环外部,单次打开文件,循环内持续写入,循环结束后自动关闭。
优化示例:
python
运行
data_list = ["数据1", "数据2", "数据3"] # 单次打开文件 with open("./optimize.txt", mode="a", encoding="utf-8") as f: for data in data_list: f.write(data + "\n")6.3 大文件读取卡顿
问题现象:TXT 文件达到数百 MB 后,使用read()一次性读取全部内容,内存占用过高。解决方案:使用readline()逐行读取,或分块读取,降低内存压力。
6.4 路径不存在报错
问题现象:指定文件夹未手动创建,程序运行直接报错。解决方案:结合os库,在文件写入前自动检测并创建文件夹,实现全自动运行。