在IO程式設計中,Stream(流)是一種重要的概念,分為輸入流(Input Stream)和輸出流(Output Stream)。我們可以把流理解為一個水管,資料相當於水管中的水,但是隻能單向流動,所以資料傳輸過程中需要架設兩個水管,一個負責輸入,一個負責輸出,這樣讀寫就可以實現同步。
本文主要講解磁碟IO操作。
作者:範傳輝
如需轉載請聯絡大資料(ID:hzdashuju)
01 檔案讀寫
1. 開啟檔案
讀寫檔案是最常見的IO操作。Python內建了讀寫檔案的函式,方便了檔案的IO操作。
檔案讀寫之前需要開啟檔案,確定檔案的讀寫樣式。open函式用來開啟檔案,語法如下:
open(name[.mode[.buffering]])
open函式使用一個檔案名作為唯一的強制引數,然後傳回一個檔案物件。樣式(mode)和緩衝區(buffering)引數都是可選的,預設樣式是讀樣式,預設緩衝區是無。
假設有個名為qiye.txt的文字檔案,其儲存路徑是c:\text(或者是在Linux下的~/text),那麼可以像下麵這樣開啟檔案。在互動式環境的提示符“>>>”下,輸入如下內容:
>>> f = open(r'c:\text\qiye.txt')
如果檔案不存在,將會看到一個類似下麵的異常回溯:
Traceback (most recent call last):
File "", line 1, in
IOError: [Errno 2] No such file or directory: 'C:\\qiye.txt'
2. 檔案樣式
下麵主要說一下open函式中的mode引數,透過改變mode引數可以實現對檔案的不同操作。
-
‘r’:讀樣式
-
‘w’:寫樣式
-
‘a’:追加樣式
-
‘b’:二進位制樣式(可新增到其他樣式中使用)
-
‘+’:讀/寫樣式(可新增到其他樣式中使用)
這裡主要是提醒一下’b’引數的使用,一般處理文字檔案時,是用不到’b’引數的,但處理一些其他型別的檔案(二進位制檔案),比如mp3音樂或者影象,那麼應該在樣式引數中增加’b’,這在爬蟲中處理媒體檔案很常用。引數’rb’可以用來讀取一個二進位制檔案。
3. 檔案緩衝區
open函式中第三個可選引數buffering控制著檔案的緩衝。
如果引數是0,I/O操作就是無緩衝的,直接將資料寫到硬碟上;如果引數是1,I/O操作就是有緩衝的,資料先寫到記憶體裡,只有使用flush函式或者close函式才會將資料更新到硬碟;如果引數為大於1的數字則代表緩衝區的大小(單位是位元組),-1(或者是任何負數)代表使用預設緩衝區的大小。
4. 檔案讀取
檔案讀取主要是分為按位元組讀取和按行進行讀取,經常用到的方法有read()、readlines()、close()。
在“>>>”輸入f = open(r’c:\text\qiye.txt’)後,如果成功開啟文字檔案,接下來呼叫read()方法則可以一次性將檔案內容全部讀到記憶體中,最後傳回的是str型別的物件:
>>> f.read()
"qiye"
最後一步呼叫close(),可以關閉對檔案的取用。檔案使用完畢後必須關閉,因為檔案物件會佔用作業系統資源,影響系統的IO操作。
>>> f.close()
由於檔案操作可能會出現IO異常,一旦出現IO異常,後面的close()方法就不會呼叫。所以為了保證程式的健壯性,我們需要使用try … finally來實現。
try:
f = open(r'c:\text\qiye.txt','r')
print f.read()
finally:
if f:
f.close()
上面的程式碼略長,Python提供了一種簡單的寫法,使用with陳述句來替代try … finally程式碼塊和close()方法,如下所示:
with open(r'c:\text\qiye.txt','r') as fileReader:
print fileReader.read()
呼叫read()一次將檔案內容讀到記憶體,但是如果檔案過大,將會出現記憶體不足的問題。一般對於大檔案,可以反覆呼叫read(size)方法,一次最多讀取size個位元組。如果檔案是文字檔案,Python提供了更加合理的做法,呼叫readline()可以每次讀取一行內容,呼叫readlines()一次讀取所有內容並按行傳回串列。
大家可以根據自己的具體需求採取不同的讀取方式,例如小檔案可以直接採取read()方法讀到記憶體,大檔案更加安全的方式是連續呼叫read(size),而對於配置檔案等文字檔案,使用readline()方法更加合理。
將上面的程式碼進行修改,採用readline()的方式實現如下所示:
with open(r'c:\text\qiye.txt','r') as fileReader:
for line in fileReader.readlines():
print line.strip()
5. 檔案寫入
寫檔案和讀檔案是一樣的,唯一的區別是在呼叫open方法時,傳入識別符號’w’或者’wb’表示寫入文字檔案或者寫入二進位制檔案,示例如下:
f = open(r'c:\text\qiye.txt','w')
f.write('qiye')
f.close()
我們可以反覆呼叫write()方法寫入檔案,最後必須使用close()方法來關閉檔案。使用write()方法的時候,作業系統不是立即將資料寫入檔案中的,而是先寫入記憶體中快取起來,等到空閑時候再寫入檔案中,最後使用close()方法就將資料完整地寫入檔案中了。
當然也可以使用f.flush()方法,不斷將資料立即寫入檔案中,最後使用close()方法來關閉檔案。和讀檔案同樣道理,檔案操作中可能會出現IO異常,所以還是推薦使用with陳述句:
with open(r'c:\text\qiye.txt','w') as fileWriter:
fileWriter.write('qiye')
02 操作檔案和目錄
在Python中對檔案和目錄的操作經常用到os模組和shutil模組。接下來主要介紹一些操作檔案和目錄的常用方法:
-
獲得當前Python指令碼工作的目錄路徑:
os.getcwd()。
-
傳回指定目錄下的所有檔案和目錄名:
os.listdir()。例如傳回C盤下的檔案:os.listdir(“C:\\”)
-
刪除一個檔案:
os.remove(filepath)。
-
刪除多個空目錄:
os.removedirs(r”d:\python”)。
-
檢驗給出的路徑是否是一個檔案:
os.path.isfile(filepath)。
-
檢驗給出的路徑是否是一個目錄:
os.path.isdir(filepath)。
-
判斷是否是絕對路徑:
os.path.isabs()。
-
檢驗路徑是否真的存在:
os.path.exists()。例如檢測D盤下是否有Python檔案夾:os.path.exists(r”d:\python”)
-
分離一個路徑的目錄名和檔案名:
os.path.split()。例如:os.path.split(r”/home/qiye/qiye.txt”),傳回結果是一個元組:(‘/home/qiye’, ‘qiye.txt’)。
-
分離副檔名:
os.path.splitext()。例如os.path.splitext(r”/home/qiye/qiye.txt”),傳回結果是一個元組:(‘/home/qiye/qiye’, ‘.txt’)。
-
獲取路徑名:
os.path.dirname(filetpah)。
-
獲取檔案名:
os.path.basename(filepath)。
-
讀取和設定環境變數:
os.getenv()與os.putenv()。
-
給出當前平臺使用的行終止符:
os.linesep。Windows使用’\r\n’,Linux使用’\n’而Mac使用’\r’。
-
指示你正在使用的平臺:
os.name。對於Windows,它是’nt’,而對於Linux/Unix使用者,它是’posix’。
-
重新命名檔案或者目錄:
os.rename(old,new)。
-
建立多級目錄:
os.makedirs(r”c:\python\test”)。
-
建立單個目錄:
os.mkdir(“test”)。
-
獲取檔案屬性:
os.stat(file)。
-
修改檔案許可權與時間戳:
os.chmod(file)。
-
獲取檔案大小:
os.path.getsize(filename)。
-
複製檔案夾:
shutil.copytree(“olddir”,”newdir”)。olddir和newdir都只能是目錄,且newdir必須不存在。
-
複製檔案:
shutil.copyfile(“oldfile”,”newfile”),oldfile和newfile都只能是檔案;shutil. copy(“oldfile”,”newfile”),oldfile只能是檔案,newfile可以是檔案,也可以是標的目錄。
-
移動檔案(目錄):
shutil.move(“oldpos”,”newpos”)。
-
刪除目錄:
os.rmdir(“dir”),只能刪除空目錄;shutil.rmtree(“dir”),空目錄、有內容的目錄都可以刪。
03 序列化操作
物件的序列化在很多高階程式語言中都有相應的實現,Python也不例外。程式執行時,所有的變數都是在記憶體中的,例如在程式中宣告一個dict物件,裡面儲存著爬取的頁面的連結、頁面的標題、頁面的摘要等資訊:
d = dict(url='index.html',title='首頁',content='首頁')
在程式執行的過程中爬取的頁面的連結會不斷變化,比如把url改成了second.html,但是程式一結束或意外中斷,程式中的記憶體變數都會被作業系統進行回收。
如果沒有把修改過的url儲存起來,下次執行程式的時候,url被初始化為index.html,又是從首頁開始,這是我們不願意看到的。所以把記憶體中的變數變成可儲存或可傳輸的過程,就是序列化。
將記憶體中的變數序列化之後,可以把序列化後的內容寫入磁碟,或者透過網路傳輸到別的機器上,實現程式狀態的儲存和共享。反過來,把變數內容從序列化的物件重新讀取到記憶體,稱為反序列化。
在Python中提供了兩個模組:cPickle和pickle來實現序列化,前者是由C語言編寫的,效率比後者高很多,但是兩個模組的功能是一樣的。一般編寫程式的時候,採取的方案是先匯入cPickle模組,如果此模組不存在,再匯入pickle模組。示例如下:
try:
import cPickle as pickle
except ImportError:
import pickle
pickle實現序列化主要使用的是dumps方法或dump方法。dumps方法可以將任意物件序列化成一個str,然後可以將這個str寫入檔案進行儲存。在Python Shell中示例如下:
>>> import cPickle as pickle
>>> d = dict(url='index.html',title='首頁',content='首頁')
>>> pickle.dumps(d)
"(dp1\nS'content'\np2\nS'\\xca\\xd7\\xd2\\xb3'\np3\nsS'url'\np4\nS'index.html'\np5\nsS'title'\np6\ng3\ns."
如果使用dump方法,可以將序列化後的物件直接寫入檔案中:
>>> f=open(r'D:\dump.txt','wb')
>>> pickle.dump(d,f)
>>> f.close()
pickle實現反序列化使用的是loads方法或load方法。把序列化後的檔案從磁碟上讀取為一個str,然後使用loads方法將這個str反序列化為物件,或者直接使用load方法將檔案直接反序列化為物件,如下所示:
>>> f=open(r'D:\dump.txt','rb')
>>> d=pickle.load(f)
>>> f.close()
>>> d
{'content': '\xca\xd7\xd2\xb3', 'url': 'index.html', 'title': '\xca\xd7\xd2\xb3'}
透過反序列化,儲存為檔案的dict物件,又重新恢復出來,但是這個變數和原變數沒有什麼關係,只是內容一樣。以上就是序列化操作的整個過程。
假如我們想在不同的程式語言之間傳遞物件,把物件序列化為標準格式是關鍵,例如XML,但是現在更加流行的是序列化為JSON格式,既可以被所有的程式語言讀取解析,也可以方便地儲存到磁碟或者透過網路傳輸。
關於作者:範傳輝,資深網蟲,Python開發者,參與開發了多項網路應用,在實際開發中積累了豐富的實戰經驗,並善於總結,貢獻了多篇技術文章廣受好評。研究興趣是網路安全、爬蟲技術、資料分析、驅動開發等技術。
本文摘編自《Python爬蟲開發與專案實戰》,經出版方授權釋出。
延伸閱讀《Python爬蟲開發與專案實戰》
點選上圖瞭解及購買
轉載請聯絡微信:DoctorData
推薦語:零基礎學習爬蟲技術,從Python和Web前端基礎開始講起,由淺入深,包含大量案例,實用性強。
朋友會在“發現-看一看”看到你“在看”的內容