如何將 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
中,然后開始探索、清洗和可視化這些數據吧。你已經為你的下一次數據冒險做好了充分的準備。