如何将 Python 爬取的数据保存为 CSV 文件
从原始网络数据到纯净 CSV - 搭建通往分析的桥梁
恭喜你!经过前面的努力,你的 Python 脚本终于成功地从一个网站上爬取了数据,一个充满信息的宝库正静静地躺在你的变量中。但接下来呢?你该如何保存这些珍贵的信息,使其变得有用、可共享,并为下一步的分析做好准备?
这时,CSV (Comma-Separated Values,逗号分隔值) 文件就如同英雄般登场了。在本教程中,我们将深入探讨 CSV 格式的重要性。它是一种简单、人类可读且通用兼容的格式,被誉为数据世界的"通用语" 。无论是 Microsoft Excel、Google Sheets、各种数据库,还是最重要的------其他数据分析工具,都能够轻松地打开和处理它 。对于数据科学家和分析师来说,将网络爬取的数据、API 响应或任何结构化信息保存为 CSV 格式,是数据处理流程中的一个基础且关键的步骤。
在本指南中,我们将探索两条将数据写入 CSV 的主要路径:
-
内置工具包 :Python 标准库中的
csv
模块------它轻量、无需额外安装,非常适合处理简单的任务 。 -
数据科学家的"瑞士军刀" :
pandas
库------这是任何严肃的数据处理与分析工作流中的行业标准 。
这篇详尽的教程将引导你完成从建立一个网络爬虫项目,到使用上述两种方法保存数据,再到处理高级选项以及解决最常见错误的整个过程。读完本文,你将不仅知道"如何做",更会理解"为什么这么做",从而自信地将任何结构化数据转化为整洁、可用的 CSV 文件。
奠定基础 - 一个实用的网络爬虫项目
在学习如何保存数据之前,我们首先需要获取一些数据。本节将通过一个具体的实战项目来奠定基础:从 books.toscrape.com
网站上爬取书籍信息。这是一个专门为教学目的而设计的"沙箱"环境,可以让我们安全合法地练习网络爬虫技术 。这使得接下来的教程内容都基于实际操作,而非空泛的理论。
我们的目标是为网站上的每本书提取四个关键信息:书名 (Title) 、价格 (Price) 、星级评分 (Star Rating) 和 库存状态 (Availability)。
审查源代码(获取"蓝图")
任何网络爬虫项目的第一步都是了解目标网页的结构。使用现代浏览器的开发者工具(通常通过右键点击页面并选择"检查"来打开)是这项工作的核心技能 。通过检查 HTML 源代码,我们可以找到包含目标数据的特定标签和类名。
在 books.toscrape.com
上,我们发现:
-
每一本书的所有信息都被包裹在一个
<article class="product_pod">
标签内。 -
书名位于
<h3>
标签内部的一个<a>
标签的title
属性中 。 -
价格在一个
<p class="price_color">
标签内 。 -
星级评分是
<p>
标签的一个类名,例如<p class="star-rating Three">
,这里的 "Three" 就是我们需要的评分信息 。
爬虫脚本(附带注释的代码)
下面的 Python 脚本使用了两个核心库:requests
库用于向网站发送 HTTP 请求并获取网页的 HTML 内容,而 BeautifulSoup
库则用于解析这些 HTML,让我们能够轻松地提取所需信息 。
Python
import requests
from bs4 import BeautifulSoup
# 目标 URL
url = 'http://books.toscrape.com/'
# 发送请求并获取网页内容
response = requests.get(url)
# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(response.content, 'html.parser')
# 找到所有书籍的容器
books = soup.find_all('article', class_='product_pod')
# 准备一个列表来存储所有书籍的数据
all_books_data =
for book in books:
# 提取书名
title = book.h3.a['title']
# 提取价格
price = book.find('p', class_='price_color').text
# 提取星级评分 (例如 'star-rating Three' -> 'Three')
rating = book.p['class']
# 将提取的数据存入一个字典
book_data = {
'title': title,
'price': price,
'rating': rating
}
# 将这本数的数据字典添加到总列表中
all_books_data.append(book_data)
# 打印第一本书的数据以验证
print(all_books_data)
结构化数据(关键步骤)
请特别注意我们是如何组织爬取到的数据的。我们将数据整理成了一个字典的列表 (a list of dictionaries) 。列表中的每一个字典都代表一本书(也就是我们未来 CSV 文件中的一行),字典的键(如 'title'
, 'price'
)则对应着列名。
选择这种数据结构并非偶然,而是一个深思熟虑的设计决策,它极大地简化了后续的数据保存工作。这种结构是"自描述"的------键本身就说明了值的含义,这使得代码更具可读性。更重要的是,它与我们接下来要学习的两种最高效、最可靠的数据保存方法------csv.DictWriter
和 pandas.DataFrame
------完美契合 。
这个决策的逻辑流程如下:
-
爬虫为每本书分别提取了多个独立的数据点(书名、价格等)。
-
我们需要将属于同一本书的数据点组织在一起。字典是 Python 中实现这一目标的自然选择,它使用描述性的键(如
'title'
)来标记每个值。 -
我们爬取了多本书,因此需要一个容器来存放这些书的字典。列表是容纳这些字典集合的理想结构。
-
最终形成的
list[dict]
格式之所以理想,是因为csv
模块中的DictWriter
就是专门为处理这种格式而设计的 ,而pandas
库的DataFrame
构造函数可以直接接收这种格式,并智能地从字典的键中推断出列名 。
通过在爬取阶段就采用这种结构,我们为后续的数据保存铺平了道路,避免了手动管理表头或进行复杂的数据重组,使整个流程更简单、更不易出错。
标准库方法 - 使用 Python 的 csv
模块
现在我们有了一份干净、结构化的数据,是时候将它保存到文件中了。我们将从 Python 的内置 csv
模块开始。它的最大优点是无需安装任何第三方库(它已经包含在 Python 中),非常适合编写轻量级的脚本 。
使用 csv.writer
写入(列表的列表方法)
csv.writer
对象是 csv
模块的基础写入工具。它被设计用来处理序列数据,最常见的形式是一个"列表的列表"(a list of lists),其中每个内部列表代表 CSV 文件中的一行 。
要使用 csv.writer
,我们需要先将我们的"字典列表"转换成"列表的列表"。这个额外的步骤恰好凸显了稍后将介绍的 csv.DictWriter
的便利性。
深入剖析 open()
函数及其关键参数
在写入文件之前,我们必须先用 open()
函数打开一个文件。这里的参数设置至关重要,尤其是对于初学者而言,很多常见的错误都源于此。
-
上下文管理器
with open(...)
:强烈推荐始终使用with open(...) as f:
语法。这种结构被称为上下文管理器,它能确保在代码块执行完毕后自动关闭文件,即使在发生错误时也是如此。这是一种最佳实践,可以有效防止资源泄露和数据损坏 。 -
文件模式
mode
:-
'w'
(write):写入模式。如果文件已存在,它会清空文件内容后重新写入。如果文件不存在,则会创建新文件。 -
'a'
(append):追加模式。它不会清空文件,而是在文件末尾添加新的数据行。这在需要分批次向同一个文件写入数据时非常有用 。
-
-
揭开
newline=''
的神秘面纱:这是初学者最容易遇到的陷阱之一。-
问题根源 :不同的操作系统使用不同的字符组合来表示一行的结束。例如,Windows 使用回车符和换行符 (
\r\n
),而 Linux 和 macOS 只使用换行符 (\n
) 。 -
内在冲突 :当 Python 以文本模式 (
'w'
) 写入文件时,它会默认进行一种"通用换行符"翻译,即将代码中的\n
转换为当前操作系统的标准行尾符 。然而,csv
模块为了确保跨平台兼容性,它自己也会处理行尾符,默认写入\r\n
。 -
后果 :在 Windows 系统上,这两套换行机制会同时生效。Python 将
\n
转换成\r\n
,csv
模块再写入自己的\r\n
。这导致了多余的回车符,最终在你的 CSV 文件中每行数据之间都出现一个烦人的空行 。 -
解决方案 :在
open()
函数中指定newline=''
。这个参数告诉 Python 的文件处理器:"请不要进行任何换行符翻译,将这个任务完全交给csv
模块,它知道该怎么做。" 这是一个简单而关键的修复,可以确保生成格式正确的 CSV 文件 。
-
-
encoding='utf-8'
的必要性 :从网络上爬取的数据可能包含各种语言的字符和特殊符号(例如é
,€
)。utf-8
是一种能够表示世界上几乎所有字符的现代编码标准。在打开文件时明确指定encoding='utf-8'
可以有效防止UnicodeEncodeError
错误,保证数据的完整性和正确性 。
整合实践:writerow()
与 writerows()
现在,我们将以上知识点整合起来,使用 csv.writer
保存我们的书籍数据。
Python
import csv
# 这是我们从第一部分爬取到的数据
all_books_data =
# 定义表头
headers = ['title', 'price', 'rating']
# 将字典列表转换为列表的列表
data_for_writer =
for book in all_books_data:
data_for_writer.append([book['title'], book['price'], book['rating']])
# 打开文件进行写入,注意 newline='' 和 encoding='utf-8'
with open('books_writer.csv', 'w', newline='', encoding='utf-8') as f:
# 创建一个 writer 对象
writer = csv.writer(f)
# 1. 手动写入表头
writer.writerow(headers)
# 2. 逐行写入数据 (方法一:使用 writerow)
# for row in data_for_writer:
# writer.writerow(row)
# 3. 一次性写入所有数据 (方法二:使用 writerows,更高效)
writer.writerows(data_for_writer)
print("使用 csv.writer 保存数据成功!")
writerow()
方法接收一个列表,并将其作为一行写入文件。writerows()
则更进一步,它接收一个列表的列表,并一次性将所有行写入文件,通常效率更高 。
更 Pythonic 的方式:csv.DictWriter
当你处理的数据已经是字典列表时(就像我们爬取的结果),csv.DictWriter
是一个更优越、更健壮、可读性更强的选择 。
DictWriter
的主要优势在于:
-
直接处理字典 :它直接使用我们的
all_books_data
,无需进行任何数据格式转换。 -
明确的列顺序 :通过
fieldnames
参数,你可以精确地控制 CSV 文件中列的顺序。这避免了因字典在旧版 Python 中无序而导致的列顺序混乱问题,并且允许你只选择部分列进行写入 。 -
自动写入表头 :它拥有一个专门的
writeheader()
方法,可以根据fieldnames
列表自动写入表头行。这比手动创建和写入表头列表要简洁和安全得多 。
下面是使用 DictWriter
的完整代码示例:
Python
import csv
# 我们从第一部分爬取到的数据
all_books_data =
# 定义字段名(即列名),这决定了列的顺序
fieldnames = ['title', 'price', 'rating']
# 打开文件,同样注意 newline='' 和 encoding='utf-8'
with open('books_dictwriter.csv', 'w', newline='', encoding='utf-8') as f:
# 创建一个 DictWriter 对象,传入文件对象和字段名
writer = csv.DictWriter(f, fieldnames=fieldnames)
# 写入表头
writer.writeheader()
# 写入所有数据行
writer.writerows(all_books_data)
print("使用 csv.DictWriter 保存数据成功!")
通过对比,可以清晰地看到 DictWriter
的代码更简洁、意图更明确,是处理结构化字典数据时的首选。
csv.writer
与 csv.DictWriter
对比
为了帮助你快速选择合适的工具,下表总结了它们的核心区别:
特性 | csv.writer |
csv.DictWriter |
---|---|---|
输入数据格式 | 列表的列表/元组 (List of lists/tuples) | 字典的列表 (List of dictionaries) |
表头处理 | 必须手动使用 writerow() 写入 |
使用 writeheader() 自动写入 |
列顺序 | 由内部列表的元素顺序隐式决定 | 由 fieldnames 参数显式控制 |
最佳适用场景 | 处理简单的、顺序固定的序列数据 | 处理自描述的、键值对形式的数据(如网络爬虫或 API 返回的结果),追求代码的健壮性和可读性 |
Export to Sheets
数据科学家的选择 - 使用 pandas
库
虽然 csv
模块功能强大且方便,但当你的工作流涉及到数据分析时,pandas
库无疑是更佳的选择。pandas
是 Python 数据分析生态的基石 。如果说
csv
模块是一个文件读写工具,那么 pandas
则是一个完整的车间,涵盖了从读取、清洗、转换、分析到最终保存的整个数据生命周期。将数据保存为 CSV 只是其强大功能的冰山一角。
从原始数据到强大的 DataFrame
pandas
的核心数据结构是 DataFrame
,你可以将其想象成一个带有标签(行索引和列名)的二维表格,类似于电子表格或数据库中的表 。
将我们之前爬取的字典列表转换为 DataFrame
非常简单,甚至可以说是"一行魔法":
Python
import pandas as pd
# 我们从第一部分爬取到的数据
all_books_data =
# 使用一行代码创建 DataFrame
df = pd.DataFrame(all_books_data)
# 打印 DataFrame 的前几行以查看结果
print(df.head())
pandas
非常智能,它会自动接收字典列表,将字典的键作为 DataFrame
的列名,将值作为对应的数据行 。如果某些字典缺少某个键,
pandas
会在相应的位置自动填充为 NaN
(Not a Number),表示缺失值。这个功能对于处理不规整的爬虫数据来说极为方便。
使用 to_csv()
导出 - 一行代码的奇迹
一旦数据被加载到 DataFrame
中,将其保存为 CSV 文件同样只需要一行代码:
Python
# 将 DataFrame 保存为 CSV 文件
df.to_csv('books_pandas.csv')
虽然这行代码已经能工作,但 to_csv()
方法提供了许多强大的参数,可以让我们对输出文件进行精细控制。
深入剖析 to_csv()
的核心参数
-
index=False
:这是to_csv()
中最常用也最重要的参数之一。默认情况下,pandas
会将DataFrame
的行索引(即每行开头的 0, 1, 2...)作为一列写入 CSV 文件。在大多数情况下,这个索引是多余的,我们并不需要它。设置index=False
可以阻止写入这个索引列,从而得到一个更干净的输出文件 。 -
header=True/False
:这个参数控制是否将列名作为表头写入文件。默认值为True
,通常我们都会保留它 。 -
sep=','
:sep
代表分隔符 (separator)。默认是逗号','
。如果你的数据本身包含逗号(例如,一段描述性文本),你可能希望使用其他分隔符,如制表符\t
或竖线|
。例如,df.to_csv('output.tsv', sep='\t')
。 -
mode='w'/'a'
:与open()
函数中的模式参数完全相同,'w'
用于覆盖写入,'a'
用于在文件末尾追加 。 -
encoding='utf-8'
:同样,为了确保所有特殊字符都能被正确保存,强烈建议始终明确指定编码 。
专业技巧:为 Excel 用户解决乱码问题 - encoding='utf-8-sig'
这是一个非常常见且令人沮丧的场景:你用 pandas
精心创建了一个包含特殊字符(如中文、é
或 €
)的 CSV 文件,通过邮件发送给同事。同事用 Microsoft Excel 打开后,看到的却是一堆乱码,比如 䏿--‡
或 é
。
这个问题根源在于一个叫做字节顺序标记 (Byte Order Mark, BOM) 的东西。BOM 是一个位于文件开头的不可见特殊字符 (\ufeff
),它的作用就像一个"签名",用来告诉程序这个文件使用的是哪种 Unicode 编码 。
Excel 的"怪癖":许多程序,尤其是 Windows 平台上的 Microsoft Excel,在打开文件时行为有些"懒惰"。如果文件开头没有这个 BOM 签名,Excel 就不会主动将其识别为 UTF-8 编码,而是会退回到使用一种旧的、本地化的编码(例如中文环境下的 GBK 或欧美环境下的 cp1252)来解析文件,从而导致乱码 。
解决方案 :使用 encoding='utf-8-sig'
。这个特殊的编码名称告诉 pandas
,在以标准的 UTF-8 格式写入数据之前,先在文件开头加上那个 Excel 需要的 BOM 签名。这样,无论文件内容多么复杂,Excel 都能正确识别并显示它,从而为你和你的同事省去无数麻烦 。
掌握这一点不仅仅是了解一个技术细节,更是学习数据互操作性的重要一课。数据不仅仅是为了脚本本身,更是为了人和其他工具的使用。理解像 Excel 这样流行工具的特性,并知道如何去适应它们,是任何数据从业者都应具备的宝贵实用技能。
Python
# 使用 index=False 和 encoding='utf-8-sig' 来创建最兼容的 CSV 文件
df.to_csv('books_pandas_final.csv', index=False, encoding='utf-8-sig')
print("使用 pandas 保存数据成功,并优化了 Excel 兼容性!")
pandas.DataFrame.to_csv()
关键参数速查表
下表为你整理了 to_csv()
最常用的几个参数,方便你快速查阅。
参数 | 描述 | 常用设置 |
---|---|---|
path_or_buf |
文件路径和名称,例如 'books.csv' 。 |
总是需要提供的第一个参数。 |
index |
是否将 DataFrame 的行索引写入文件。 | index=False (几乎总是使用)。 |
header |
是否将列名作为表头写入文件。 | header=True (默认值,通常保留)。 |
sep |
用作字段分隔的字符。 | sep=',' (默认),或 sep='\t' (制表符)。 |
mode |
文件写入模式:'w' 覆盖,'a' 追加。 |
mode='a' (当向已存在的文件添加数据时)。 |
encoding |
指定文件编码。 | encoding='utf-8-sig' (为获得最佳 Excel 兼容性)。 |
Export to Sheets
解决常见"噩梦" - 错误排查指南
在编程中,遇到错误是常态。错误不是失败的标志,而是指引你走向正确方向的路牌。学会阅读和理解错误信息,是一项能让你事半功倍的"超能力"。本节将把初学者最常遇到的几个令人头疼的错误,转化为宝贵的学习机会。
揭秘 FileNotFoundError: [Errno 2] No such file or directory
-
症状:程序崩溃,并提示"没有那个文件或目录"。
-
核心问题:Python 在错误的地方寻找你指定的文件或目录 。
-
排查清单:
-
相对路径 vs. 绝对路径 :首先要理解两者的区别。绝对路径 是一个完整的、从根目录开始的路径(例如
C:\Users\YourUser\Documents\data.csv
或/home/user/data.csv
),它清晰明确。而相对路径 (例如data.csv
或output/data.csv
)是相对于"当前工作目录"(Current Working Directory, CWD)的路径 。 -
什么是当前工作目录 (CWD)? CWD 是你的 Python 脚本"认为"自己正在运行的位置。这个位置会根据你启动脚本的方式而变化。例如,直接在终端中运行
python my_script.py
和在 VS Code 或 PyCharm 中点击"运行"按钮,CWD 可能会不同 。你可以通过以下代码来检查当前的 CWD:Python
import os print(os.getcwd())
-
拼写错误和隐藏的扩展名 :文件名或路径中的一个微小拼写错误是常见原因。另外,Windows 系统默认会隐藏已知文件的扩展名,这会导致一个名为
data.csv
的文件实际上可能是data.csv.txt
,从而引发错误 。 -
路径分隔符 :Windows 使用反斜杠
\
作为路径分隔符,而 Linux 和 macOS 使用正斜杠/
。在 Python 字符串中,反斜杠是转义字符(例如\n
表示换行)。为了避免问题,最佳实践是:-
统一使用正斜杠
/
,它在所有主流操作系统上都能正常工作 。 -
或者使用 Python 的
pathlib
模块来构建路径,它会自动处理跨平台的兼容性问题。
-
-
解决 PermissionError: [Errno 13] Permission denied
-
症状:脚本失败,并提示"权限被拒绝"。
-
最常见的原因(对于初学者,99% 的情况是这个):你试图写入的 CSV 文件此刻正被另一个程序打开,最常见的就是 Microsoft Excel 。当 Excel 打开一个文件时,它会对其施加一个"写锁定",以防止其他程序修改它。Python 会尊重这个锁定,因此无法写入,从而引发权限错误。
-
简单的解决方案:在其他程序(如 Excel)中关闭该文件,然后重新运行你的 Python 脚本。
-
其他(较罕见的)原因 :你的脚本试图在一个受保护的系统目录(如
C:\Program Files
或/root
)中创建或写入文件,而当前用户没有这些目录的写入权限 。请确保你的输出路径位于你有权限写入的文件夹中,例如"我的文档"或项目文件夹。
处理 UnicodeEncodeError
-
症状 :脚本崩溃,并提示类似
'ascii' codec can't encode character '\u20ac' in position...
的错误。 -
原因 :当你试图写入包含非 ASCII 字符(如欧元符号
€
、重音字母é
或中文字符)的数据,但没有在open()
函数或to_csv()
方法中指定一个能够处理这些字符的编码时,就会发生此错误。在某些环境下,Python 的默认编码可能是ascii
,这是一个非常有限的编码集,无法表示这些特殊字符 。 -
解决方案 :这个错误再次提醒了我们前面章节的教训。解决方案很简单:始终 在你的文件操作中明确指定一个功能强大的编码。使用
encoding='utf-8'
或encoding='utf-8-sig'
即可解决问题。
常见 CSV 错误及其解决方案
这张速查表可以作为你的书签,当你遇到这些常见错误时,可以快速找到原因和解决方案。
错误信息/症状 | 可能的原因 | 解决方案 |
---|---|---|
FileNotFoundError |
文件路径不正确,或脚本的当前工作目录 (CWD) 不是你预期的目录。 | 使用绝对路径,或用 os.getcwd() 检查 CWD。仔细检查文件名和路径的拼写。 |
PermissionError |
CSV 文件正被另一个程序(通常是 Excel)打开并锁定。 | 在其他程序中关闭该文件,然后重新运行脚本。 |
UnicodeEncodeError |
试图写入特殊字符(如中文、€),但未指定正确的编码。 | 在 to_csv() 或 open() 调用中添加 encoding='utf-8-sig' (推荐) 或 encoding='utf-8' 。 |
CSV 文件中出现空行 | (使用 csv 模块时) 在 open() 函数中忘记了 newline='' 参数。 |
始终使用 with open(..., newline='') 来写入 CSV 文件。 |
Export to Sheets
选择你的路径,开启你的数据冒险
恭喜你!你已经掌握了一项数据处理领域的基础核心技能:将动态、原始的网络数据,转化为静态、规整、随时可用的 CSV 文件。
我们回顾一下所学的两种主要方法:
csv
模块 :适用于简单的、无外部依赖的脚本,当你只需要快速将数据"倾倒"到文件中时,它是一个不错的选择。特别是csv.DictWriter
,为处理字典数据提供了清晰的接口。pandas
库 :当你的工作流不仅仅是保存文件,还包括后续的数据清洗、分析或转换时,pandas
是必不可少的选择。它为整个数据科学生命周期提供了强大支持,是通往更广阔数据世界的门户。
对于初学者,一个明确的建议是:了解 csv
模块的存在是件好事,但你的学习精力应该主要投入到 pandas
上。虽然 pandas
的初始学习曲线可能稍显陡峭,但它在功能和灵活性上带来的巨大回报是无与伦比的。
现在,你的数据已经整洁地保存在 CSV 文件中,真正的乐趣才刚刚开始!尝试用 pandas.read_csv()
将它重新加载到一个新的 DataFrame
中,然后开始探索、清洗和可视化这些数据吧。你已经为你的下一次数据冒险做好了充分的准备。