原文:https://automatetheboringstuff.com/2e/chapter16/
在第 15 章,你學(xué)習(xí)了如何從 PDF 和 Word 文檔中提取文本。這些文件是二進(jìn)制格式的,需要特殊的 Python 模塊來訪問它們的數(shù)據(jù)。另一方面,CSV 和 JSON 文件只是純文本文件。您可以在文本編輯器(如 Mu)中查看它們。但是 Python 還附帶了特殊的csv
和json
模塊,每個模塊都提供了幫助您處理這些文件格式的函數(shù)。
CSV 代表“逗號分隔值”,CSV 文件是存儲為純文本文件的簡化電子表格。Python 的csv
模塊使得解析 CSV 文件變得很容易。
JSON(讀作“JAY-saw”或“Jason”——怎么讀并不重要,因為人們會說你讀錯了)是一種將信息作為 JavaScript 源代碼存儲在純文本文件中的格式。(JSON 是 JavaScript 對象符號的縮寫。)使用 JSON 文件不需要了解 JavaScript 編程語言,但是了解 JSON 格式很有用,因為它在許多 Web 應(yīng)用中使用。
CSV 模塊
CSV 文件中的每一行代表電子表格中的一行,行中的單元格用逗號分隔。例如,的電子表格example.xlsx
在一個 CSV 文件中會是這樣的:
4/5/2015 13:34,Apples,73
4/5/2015 3:41,Cherries,85
4/6/2015 12:46,Pears,14
4/8/2015 8:59,Oranges,52
4/10/2015 2:07,Apples,152
4/10/2015 18:10,Bananas,23
4/10/2015 2:40,Strawberries,98
我將在本章的交互式 Shell 示例中使用這個文件。您可以從下載example.csv
或者在文本編輯器中輸入文本并保存為example.csv
。
CSV 文件很簡單,缺少 Excel 電子表格的許多功能。例如,CSV 文件:
- 它們的值沒有類型——一切都是字符串
- 沒有字體大小或顏色的設(shè)置
- 沒有多個工作表
- 無法指定單元格的寬度和高度
- 不能有合并單元格
- 不能嵌入圖像或圖表
CSV 文件的優(yōu)點是簡單。CSV 文件被許多類型的程序廣泛支持,可以在文本編輯器(包括 Mu)中查看,并且是表示電子表格數(shù)據(jù)的一種直接方式。CSV 格式與廣告中的完全一樣:它只是一個由逗號分隔的值組成的文本文件。
由于 CSV 文件只是文本文件,您可能會嘗試將它們作為字符串讀入,然后使用您在第 9 章中學(xué)到的技術(shù)處理該字符串。例如,由于 CSV 文件中的每個單元格都由逗號分隔,所以您可以在每行文本上調(diào)用split(',')
來獲取逗號分隔的值作為字符串列表。但并不是 CSV 文件中的每個逗號都代表兩個單元格之間的邊界。CSV 文件也有自己的轉(zhuǎn)義字符集,允許逗號和其他字符作為值的一部分包含在其中。split()
方法不處理這些轉(zhuǎn)義字符。因為這些潛在的陷阱,你應(yīng)該總是使用csv
模塊來讀寫 CSV 文件。
reader
對象
要用csv
模塊從 CSV 文件中讀取數(shù)據(jù),您需要創(chuàng)建一個reader
對象。一個reader
對象讓你遍歷 CSV 文件中的行。在交互 Shell 中輸入以下內(nèi)容,當(dāng)前工作目錄中有example.csv
:
>>> import csv # ?
>>> exampleFile = open('example.csv') # ?
>>> exampleReader = csv.reader(exampleFile) # ?
>>> exampleData = list(exampleReader) # ?
>>> exampleData # ?
[['4/5/2015 13:34', 'Apples', '73'], ['4/5/2015 3:41', 'Cherries', '85'],
['4/6/2015 12:46', 'Pears', '14'], ['4/8/2015 8:59', 'Oranges', '52'],
['4/10/2015 2:07', 'Apples', '152'], ['4/10/2015 18:10', 'Bananas', '23'],
['4/10/2015 2:40', 'Strawberries', '98']]
Python 自帶了csv
模塊,所以我們可以導(dǎo)入它 ? 而不必先安裝它。
要使用csv
模塊讀取一個 CSV 文件,首先使用open()
函數(shù) ? 打開它,就像您處理任何其他文本文件一樣。但不是在open()
返回的File
對象上調(diào)用read()
或readlines()
方法,而是將其傳遞給csv.reader()
函數(shù) ?。這將返回一個reader
對象供您使用。注意,您沒有將文件名字符串直接傳遞給csv.reader()
函數(shù)。
訪問reader
對象中的值的最直接的方法是通過將它傳遞給list()
? 來將其轉(zhuǎn)換成普通的 Python 列表。在這個reader
對象上使用list()
會返回一個列表列表,您可以將它存儲在一個類似exampleData
的變量中。在 Shell 中輸入exampleData
顯示列表列表 ?。
現(xiàn)在您已經(jīng)將 CSV 文件作為一個列表列表,您可以使用表達(dá)式exampleData[row][col]
訪問特定行和列的值,其中row
是exampleData
中一個列表的索引,col
是您希望從該列表中獲得的項目的索引。在交互式 Shell 中輸入以下內(nèi)容:
>>> exampleData[0][0]
'4/5/2015 13:34'
>>> exampleData[0][1]
'Apples'
>>> exampleData[0][2]
'73'
>>> exampleData[1][1]
'Cherries'
>>> exampleData[6][1]
'Strawberries'
從輸出中可以看到,exampleData[0][0]
進(jìn)入第一個列表并給出第一個字符串,exampleData[0][2]
進(jìn)入第一個列表并給出第三個字符串,依此類推。
在for
循環(huán)中從reader
對象中讀取數(shù)據(jù)
對于大的 CSV 文件,您將希望在一個for
循環(huán)中使用reader
對象。這避免了一次將整個文件加載到內(nèi)存中。例如,在交互式 Shell 中輸入以下內(nèi)容:
>>> import csv
>>> exampleFile = open('example.csv')
>>> exampleReader = csv.reader(exampleFile)
>>> for row in exampleReader:
print('Row #' + str(exampleReader.line_num) + ' ' + str(row))
Row #1 ['4/5/2015 13:34', 'Apples', '73']
Row #2 ['4/5/2015 3:41', 'Cherries', '85']
Row #3 ['4/6/2015 12:46', 'Pears', '14']
Row #4 ['4/8/2015 8:59', 'Oranges', '52']
Row #5 ['4/10/2015 2:07', 'Apples', '152']
Row #6 ['4/10/2015 18:10', 'Bananas', '23']
Row #7 ['4/10/2015 2:40', 'Strawberries', '98']
在您導(dǎo)入了csv
模塊并從 CSV 文件中創(chuàng)建了一個reader
對象之后,您可以遍歷reader
對象中的行。每行是一個值列表,每個值代表一個單元格。
print()
函數(shù)調(diào)用打印當(dāng)前行的編號和該行的內(nèi)容。要獲得行號,使用reader
對象的line_num
變量,它包含當(dāng)前行的行號。
reader
對象只能循環(huán)一次。要重新讀取 CSV 文件,您必須調(diào)用csv.reader
來創(chuàng)建一個reader
對象。
writer
對象
一個writer
對象允許你將數(shù)據(jù)寫入一個 CSV 文件。要創(chuàng)建一個writer
對象,可以使用csv.writer()
函數(shù)。在交互式 Shell 中輸入以下內(nèi)容:
>>> import csv
>>> outputFile = open('output.csv', 'w', newline='') # ?
>>> outputWriter = csv.writer(outputFile) # ?
>>> outputWriter.writerow(['spam', 'eggs', 'bacon', 'ham'])
21
>>> outputWriter.writerow(['Hello, world!', 'eggs', 'bacon', 'ham'])
32
>>> outputWriter.writerow([1, 2, 3.141592, 4])
16
>>> outputFile.close()
首先調(diào)用open()
并傳遞'w'
以寫模式打開一個文件 ?。這將創(chuàng)建一個對象,然后你可以傳遞給csv.writer()
? 來創(chuàng)建一個writer
對象。
在 Windows 上,您還需要為open()
函數(shù)的newline
關(guān)鍵字參數(shù)傳遞一個空字符串。由于超出本書范圍的技術(shù)原因,如果你忘記設(shè)置newline
參數(shù),那么output.csv
中的行將是雙倍行距,如圖圖 16-1 所示。
圖 16-1:如果你忘記了open()
中的newline=''
關(guān)鍵字參數(shù),CSV 文件將會是雙倍行距。
writer
對象的writerow()
方法接受一個列表參數(shù)。列表中的每個值都放在輸出 CSV 文件中自己的單元格中。writerow()
的返回值是寫入文件中該行的字符數(shù)(包括換行符)。
這段代碼生成一個類似于下面的output.csv
文件:
spam,eggs,bacon,ham
"Hello, world!",eggs,bacon,ham
1,2,3.141592,4
注意在 CSV 文件中,writer
對象是如何用雙引號自動轉(zhuǎn)義值'Hello, world!'
中的逗號的。csv
模塊讓您不必親自處理這些特殊情況。
delimiter
和lineterminator
關(guān)鍵字參數(shù)
假設(shè)您希望用制表符而不是逗號來分隔單元格,并且希望行是雙倍行距。您可以在交互式 Shell 中輸入如下內(nèi)容:
>>> import csv
>>> csvFile = open('example.tsv', 'w', newline='')
>>> csvWriter = csv.writer(csvFile, delimiter='\t', lineterminator='\n\n') # ?
>>> csvWriter.writerow(['apples', 'oranges', 'grapes'])
24
>>> csvWriter.writerow(['eggs', 'bacon', 'ham'])
17
>>> csvWriter.writerow(['spam', 'spam', 'spam', 'spam', 'spam', 'spam'])
32
>>> csvFile.close()
這將更改文件中的分隔符和行結(jié)束符。分隔符是出現(xiàn)在一行單元格之間的字符。默認(rèn)情況下,CSV 文件的分隔符是逗號。行結(jié)束符是出現(xiàn)在一行末尾的字符。默認(rèn)情況下,行結(jié)束符是換行符。您可以通過使用帶有csv.writer()
的delimiter
和lineterminator
關(guān)鍵字參數(shù)將字符更改為不同的值。
傳遞delimiter='\t'
和lineterminator='\n\n'
? 將單元格之間的字符更改為制表符,將行之間的字符更改為兩個換行符。然后我們調(diào)用writerow()
三次,得到三行。
這將生成一個名為example.tsv
的文件,其內(nèi)容如下:
apples oranges grapes
eggs bacon ham
spam spam spam spam spam spam
現(xiàn)在我們的單元格由制表符分隔,我們使用文件擴(kuò)展名tsv
,用于制表符分隔的值。
DictReader
和DictWriter
CSV 對象
對于包含標(biāo)題行的 CSV 文件,使用DictReader
和DictWriter
對象通常比使用reader
和writer
對象更方便。
reader
和writer
對象通過使用列表讀寫 CSV 文件行。DictReader
和DictWriter
CSV 對象執(zhí)行相同的功能,但是使用字典,它們使用 CSV 文件的第一行作為這些字典的鍵。
前往下載exampleWithHeader.csv
文件。這個文件與example.csv
相同,除了它在第一行中有時間戳、水果和數(shù)量作為列標(biāo)題。
要讀取該文件,請在交互式 Shell 中輸入以下內(nèi)容:
>>> import csv
>>> exampleFile = open('exampleWithHeader.csv')
>>> exampleDictReader = csv.DictReader(exampleFile)
>>> for row in exampleDictReader:
... print(row['Timestamp'], row['Fruit'], row['Quantity'])
...
4/5/2015 13:34 Apples 73
4/5/2015 3:41 Cherries 85
4/6/2015 12:46 Pears 14
4/8/2015 8:59 Oranges 52
4/10/2015 2:07 Apples 152
4/10/2015 18:10 Bananas 23
4/10/2015 2:40 Strawberries 98
在循環(huán)內(nèi)部,DictReader
object 將row
設(shè)置為一個字典對象,其鍵來自第一行的標(biāo)題。(嗯,從技術(shù)上來說,它將row
設(shè)置為一個OrderedDict
對象,你可以像使用字典一樣使用它;它們之間的區(qū)別超出了本書的范圍。)使用一個DictReader
對象意味著你不需要額外的代碼來跳過第一行的標(biāo)題信息,因為DictReader
對象為你做了這件事。
如果您試圖將DictReader
對象與第一行沒有列標(biāo)題的example.csv
一起使用,DictReader
對象將使用'4/5/2015 13:34'
、'Apples'
和'73'
作為字典鍵。為了避免這種情況,您可以為DictReader()
函數(shù)提供第二個參數(shù),其中包含虛構(gòu)的頭名稱:
>>> import csv
>>> exampleFile = open('example.csv')
>>> exampleDictReader = csv.DictReader(exampleFile, ['time', 'name',
'amount'])
>>> for row in exampleDictReader:
... print(row['time'], row['name'], row['amount'])
...
4/5/2015 13:34 Apples 73
4/5/2015 3:41 Cherries 85
4/6/2015 12:46 Pears 14
4/8/2015 8:59 Oranges 52
4/10/2015 2:07 Apples 152
4/10/2015 18:10 Bananas 23
4/10/2015 2:40 Strawberries 98
因為example.csv
的第一行沒有任何用于每列標(biāo)題的文本,所以我們創(chuàng)建了自己的:'time'
、'name'
和'amount'
。
DictWriter
對象使用字典創(chuàng)建 CSV 文件。
>>> import csv
>>> outputFile = open('output.csv', 'w', newline='')
>>> outputDictWriter = csv.DictWriter(outputFile, ['Name', 'Pet', 'Phone'])
>>> outputDictWriter.writeheader()
>>> outputDictWriter.writerow({'Name': 'Alice', 'Pet': 'cat', 'Phone': '555-
1234'})
20
>>> outputDictWriter.writerow({'Name': 'Bob', 'Phone': '555-9999'})
15
>>> outputDictWriter.writerow({'Phone': '555-5555', 'Name': 'Carol', 'Pet':
'dog'})
20
>>> outputFile.close()
如果您希望您的文件包含一個標(biāo)題行,通過調(diào)用writeheader()
來編寫該行。否則,跳過調(diào)用writeheader()
從文件中省略一個標(biāo)題行。然后用一個writerow()
方法調(diào)用寫入 CSV 文件的每一行,傳遞一個字典,該字典使用文件頭作為鍵,包含要寫入文件的數(shù)據(jù)。
這段代碼創(chuàng)建的output.csv
文件如下所示:
Name,Pet,Phone
Alice,cat,555-1234
Bob,,555-9999
Carol,dog,555-5555
注意,你傳遞給writerow()
的字典中鍵-值對的順序并不重要:它們是按照給DictWriter()
的鍵的順序編寫的。例如,即使您在第四行的Name
和Pet
鍵和值之前傳遞了Phone
鍵和值,電話號碼仍然出現(xiàn)在輸出的最后。
還要注意,任何丟失的鍵,比如{'Name': 'Bob', 'Phone': '555-9999'}
中的'Pet'
,在 CSV 文件中都會是空的。
項目:從 CSV 文件中移除文件頭
假設(shè)您有一份從數(shù)百個 CSV 文件中刪除第一行的枯燥工作。也許您會將它們輸入到一個自動化的流程中,該流程只需要數(shù)據(jù),而不需要列頂部的標(biāo)題。你可以在 Excel 中打開每個文件,刪除第一行,然后重新保存文件——但這需要幾個小時。讓我們寫一個程序來代替它。
該程序?qū)⑿枰蜷_當(dāng)前工作目錄下每個csv
擴(kuò)展名的文件,讀入 CSV 文件的內(nèi)容,將沒有第一行的內(nèi)容重寫到同名文件中。這將用新的無頭內(nèi)容替換 CSV 文件的舊內(nèi)容。
警告
和往常一樣,每當(dāng)你編寫一個修改文件的程序時,一定要先備份這些文件,以防你的程序不按你期望的方式運行。你不想意外刪除你的原始文件。
在高層次上,程序必須做到以下幾點:
- 在當(dāng)前工作目錄中查找所有 CSV 文件。
- 讀入每個文件的全部內(nèi)容。
- 跳過第一行,將內(nèi)容寫入一個新的 CSV 文件。
在代碼級別,這意味著程序需要做以下事情:
- 從
os.listdir()
開始循環(huán)文件列表,跳過非 CSV 文件。 - 創(chuàng)建一個 CSV
reader
對象并讀入文件的內(nèi)容,使用line_num
屬性來決定跳過哪一行。 - 創(chuàng)建一個 CSV
writer
對象并將讀入的數(shù)據(jù)寫出到新文件中。
對于這個項目,打開一個新的文件編輯器窗口,保存為removeCsvHeader.py
。
第一步:遍歷每個 CSV 文件
您的程序需要做的第一件事是遍歷當(dāng)前工作目錄的所有 CSV 文件名的列表。讓您的removeCsvHeader.py
看起來像這樣:
#! python3
# removeCsvHeader.py - Removes the header from all CSV files in the current
# working directory.
import csv, os
os.makedirs('headerRemoved', exist_ok=True)
# Loop through every file in the current working directory.
for csvFilename in os.listdir('.'):
if not csvFilename.endswith('.csv'):
continue # skip non-csv files # ?
print('Removing header from ' + csvFilename + '...')
# TODO: Read the CSV file in (skipping first row).
# TODO: Write out the CSV file.
調(diào)用os.makedirs()
將創(chuàng)建一個headerRemoved
文件夾,所有的無頭 CSV 文件將被寫入其中。在os.listdir('.')
上的一個for
循環(huán)可以讓你完成一部分,但是它會遍歷工作目錄中的所有文件,所以你需要在循環(huán)的開始添加一些代碼,跳過不以.csv
結(jié)尾的文件名。當(dāng)遇到非 CSV 文件時,continue
語句 ? 使for
循環(huán)移動到下一個文件名。
程序運行時會有一些輸出,打印出一條消息,說明程序正在處理哪個 CSV 文件。然后,添加一些關(guān)于程序其余部分應(yīng)該做什么的TODO
注釋。
第二步:讀入 CSV 文件
程序不會刪除 CSV 文件的第一行。相反,它創(chuàng)建一個沒有第一行的 CSV 文件的新副本。由于副本的文件名與原始文件名相同,副本將覆蓋原始文件名。
程序需要一種方法來跟蹤它當(dāng)前是否在第一行循環(huán)。將以下內(nèi)容添加到removeCsvHeader.py
中。
#! python3
# removeCsvHeader.py - Removes the header from all CSV files in the current
# working directory.
--snip--
# Read the CSV file in (skipping first row).
csvRows = []
csvFileObj = open(csvFilename)
readerObj = csv.reader(csvFileObj)
for row in readerObj:
if readerObj.line_num == 1:
continue # skip first row
csvRows.append(row)
csvFileObj.close()
# TODO: Write out the CSV file.
reader
對象的line_num
屬性可用于確定它當(dāng)前正在讀取 CSV 文件中的哪一行。另一個for
循環(huán)將遍歷從 CSV reader
對象返回的行,除了第一行之外的所有行將被附加到csvRows
。
當(dāng)for
循環(huán)遍歷每一行時,代碼檢查readerObj.line_num
是否被設(shè)置為1
。如果是,它執(zhí)行一個continue
來移動到下一行,而不把它附加到csvRows
。對于之后的每一行,條件將始終為False
,并且該行將被附加到csvRows
。
第三步:寫出沒有第一行的 CSV 文件
現(xiàn)在csvRows
包含了除第一行之外的所有行,這個列表需要寫到headerRemoved
文件夾中的一個 CSV 文件中。將以下內(nèi)容添加到removeCsvHeader.py
:
#! python3
# removeCsvHeader.py - Removes the header from all CSV files in the current
# working directory.
--snip--
# Loop through every file in the current working directory.
for csvFilename in os.listdir('.'): # ?
if not csvFilename.endswith('.csv'):
continue # skip non-CSV files
--snip--
# Write out the CSV file.
csvFileObj = open(os.path.join('headerRemoved', csvFilename), 'w',
newline='')
csvWriter = csv.writer(csvFileObj)
for row in csvRows:
csvWriter.writerow(row)
csvFileObj.close()
CSV writer
對象將使用csvFilename
(我們在 CSV 讀取器中也使用了它)將列表寫入到headerRemoved
中的 CSV 文件中。這將覆蓋原始文件。
一旦我們創(chuàng)建了writer
對象,我們就遍歷存儲在csvRows
中的子列表,并將每個子列表寫入文件。
代碼執(zhí)行后,外層for
循環(huán) ? 將從os.listdir('.')
開始循環(huán)到下一個文件名。當(dāng)這個循環(huán)結(jié)束時,程序就完成了。
為了測試你的程序,從nostarch.com/automatestuff2
下載removeCsvHeader.zip
并解壓到一個文件夾中。運行該文件夾中的removeCsvHeader.py
程序。輸出將如下所示:
Removing header from NAICS_data_1048.csv...
Removing header from NAICS_data_1218.csv...
--snip--
Removing header from NAICS_data_9834.csv...
Removing header from NAICS_data_9986.csv...
這個程序應(yīng)該在每次從 CSV 文件中刪除第一行時打印一個文件名。
類似程序的創(chuàng)意
您可以為 CSV 文件編寫的程序類似于您可以為 Excel 文件編寫的程序,因為它們都是電子表格文件。您可以編寫程序來完成以下任務(wù):
- 比較一個 CSV 文件中不同行之間或多個 CSV 文件之間的數(shù)據(jù)。
- 將特定數(shù)據(jù)從 CSV 文件復(fù)制到 Excel 文件,反之亦然。
- 檢查 CSV 文件中的無效數(shù)據(jù)或格式錯誤,并提醒用戶注意這些錯誤。
- 從 CSV 文件中讀取數(shù)據(jù)作為 Python 程序的輸入。
JSON 和 API
JavaScript 對象符號是將數(shù)據(jù)格式化為單個人類可讀字符串的一種流行方式。JSON 是 JavaScript 程序編寫數(shù)據(jù)結(jié)構(gòu)的原生方式,通常類似于 Python 的pprint()
函數(shù)會產(chǎn)生的結(jié)果。為了處理 JSON 格式的數(shù)據(jù),您不需要了解 JavaScript。
下面是一個格式化為 JSON 的數(shù)據(jù)示例:
{"name": "Zophie", "isCat": true,
"miceCaught": 0, "napsTaken": 37.5,
"felineIQ": null}
了解 JSON 是很有用的,因為許多網(wǎng)站提供 JSON 內(nèi)容作為程序與網(wǎng)站交互的一種方式。這被稱為提供應(yīng)用編程接口(API) 。訪問 API 與通過 URL 訪問任何其他網(wǎng)頁是一樣的。區(qū)別在于 API 返回的數(shù)據(jù)是為機(jī)器格式化的(例如用 JSON );API 不容易讓人讀懂。
許多網(wǎng)站以 JSON 格式提供數(shù)據(jù)。Data.gov、推特、雅虎、谷歌、Tumblr、維基百科、Flickr、Reddit、IMDb、爛番茄、LinkedIn 和許多其他流行的網(wǎng)站都提供 API 供程序使用。其中一些網(wǎng)站需要注冊,而注冊幾乎總是免費的。為了獲得想要的數(shù)據(jù),您必須找到程序需要請求哪些 URL 的文檔,以及返回的 JSON 數(shù)據(jù)結(jié)構(gòu)的一般格式。這個文檔應(yīng)該由提供 API 的任何站點提供;如果他們有一個“開發(fā)者”頁面,在那里尋找文檔。
使用 API,您可以編寫執(zhí)行以下操作的程序:
- 從網(wǎng)站上搜集原始數(shù)據(jù)。(訪問 API 往往比下載網(wǎng)頁和用 BeautifulSoup 解析 HTML 更方便。)
- 自動從您的一個社交網(wǎng)絡(luò)帳戶下載新帖子,并將其發(fā)布到另一個帳戶。例如,你可以把你的 Tumblr 帖子發(fā)到臉書。
- 從 IMDb、爛番茄和維基百科中提取數(shù)據(jù),放入你電腦上的一個文本文件中,為你的個人電影收藏創(chuàng)建一個“電影百科全書”。
您可以在參考資料中的看到一些 JSON APIs 的例子。
JSON 并不是將數(shù)據(jù)格式化為可讀字符串的唯一方法。還有許多其他格式,包括 XML(可擴(kuò)展標(biāo)記語言)、TOML (Tom 的顯而易見的最小化語言)、YML(另一種標(biāo)記語言)、INI(初始化),甚至是過時的 ASN.1(抽象語法符號一)格式,所有這些都提供了一種將數(shù)據(jù)表示為人類可讀文本的結(jié)構(gòu)。本書不會涉及這些,因為 JSON 已經(jīng)迅速成為使用最廣泛的替代格式,但是有第三方 Python 模塊可以輕松處理它們。
json模塊
Python 的json
模塊為json.loads()
和json.dumps()
函數(shù)處理帶有 JSON 數(shù)據(jù)的字符串和 Python 值之間轉(zhuǎn)換的所有細(xì)節(jié)。JSON 不能存儲每一種 Python 值。它只能包含以下數(shù)據(jù)類型的值:字符串、整數(shù)、浮點、布爾、列表、字典和NoneType
。JSON 不能表示特定于 Python 的對象,比如File
對象、CSV reader
或writer
對象、Regex
對象或 Selenium WebElement
對象。
用loads()
函數(shù)讀取 JSON
要將包含 JSON 數(shù)據(jù)的字符串轉(zhuǎn)換成 Python 值,請將其傳遞給json.loads()
函數(shù)。(該名稱的意思是“加載字符串”,而不是“加載”)在交互式 Shell 中輸入以下內(nèi)容:
>>> stringOfJsonData = '{"name": "Zophie", "isCat": true, "miceCaught": 0,
"felineIQ": null}'
>>> import json
>>> jsonDataAsPythonValue = json.loads(stringOfJsonData)
>>> jsonDataAsPythonValue
{'isCat': True, 'miceCaught': 0, 'name': 'Zophie', 'felineIQ': None}
導(dǎo)入json
模塊后,可以調(diào)用loads()
并向其傳遞一串 JSON 數(shù)據(jù)。注意,JSON 字符串總是使用雙引號。它將以 Python 字典的形式返回數(shù)據(jù)。Python 字典不是按順序排列的,所以在打印jsonDataAsPythonValue
時,鍵值對可能會以不同的順序出現(xiàn)。
編寫 JSON 與dumps()
函數(shù)
json.dumps()
函數(shù)(意思是“轉(zhuǎn)儲字符串”,而不是“轉(zhuǎn)儲”)將把 Python 值轉(zhuǎn)換成 JSON 格式的數(shù)據(jù)字符串。在交互式 Shell 中輸入以下內(nèi)容:
>>> pythonValue = {'isCat': True, 'miceCaught': 0, 'name': 'Zophie',
'felineIQ': None}
>>> import json
>>> stringOfJsonData = json.dumps(pythonValue)
>>> stringOfJsonData
'{"isCat": true, "felineIQ": null, "miceCaught": 0, "name": "Zophie" }'
該值只能是以下基本 Python 數(shù)據(jù)類型之一:字典、列表、整數(shù)、浮點、字符串、布爾或None
。
項目:獲取當(dāng)前天氣數(shù)據(jù)
查看天氣似乎很簡單:打開你的網(wǎng)絡(luò)瀏覽器,點擊地址欄,輸入一個天氣網(wǎng)站的 URL(或者搜索一個然后點擊鏈接),等待頁面加載,瀏覽所有的廣告,等等。
實際上,如果你有一個程序可以下載未來幾天的天氣預(yù)報并以純文本格式打印出來,那么你可以跳過很多無聊的步驟。這個程序使用第 12 章中的requests
模塊從網(wǎng)上下載數(shù)據(jù)。
總的來說,該程序完成了以下工作:
- 從命令行讀取請求的位置
- 從 OpenWeatherMap.org 下載 JSON 天氣數(shù)據(jù)
- 將 JSON 數(shù)據(jù)的字符串轉(zhuǎn)換為 Python 數(shù)據(jù)結(jié)構(gòu)
- 打印今天和未來兩天的天氣
因此,代碼需要執(zhí)行以下操作:
- 連接
sys.argv
中的字符串以獲得位置。 - 調(diào)用
requests.get()
下載天氣數(shù)據(jù)。 - 調(diào)用
json.loads()
將 JSON 數(shù)據(jù)轉(zhuǎn)換成 Python 數(shù)據(jù)結(jié)構(gòu)。 - 打印天氣預(yù)報。
對于這個項目,打開一個新的文件編輯器窗口,并將其保存為getOpenWeather.py
。然后在你的瀏覽器中訪問openweathermap.org/api
并注冊一個免費帳戶,以獲得一個 API 密鑰,也稱為應(yīng)用 ID,對于 OpenWeatherMap 服務(wù)來說,它是一個類似于'30144aba38018987d84710d0e319281e'
的字符串代碼。除非你計劃每分鐘進(jìn)行 60 次以上的 API 調(diào)用,否則你不需要為這項服務(wù)付費。對 API 密鑰保密;任何知道它的人都可以編寫使用您帳戶的使用配額的腳本。
第一步:從命令行參數(shù)獲取位置
這個程序的輸入將來自命令行。使getOpenWeather.py
看起來像這樣:
#! python3
# getOpenWeather.py - Prints the weather for a location from the command line.
APPID = 'YOUR_APPID_HERE'
import json, requests, sys
# Compute location from command line arguments.
if len(sys.argv) < 2:
print('Usage: getOpenWeather.py city_name, 2-letter_country_code')
sys.exit()
location = ' '.join(sys.argv[1:])
# TODO: Download the JSON data from OpenWeatherMap.org's API.
# TODO: Load JSON data into a Python variable.
在 Python 中,命令行參數(shù)存儲在sys.argv
列表中。APPID
變量應(yīng)該設(shè)置為您的帳戶的 API 密鑰。沒有這個密鑰,您對天氣服務(wù)的請求將會失敗。在#!
shebang 行和import
語句之后,程序?qū)z查是否有多個命令行參數(shù)。(回想一下,sys.argv
總是至少有一個元素sys.argv[0]
,它包含 Python 腳本的文件名。)如果列表中只有一個元素,那么用戶沒有在命令行上提供位置,并且在程序結(jié)束之前將向用戶提供“用法”消息。
OpenWeatherMap 服務(wù)要求查詢格式為城市名、逗號和兩個字母的國家代碼(如“US”代表美國)。你可以在en.wikipedia.org/wiki/ISO_3166-1_alpha-2
找到這些代碼的列表。我們的腳本顯示檢索到的 JSON 文本中列出的第一個城市的天氣。不幸的是,同名的城市,如俄勒岡州的波特蘭和緬因州的波特蘭,都將被包括在內(nèi),盡管 JSON 文本將包括經(jīng)度和緯度信息以區(qū)分這兩個城市。
命令行參數(shù)按空格拆分。命令行參數(shù)San Francisco, US
將使sys.argv
保持['getOpenWeather.py', 'San', 'Francisco,', 'US']
。因此,調(diào)用join()
方法來連接除了sys.argv
中第一個以外的所有字符串。將這個連接的字符串存儲在一個名為location
的變量中。
第二步:下載 JSON 數(shù)據(jù)
OpenWeatherMap.org
以 JSON 格式提供實時天氣信息。首先你必須在網(wǎng)站上注冊一個免費的 API 密匙。(此鍵用于限制您在他們的服務(wù)器上發(fā)出請求的頻率,以降低他們的帶寬成本。)您的程序只需下載位于api.openweathermap.org/data/2.5/forecast/daily?q=<LOC>&cnt=3&appid=<APIkey>
的頁面,其中位置<LOC>
是您想要了解其天氣的城市名稱,<APIkey>
是您的個人 API key。將以下內(nèi)容添加到getOpenWeather.py
中。
#! python3
# getOpenWeather.py - Prints the weather for a location from the command line.
--snip--
# Download the JSON data from OpenWeatherMap.org's API.
url ='https://api.openweathermap.org/data/2.5/forecast/daily?q=%s&cnt=3&APPID=%s ' % (location,
APPID)
response = requests.get(url)
response.raise_for_status()
# Uncomment to see the raw JSON text:
#print(response.text)
# TODO: Load JSON data into a Python variable.
我們從命令行參數(shù)中得到location
。為了創(chuàng)建我們想要訪問的 URL,我們使用了%s
占位符,并將存儲在location
中的任何字符串插入到 URL 字符串中的那個位置。我們將結(jié)果存儲在url
中,并將url
傳遞給requests.get()
。requests.get()
調(diào)用返回一個Response
對象,您可以通過調(diào)用raise_for_status()
來檢查它的錯誤。如果沒有出現(xiàn)異常,下載的文本將在response.text
中。
第三步:加載 JSON 數(shù)據(jù)并打印天氣
response.text
成員變量保存一大串 JSON 格式的數(shù)據(jù)。要將其轉(zhuǎn)換為 Python 值,請調(diào)用json.loads()
函數(shù)。JSON 數(shù)據(jù)將如下所示:
{'city': {'coord': {'lat': 37.7771, 'lon': -122.42},
'country': 'United States of America',
'id': '5391959',
'name': 'San Francisco',
'population': 0},
'cnt': 3,
'cod': '200',
'list': [{'clouds': 0,
'deg': 233,
'dt': 1402344000,
'humidity': 58,
'pressure': 1012.23,
'speed': 1.96,
'temp': {'day': 302.29,
'eve': 296.46,
'max': 302.29,
'min': 289.77,
'morn': 294.59,
'night': 289.77},
'weather': [{'description': 'sky is clear',
'icon': '01d',
--snip--
您可以通過將weatherData
傳遞給pprint.pprint()
來查看這些數(shù)據(jù)。你可能想查看openweathermap.org
以獲得更多關(guān)于這些字段含義的文檔。例如,在線文檔會告訴你'day'
后的302.29
是白天的開爾文溫度,而不是攝氏度或華氏度。
你要的天氣描述在'main'
和'description'
之后。為了整齊地打印出來,將以下內(nèi)容添加到getOpenWeather.py
中。
! python3
# getOpenWeather.py - Prints the weather for a location from the command line.
--snip--
# Load JSON data into a Python variable.
weatherData = json.loads(response.text)
# Print weather descriptions.
w = weatherData['list'] # ?
print('Current weather in %s:' % (location))
print(w[0]['weather'][0]['main'], '-', w[0]['weather'][0]['description'])
print()
print('Tomorrow:')
print(w[1]['weather'][0]['main'], '-', w[1]['weather'][0]['description'])
print()
print('Day after tomorrow:')
print(w[2]['weather'][0]['main'], '-', w[2]['weather'][0]['description'])
請注意代碼是如何將weatherData['list']
存儲在變量w
中的,以節(jié)省您鍵入 ? 的時間。您使用w[0]
、w[1]
和w[2]
分別檢索今天、明天和后天天氣的字典。每個字典都有一個'weather'
鍵,其中包含一個列表值。您感興趣的是第一個列表項,它是一個嵌套字典,在索引 0 處還有幾個鍵。這里,我們打印存儲在'main'
和'description'
鍵中的值,用連字符分隔。
當(dāng)使用命令行參數(shù)getOpenWeather.py San Francisco, CA
運行該程序時,輸出如下所示:
Current weather in San Francisco, CA:
Clear - sky is clear
Tomorrow:
Clouds - few clouds
Day after tomorrow:
Clear - sky is clear
(天氣是我喜歡住在舊金山的原因之一?。?/p>
類似程序的創(chuàng)意
訪問天氣數(shù)據(jù)可以構(gòu)成許多類型程序的基礎(chǔ)。您可以創(chuàng)建類似的程序來執(zhí)行以下操作:
- 收集幾個露營地或徒步旅行路線的天氣預(yù)報,看看哪個會有最好的天氣。
- 制定一個計劃,定期檢查天氣,如果你需要將植物移到室內(nèi),會給你發(fā)送霜凍警告。(第 17 章講述日程安排,第 18 章解釋如何發(fā)送電子郵件。)
- 從多個站點獲取天氣數(shù)據(jù)并一次顯示,或者計算并顯示多個天氣預(yù)測的平均值。
總結(jié)
CSV 和 JSON 是存儲數(shù)據(jù)的常見純文本格式。它們很容易被程序解析,同時仍然是人類可讀的,所以它們通常用于簡單的電子表格或 Web 應(yīng)用數(shù)據(jù)。csv
和json
模塊大大簡化了 CSV 和 JSON 文件的讀寫過程。
前幾章已經(jīng)教你如何使用 Python 來解析各種文件格式的信息。一個常見的任務(wù)是從各種格式中提取數(shù)據(jù),并對其進(jìn)行解析以獲得您需要的特定信息。這些任務(wù)通常特定于商業(yè)軟件沒有最佳幫助的情況。通過編寫自己的腳本,您可以讓計算機(jī)處理以這些格式渲染的大量數(shù)據(jù)。
在第 18 章中,你將脫離數(shù)據(jù)格式,學(xué)習(xí)如何讓你的程序通過發(fā)送電子郵件和文本信息與你交流。
練習(xí)題
-
Excel 電子表格有哪些 CSV 電子表格沒有的功能?
-
你傳遞給
csv.reader()
和csv.writer()
什么來創(chuàng)建reader
和writer
對象? -
reader
和writer
對象的File
對象需要在什么模式下打開? -
什么方法獲取列表參數(shù)并將其寫入 CSV 文件?
-
delimiter
和lineterminator
關(guān)鍵字參數(shù)是做什么的? -
什么函數(shù)接受一串 JSON 數(shù)據(jù)并返回一個 Python 數(shù)據(jù)結(jié)構(gòu)?
-
哪個函數(shù)采用 Python 數(shù)據(jù)結(jié)構(gòu)并返回一串 JSON 數(shù)據(jù)?
實踐項目
為了練習(xí),編寫一個程序來完成以下任務(wù)。
Excel 到 CSV 轉(zhuǎn)換器
Excel 只需點擊幾下鼠標(biāo)就可以將電子表格保存為 CSV 文件,但是如果您必須將數(shù)百個 Excel 文件轉(zhuǎn)換為 CSV 文件,則需要花費數(shù)小時的點擊時間。使用第十二章的中的openpyxl
模塊,編寫一個程序,讀取當(dāng)前工作目錄中的所有 Excel 文件,并將其輸出為 CSV 文件。
一個 Excel 文件可能包含多個工作表;您必須為每張工作表創(chuàng)建一個 CSV 文件。CSV 文件的文件名應(yīng)為<excel_file_name>_<worksheet_title>.csv
,其中<excel_file_name>
是不帶文件擴(kuò)展名的 Excel 文件的文件名(例如,'spam_data'
,而不是'spam_data.xlsx'
),<worksheet_title>
是來自Worksheet
對象的title
變量的字符串。
這個程序?qū)S多嵌套的for
循環(huán)。程序的框架看起來會像這樣:文章來源:http://www.zghlxwxcb.cn/news/detail-400092.html
for excelFile in os.listdir('.'):
# Skip non-xlsx files, load the workbook object.
for sheetName in wb.get_sheet_names():
# Loop through every sheet in the workbook.
sheet = wb.get_sheet_by_name(sheetName)
# Create the CSV filename from the Excel filename and sheet title.
# Create the csv.writer object for this CSV file.
# Loop through every row in the sheet.
for rowNum in range(1, sheet.max_row + 1):
rowData = [] # append each cell to this list
# Loop through each cell in the row.
for colNum in range(1, sheet.max_column + 1):
# Append each cell's data to rowData.
# Write the rowData list to the CSV file.
csvFile.close()
從nostarch.com/automatestuff2
下載 ZIP 文件excelSpreadsheets.zip
并將電子表格解壓到與你的程序相同的目錄下。您可以將這些文件用作測試程序的文件。文章來源地址http://www.zghlxwxcb.cn/news/detail-400092.html
到了這里,關(guān)于Python 自動化指南(繁瑣工作自動化)第二版:十六、使用 CSV 文件和 JSON 數(shù)據(jù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!