:從文本讀寫到CSV/JSON處理(day13))
學了 12 天的 Python 基礎語法是時候做點“真事”了。前面我們把變量賦值、類型轉換、流程控制、函數(shù)這些零件都過了一遍但說實話只會跑終端 demo 的 Python還不算會寫程序。真正寫程序大多數(shù)時間都在跟數(shù)據(jù)打交道而數(shù)據(jù)大部分時候都躺在文件里。所以從今天這個 day13 開始我打算把重心從“語法點”轉向“真實任務”第一個要啃下的硬骨頭就是文件操作與數(shù)據(jù)處理。讀完這篇你能學會用 Python 優(yōu)雅地讀寫文本文件處理 CSV 成績單、JSON 配置文件還能寫一個日志篩選小工具——這些技能不管以后做爬蟲、數(shù)據(jù)分析還是 Web 開發(fā)全都用得上。1. 為什么第13天要開始死磕文件讀寫1.1 文件操作是整個 Python 生態(tài)的地基Python 現(xiàn)在最火的方向無外乎三個爬蟲、數(shù)據(jù)分析、Web 開發(fā)。你仔細想想這三個方向有個共同點——它們的本質都是在跟數(shù)據(jù)打交道而數(shù)據(jù)不可能永遠飄在內存里最終都要落盤到文件。爬蟲抓取下來的商品信息要存成 CSV 或 JSON數(shù)據(jù)分析師拿到手的原始數(shù)據(jù)大概率是 Excel 或 CSV 文件Web 服務跑起來之后日志要寫進文件配置要讀文件緩存可能也要臨時落到磁盤上。哪怕你只是寫個自動化腳本比如批量重命名文件、定時清理臨時目錄底層也全是文件操作。所以我一直跟初學者強調文件讀寫不是 Python 的一個“可選章節(jié)”而是地基中的地基。你可以不會寫裝飾器可以暫時看不懂閉包但如果你不會讀寫文件那幾乎任何一個真實項目都做不了。day13 選在這個時間點切入也正是因為你已經有了變量、函數(shù)、流程控制這些基礎正好可以把文件操作作為第一個“綜合性實戰(zhàn)主題”。1.2 一個主題串起前面 12 天所有知識文件操作這個主題奇妙的地方在于它幾乎能把前面 12 天學的所有東西都串起來。打開一個文件讀進來的數(shù)據(jù)默認是字符串想把“123”變成數(shù)字去做運算就用到類型轉換。文件里通常不止一行數(shù)據(jù)逐行處理后往往要用列表或字典來暫存要遍歷每一行就用到 for 循環(huán)要過濾掉異常數(shù)據(jù)就用 if 判斷為了避免代碼重復把處理邏輯抽成函數(shù)又很自然。所以你不是在學一個孤立的新語法而是在做一場“知識總動員”。這套課程從 day1 走到 day12一直有讀者反饋說“單獨每個語法都懂但一合起來就不會用了”。這很正常因為零散的語法點就像散落一地的樂高零件而文件操作就是你的第一張拼裝圖紙。當你寫出一段代碼先讀文件、再循環(huán)處理、最后寫回結果你會突然發(fā)現(xiàn)“原來前面的語法是這么配合工作的”這種“開竅”的感覺就是第 13 天最大的收獲。1.3 今天的學習地圖4 個實戰(zhàn)案例這一天內容不算多但每一個都值得動手敲一遍。我按難度從小到大給你排好了順序。open 函數(shù)和 with 語句的核心原理這是所有文件操作的底層基礎。文本文件的寫入與逐行讀取先解決“能不能讀寫”的問題。CSV 和 JSON 這兩種結構化數(shù)據(jù)的處理解決“數(shù)據(jù)怎么組織”的問題。一個日志篩選的小案例把前面所有東西融進一個真實場景。為什么要按這個順序因為文件操作天然是分層遞進的。你連文本讀寫都沒搞清楚直接去搞 CSV 會一頭霧水你連 CSV 的列都還不會處理直接去分析日志更會手足無措。今天這 4 個案例走完你會發(fā)現(xiàn)文件處理在你的腦子里不再是“一個 open 函數(shù)”而是一條完整的鏈路定位文件 → 打開文件 → 讀取/解析 → 處理數(shù)據(jù) → 寫回結果。2. 核心細節(jié)從 open() 到 with把文件讀寫吃透2.1 open() 函數(shù)的模式參數(shù)理解這 5 種就夠用Python 內置的 open() 函數(shù)是打開文件唯一需要掌握的門面。它的第一個參數(shù)是文件路徑第二個參數(shù)是打開模式 mode。很多初學者看到官方文檔里列的一大堆模式容易頭大其實日常工作里只要理解這 5 種模式作用注意點r只讀文件必須存在否則報 FileNotFoundErrorw寫入會先清空原文件內容不存在則新建a追加不清空原內容從文件末尾寫入rb / wb二進制讀/寫處理圖片、視頻、壓縮包等非文本文件r / w讀寫兼用語法上可行但新手容易踩指針位置的坑不建議優(yōu)先學我想重點提醒一下 w 模式和 a 模式的區(qū)別這個基礎卻特別重要。w 是“覆蓋寫”一旦執(zhí)行文件原來的內容就會被清空。我見過不止一個新手本來想往配置里加一行結果用了 w 模式整個文件被清空了。如果你是想在日志后面追加新內容一定用 a 模式只有當你確定要“從零重新生成”一個文件時才用 w。r 這種讀寫混合模式說實話我在實際項目里用得很少。因為讀和寫的文件指針位置不同很容易出現(xiàn)“寫完讀不到”“讀的位置不對”這種莫名其妙的問題。新手階段你就老老實實分開讀取用 r寫入用 w 或 a這樣思路最清晰。2.2 為什么推薦用 with 而不是 openclose看很多老代碼你會看到這樣的寫法f open(demo.txt, w, encodingutf-8) f.write(hello) f.close()這種寫法不是不行但有個大坑如果你在 write 之后、close 之前程序突然拋了異常close 這一行就不會執(zhí)行。文件沒關會怎樣寫入的數(shù)據(jù)可能還留在緩沖區(qū)里沒真正落到磁盤程序退出后文件內容還是空的或者文件一直被程序占用你去刪文件會提示“文件正在使用中”嚴重時甚至會導致內存或文件句柄泄漏。所以現(xiàn)代 Python 強烈推薦用 with 上下文管理器with open(demo.txt, w, encodingutf-8) as f: f.write(hello)with 語句的原理其實不復雜它會在代碼塊執(zhí)行結束后自動幫我們調用 close()哪怕中間拋了異常也會做清理工作。這段代碼跑完文件一定被正確關閉你不用再手動操心。很多人剛開始不理解這個 with 為什么叫“上下文管理器”我的理解方式很簡單你看這個英文單詞 with它表達的就是“在某個場境內做某事”。你進了一個房間辦完事出門房間會自動鎖好你不需要自己記得回頭鎖門。這個理念在后端開發(fā)里非常常見早點養(yǎng)成用 with 的習慣后面學數(shù)據(jù)庫連接、網絡請求時都會輕松很多。2.3 編碼與類型轉換最容易踩的兩顆雷文件操作里最容易讓人崩潰的就是編碼問題。你寫代碼時明明覺得沒問題一運行卻給你拋一個 UnicodeDecodeError或者寫出來的文件打開全是亂碼這種經歷我敢說每個 Python 開發(fā)者都遇到過。問題的根源在于不同平臺、不同軟件生成的文件編碼可能不一樣。常見的編碼有 UTF-8 和 GBK。Windows 上某些老軟件默認用 GBK 編碼而 Linux 系統(tǒng)和大部分現(xiàn)代工具默認用 UTF-8。Python 在讀取文件時如果不指定編碼不同平臺上默認值還不一樣這就導致了“在你電腦上好好的換臺電腦就亂碼”。我的建議是每次調用 open() 讀寫文本文件都顯式寫上 encodingutf-8。這個習慣能幫你避開 90% 的編碼坑。如果讀的是老系統(tǒng)生成的 GBK 文件就改成 encodinggbk一時分不清是什么編碼可以先試 utf-8報錯了再換 gbk。第二顆雷是類型轉換。從文件里讀出來的每一行、每一個單元格本質上都是字符串。你想拿“123”去加 1必須先 int(123)你想拿“3.14”去做浮點運算必須先 float(3.14)。這聽起來很簡單但實際數(shù)據(jù)里經?;熘崭瘛Q行、特殊字符直接轉換就會拋 ValueError。所以完整流程往往是先 strip() 清理空白再轉換成目標類型如果數(shù)據(jù)本身可能是臟數(shù)據(jù)再加個 try/except 兜底。今天下面的案例里你會看到這個模式反復出現(xiàn)。3. 實操環(huán)節(jié)四個案例帶你從讀寫到處理3.1 案例一文本文件寫入與逐行讀取先來一個最基礎的往 demo.txt 里寫入三行文字再把它讀出來打印到屏幕上。with open(demo.txt, w, encodingutf-8) as f: f.write(第一行內容\n) f.write(第二行內容\n) f.write(第三行內容\n)這三行代碼做了幾件事打開文件、寫三行文字、自動關閉文件。注意每行末尾的 \n它是換行符告訴文件“這一行到這里結束”。如果不寫 \n三行文字會擠成一行。寫入后我們再讀取with open(demo.txt, r, encodingutf-8) as f: for line in f: print(line.strip())這里有一個鍵盤上找不到的細節(jié)直接 for line in f 是 Python 里逐行讀取文件最優(yōu)雅的方式。它每次只讀一行不會一次性把整個大文件加載進內存哪怕文件有 1 個 G 也能從容處理。這也回答了很多人問的“大文件怎么讀”的問題——用 for 循環(huán)逐行讀而不是 readlines() 把整個文件一次讀進來。為什么要 line.strip()因為每行末尾帶著 \n 換行符直接 print(line) 會多打一個空行。strip() 會把字符串首尾的空格、換行、制表符都清掉輸出就干凈了。這個習慣在處理真實數(shù)據(jù)時非常重要因為真實文件里幾乎每一行都帶著你不想要的空白字符。3.2 案例二CSV 成績單的清洗與統(tǒng)計接下來進入結構化數(shù)據(jù)處理。CSV 是純文本格式的表格數(shù)據(jù)每行是一條記錄每列用逗號分隔是數(shù)據(jù)交換場景里最常用的格式之一。假設我們有一個 scores.csv 文件內容是name,math,english,python 張三,88,76,92 李四,59,68,72 王五,90,85,88現(xiàn)在要讀進來算出每個人的總分和平均分寫到一個新文件 result.csv 里。import csv with open(scores.csv, r, encodingutf-8) as f: reader csv.DictReader(f) rows [] for row in reader: name row[name].strip() math int(row[math].strip()) english int(row[english].strip()) python int(row[python].strip()) total math english python avg round(total / 3, 2) rows.append([name, math, english, python, total, avg]) with open(result.csv, w, encodingutf-8, newline) as f: writer csv.writer(f) writer.writerow([name, math, english, python, total, avg]) writer.writerows(rows)這個案例里有兩個很實用的知識點。第一個是 csv.DictReader。它會把 CSV 的表頭第一行自動變成字典的 key之后每一行數(shù)據(jù)都會以字典形式返回row[name] 就能拿到姓名。這樣做的好處是不用死記“第 0 列是姓名第 1 列是數(shù)學成績”代碼可讀性大大提升。第二個是 writer 打開文件時為什么要加 newline。這個坑特別隱蔽——如果不加寫出來的 CSV 每隔一行就會出現(xiàn)一個空行。原因是 csv 模塊自己會處理換行符和 open() 的默認換行行為沖突了。我見過很多朋友在這里卡了半天明明代碼邏輯都對就是輸出文件多了一堆空行。記住操作 CSV 文件時加 newline 是一個標準姿勢。數(shù)據(jù)處理方面你看 int(row[math].strip()) 這行先 strip() 清掉兩側可能存在的空格再轉 int。真實 CSV 文件里單元格前后經常藏著空格、回車不清理就會拋 ValueError。這就是前面說的“類型轉換的雷”在真實場景里幾乎每次都會遇到。3.3 案例三JSON 配置文件的讀取與修改JSON 是另一種極其常見的數(shù)據(jù)格式它的特點是可以嵌套表達能力比 CSV 強很多特別適合存配置。做爬蟲的同學應該很熟悉請求頭、延遲時間、賬號密碼之類的參數(shù)通常都會放在一個 config.json 里。假設 config.json 內容如下{ url: https://example.com/api, interval: 3, headers: { User-Agent: python-requests } }現(xiàn)在要把 interval 改成 5同時改一下 User-Agent再寫回文件import json with open(config.json, r, encodingutf-8) as f: config json.load(f) config[interval] 5 config[headers][User-Agent] Mozilla/5.0 with open(config.json, w, encodingutf-8) as f: json.dump(config, f, ensure_asciiFalse, indent2)json.load 會把 JSON 文件自動還原成 Python 的字典和列表你直接就能通過 key 修改值非常直觀。修改完成后json.dump 再把它寫回文件。寫回的時候有兩個參數(shù)值得關注。第一個是 ensure_asciiFalse如果不設置它中文會被轉成 \uXXXX 這種轉義序列文件內容完全沒法看。第二個是 indent2讓 JSON 輸出有縮進文件可讀性好很多。少了這兩個參數(shù)代碼也能跑但產出的文件丑得你不想打開第二次。還有個小經驗讀取不熟悉的 JSON 配置文件時先用 print(config.keys()) 看一眼它有哪些頂層字段再動手改。我曾經憑記憶“我覺得字段叫 interval”結果實際文件里寫的是 “time_interval”一運行直接 KeyError。先打印結構是處理嵌套 JSON 時最穩(wěn)妥的起步動作。3.4 案例四日志文件的篩選與統(tǒng)計最后一個案例我們寫一個日志分析的小工具。假設 server.log 文件內容大致是這樣2025-01-01 10:00:00 INFO 服務啟動成功 2025-01-01 10:00:05 ERROR 數(shù)據(jù)庫連接超時 2025-01-01 10:00:08 INFO 重試中 2025-01-01 10:00:12 ERROR 數(shù)據(jù)庫連接超時 2025-01-01 10:00:20 ERROR 磁盤空間不足我們要統(tǒng)計 ERROR 的總數(shù)并把所有包含“超時”的行打印出來error_count 0 with open(server.log, r, encodingutf-8) as f: for line in f: if ERROR in line: error_count 1 if 超時 in line: print(line.strip()) print(ERROR 總數(shù):, error_count)這段代碼沒什么高深語法但非?!俺绦騿T”。你會發(fā)現(xiàn)真實世界的日志處理很多時候就是這種樸素的關鍵字判斷。等以后學了正則表達式你能用 re 模塊提取時間戳、IP 地址做更精確的分析學了 pandas你能按小時、按天聚合統(tǒng)計錯誤數(shù)量。但今天這個版本已經是一個能解決實際問題的“最小可用方案”。另外你可以試著把這段代碼封裝成一個函數(shù)比如 analyze_log(filepath, keyword)。這就在實際項目中養(yǎng)成了好習慣每個小工具都是一個獨立函數(shù)參數(shù)通過函數(shù)的入?yún)鬟f方便復用和測試。這也是前面學的“函數(shù)”知識第一次和文件處理真正結合。4. 常見問題速查表與避坑經驗4.1 新手最容易遇到的 6 個報錯文件操作報錯率極高我把最常見的問題整理成一張速查表建議你保存起來報錯信息出現(xiàn)原因解決辦法FileNotFoundError文件不存在或當前工作目錄和你以為的不一樣先檢查文件路徑用 os.path.exists() 判斷打印 os.getcwd() 看當前目錄UnicodeDecodeError讀取時的編碼與實際文件編碼不一致顯式指定 encodingutf-8如果不行再試 gbkUnicodeEncodeError寫入時編碼設置不合理寫入也顯式指定 encodingutf-8PermissionError文件被其他程序占用或沒有寫入權限關閉占用程序或換個可寫目錄ValueErrorint 轉換失敗數(shù)據(jù)里有空格、換行或非數(shù)字字符先 strip()再轉換必要時 try/except 兜底CSV 每兩行空一行打開文件時沒加 newline寫入 CSV 時 open 里加 newline這六個里面FileNotFoundError 是我被問得最多的。很多人代碼明明看著沒問題為什么找不到文件因為 open 里面的相對路徑是相對于“當前工作目錄”的而不是“Python 文件所在目錄”。你可能從桌面運行腳本工作目錄就是桌面而你寫的 demo.txt 在別的文件夾里自然找不到。遇到這種情況要么用絕對路徑要么打印 os.getcwd() 看看當前工作目錄到底是什么。4.2 三個讓我少踩很多坑的實操習慣做文件處理做得多了我慢慢養(yǎng)成了一些固定習慣談不上高大上但確實幫我省了不少時間。第一個習慣是任何時候讀寫文本文件都顯式寫 encodingutf-8。哪怕是在 Linux 上哪怕 Python 默認就是 utf-8我也寫。因為你不知道這段代碼幾個月后會被誰在什么系統(tǒng)上運行寫死編碼就是給未來的自己少挖一個坑。第二個習慣是涉及文件路徑時盡量用 os.path.join() 來拼接而不是手動寫 data/ filename 這種字符串。因為 Windows 用反斜杠Linux 和 macOS 用正斜杠手動拼接很容易跨平臺出問題。os.path.join(data, scores.csv) 會自動選擇當前平臺正確的分隔符。第三個習慣是有風險的文件寫入場景先寫臨時文件再替換。比如你有一個重要的配置文件程序要修改它如果你直接以 w 模式打開原文件寫入萬一寫到一半程序崩了原文件就壞了。穩(wěn)妥的做法是先寫到一個臨時文件比如 config.json.tmp寫成功后用 os.replace() 把它替換成正式文件。這個習慣在寫生產環(huán)境腳本時尤其重要我第一次體會到它的價值就是在一次程序崩潰后眼睜睜看著配置文件被寫壞那叫一個痛。5. 文件操作的能力如何給后續(xù)學習鋪路5.1 爬蟲與數(shù)據(jù)分析都會用到這些很多人學文件操作時會覺得就這么點東西有什么好學的但我想說今天你練的每一個案例幾乎都能在后面的主流方向里直接復用。先說爬蟲。寫爬蟲抓取數(shù)據(jù)代碼其實只占 30%剩下 30% 是數(shù)據(jù)清洗40% 是把數(shù)據(jù)存下來。存數(shù)據(jù)逃不開 csv.writer、json.dump 這兩板斧。你爬了一千條商品信息最后往往就是一句 rows.append([...]) 然后 writer.writerows(rows) 收工。今天案例二、案例三練的功夫到了爬蟲場景幾乎是直接平移使用。再說數(shù)據(jù)分析。數(shù)據(jù)分析的第一步永遠是讀數(shù)據(jù)pandas 是一個強大的數(shù)據(jù)處理庫它的 read_csv、read_json 底層就是在做你今天做的事——只不過包了一層更好用的接口。如果你連文件編碼、格式、路徑這些底層概念都沒搞清楚用 pandas 時遇到讀不出來的報錯會完全不知道從哪里排查。而有了今天的基礎你會知道問題大概率出在編碼或路徑上排查思路和定位效率完全不同。5.2 學完今天的內容下一步怎么發(fā)展如果你今天的內容都照著敲完了我給你幾個具體的進階建議。第一嘗試把案例二和案例三結合用 config.json 存輸入文件名、關鍵詞、輸出文件路徑這些參數(shù)然后寫一個通用化的數(shù)據(jù)處理腳本。這基本就是一個“準生產級”的小工具模式了你能體會到配置和代碼分離帶來的好處。第二練一練用 pathlib 模塊做路徑處理。它是 Python 3.4 之后引入的新路徑庫整體 API 設計比 os.path 更現(xiàn)代操作文件路徑的代碼寫起來也更直觀。如果你想往專業(yè)方向走pathlib 遲早要學。第三等你的文件處理練熟了可以考慮接觸一下正則表達式。它能幫你從日志、網頁、文本里更精準地提取信息和文件操作配合使用你寫出來的腳本會瞬間上一個檔次。第四如果對處理超大文件感興趣可以了解一下協(xié)程和分批處理的思想這也是 Python 處理高并發(fā)、大數(shù)據(jù)時的核心思路之一。但別急那都是后面的事今天把文件讀寫練扎實就是最值得的一步。最后再分享一個我的帶教心得文件操作這一塊我?guī)Я诉@么多新人發(fā)現(xiàn)一個很普遍的現(xiàn)象——很多人把 with open 當成一個模板死記硬背代碼能跑但是說不清原理。等到真正做項目時90% 的報錯恰恰都出在這條最簡單的語句上不是路徑錯了就是編碼亂了。所以今天文章中四個案例你最好每一行都親手敲一遍尤其是 CSV 和 JSON 那兩個。等你熟練到閉著眼都能寫出“讀配置 → 處理數(shù)據(jù) → 寫結果”這套流程后面學爬蟲、做數(shù)據(jù)分析你會發(fā)現(xiàn)到處都是熟悉的味道那種感覺真的很爽。