Python使用csv模塊進行文件讀寫的操作詳解
一、核心概念解析
1.1 基礎定義:CSV到底是什么?
CSV的全稱是"Comma-Separated Values"(逗號分隔值),但它有個小秘密:并不總是用逗號。
看看這幾個常見的CSV變體:
# 典型的CSV格式 name,age,city 張三,30,北京 李四,25,上海 # 用分號分隔(歐洲常見) name;age;city 張三;30;北京 李四;25;上海 # 用制表符分隔(TSV文件) name age city 張三 30 北京 李四 25 上海 # 帶引號的CSV(包含逗號時) product,price,description "蘋果,紅富士",5.8,"新鮮,甜" 香蕉,3.2,"進口,黃色"
CSV的本質是純文本表格,由三部分組成:
- 表頭行(可選):定義每列的名稱
- 數(shù)據(jù)行:每行一條記錄
- 分隔符:通常是逗號,但也可能是其他字符
1.2 基本語法:csv模塊的四大金剛
csv模塊的核心是四個類/函數(shù):
import csv
# 1. 讀取CSV文件
with open('data.csv', 'r', encoding='utf-8') as file:
reader = csv.reader(file)
for row in reader:
print(row) # 每行是一個列表
# 2. 寫入CSV文件
data = [
['name', 'age', 'city'],
['張三', '30', '北京'],
['李四', '25', '上海']
]
with open('output.csv', 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerows(data)
# 3. 用字典方式讀?。ㄍ扑]!)
with open('data.csv', 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
for row in reader:
print(row['name'], row['age']) # 通過列名訪問
# 4. 用字典方式寫入
data = [
{'name': '張三', 'age': '30', 'city': '北京'},
{'name': '李四', 'age': '25', 'city': '上海'}
]
with open('output_dict.csv', 'w', encoding='utf-8', newline='') as file:
fieldnames = ['name', 'age', 'city']
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader() # 寫入表頭
writer.writerows(data)
注意那個newline=''參數(shù):在Windows上,如果不加這個,每行后面會多一個空行。這是Python處理文本文件的一個坑,記住就好。
1.3 核心特點:為什么選擇csv模塊?
- 零依賴:Python自帶,不用安裝任何東西
- 內存友好:流式處理,再大的文件也不怕
- 靈活配置:分隔符、引號字符、編碼全都可以自定義
- 簡單易用:幾行代碼就能完成復雜操作
- 兼容性好:處理各種"奇怪"的CSV文件
二、應用場景詳解
2.1 讀取和分析數(shù)據(jù)
假設你有一個銷售數(shù)據(jù)文件sales.csv:
date,product,quantity,price,region 2023-01-15,Widget-A,10,29.99,North 2023-01-15,Gadget-X,5,99.99,South 2023-01-16,Widget-A,8,29.99,East 2023-01-16,Thingy-B,3,149.99,West
讓我們用csv模塊來分析它:
# analyze_sales.py
import csv
from collections import defaultdict
from datetime import datetime
def analyze_sales_data(filepath):
"""
分析銷售數(shù)據(jù)
"""
# 存儲統(tǒng)計結果
stats = {
'total_sales': 0,
'total_quantity': 0,
'by_product': defaultdict(float),
'by_region': defaultdict(float),
'by_date': defaultdict(float)
}
with open(filepath, 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
for row in reader:
try:
# 轉換數(shù)據(jù)類型
quantity = int(row['quantity'])
price = float(row['price'])
date_str = row['date']
# 計算銷售額
sales = quantity * price
# 更新統(tǒng)計
stats['total_sales'] += sales
stats['total_quantity'] += quantity
stats['by_product'][row['product']] += sales
stats['by_region'][row['region']] += sales
stats['by_date'][date_str] += sales
except (ValueError, KeyError) as e:
print(f"數(shù)據(jù)格式錯誤: {row}, 錯誤: {e}")
continue
return stats
def print_statistics(stats):
"""
打印統(tǒng)計結果
"""
print("=" * 50)
print("銷售數(shù)據(jù)分析報告")
print("=" * 50)
print(f"\n總銷售額: ${stats['total_sales']:.2f}")
print(f"總銷售數(shù)量: {stats['total_quantity']}")
print("\n按產品統(tǒng)計:")
for product, sales in sorted(stats['by_product'].items(),
key=lambda x: x[1], reverse=True):
print(f" {product}: ${sales:.2f}")
print("\n按地區(qū)統(tǒng)計:")
for region, sales in sorted(stats['by_region'].items(),
key=lambda x: x[1], reverse=True):
print(f" {region}: ${sales:.2f}")
print("\n按日期統(tǒng)計:")
for date, sales in sorted(stats['by_date'].items()):
print(f" {date}: ${sales:.2f}")
# 生成測試數(shù)據(jù)
def create_sample_data():
"""創(chuàng)建示例銷售數(shù)據(jù)"""
data = [
['date', 'product', 'quantity', 'price', 'region'],
['2023-01-15', 'Widget-A', 10, 29.99, 'North'],
['2023-01-15', 'Gadget-X', 5, 99.99, 'South'],
['2023-01-16', 'Widget-A', 8, 29.99, 'East'],
['2023-01-16', 'Thingy-B', 3, 149.99, 'West'],
['2023-01-16', 'Gadget-X', 12, 99.99, 'North'],
['2023-01-17', 'Widget-A', 15, 27.50, 'South'],
]
with open('sales.csv', 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerows(data)
print("示例數(shù)據(jù)已創(chuàng)建: sales.csv")
if __name__ == "__main__":
# 創(chuàng)建測試數(shù)據(jù)
create_sample_data()
# 分析數(shù)據(jù)
stats = analyze_sales_data('sales.csv')
# 打印結果
print_statistics(stats)
2.2 數(shù)據(jù)清洗和轉換
現(xiàn)實中的數(shù)據(jù)很少是完美的。讓我們看看如何處理各種"臟數(shù)據(jù)":
# clean_data.py
import csv
import re
def clean_csv_file(input_file, output_file):
"""
清洗CSV數(shù)據(jù)
"""
cleaned_rows = []
with open(input_file, 'r', encoding='utf-8') as infile:
reader = csv.DictReader(infile)
fieldnames = reader.fieldnames
for i, row in enumerate(reader, 1):
cleaned_row = {}
for field in fieldnames:
original_value = row.get(field, '')
cleaned_value = clean_field(field, original_value)
cleaned_row[field] = cleaned_value
# 檢查是否有必要的數(shù)據(jù)
if is_valid_row(cleaned_row):
cleaned_rows.append(cleaned_row)
else:
print(f"跳過第{i}行無效數(shù)據(jù): {row}")
# 寫入清洗后的數(shù)據(jù)
with open(output_file, 'w', encoding='utf-8', newline='') as outfile:
writer = csv.DictWriter(outfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(cleaned_rows)
print(f"數(shù)據(jù)清洗完成: {len(cleaned_rows)} 行有效數(shù)據(jù)")
def clean_field(field_name, value):
"""
根據(jù)字段類型清洗數(shù)據(jù)
"""
if not value:
return value
# 去除首尾空白
value = value.strip()
# 根據(jù)不同字段類型進行清洗
if 'email' in field_name.lower():
return clean_email(value)
elif 'phone' in field_name.lower():
return clean_phone(value)
elif 'date' in field_name.lower():
return clean_date(value)
elif 'price' in field_name.lower() or 'amount' in field_name.lower():
return clean_number(value)
else:
return value
def clean_email(email):
"""清洗郵箱地址"""
email = email.lower().strip()
# 簡單的郵箱格式驗證
if '@' in email and '.' in email.split('@')[1]:
return email
return ''
def clean_phone(phone):
"""清洗電話號碼"""
# 移除非數(shù)字字符
digits = re.sub(r'\D', '', phone)
if len(digits) == 11: # 中國手機號
return digits
elif len(digits) == 10: # 固定電話
return digits
else:
return ''
def clean_date(date_str):
"""清洗日期"""
# 嘗試不同的日期格式
formats = ['%Y-%m-%d', '%Y/%m/%d', '%Y年%m月%d日', '%d/%m/%Y']
for fmt in formats:
try:
from datetime import datetime
dt = datetime.strptime(date_str, fmt)
return dt.strftime('%Y-%m-%d') # 統(tǒng)一格式
except ValueError:
continue
return date_str # 無法解析,返回原值
def clean_number(number_str):
"""清洗數(shù)字"""
# 移除貨幣符號、千分位分隔符等
cleaned = re.sub(r'[^\d\.-]', '', number_str)
try:
return str(float(cleaned))
except ValueError:
return '0'
def is_valid_row(row):
"""檢查行是否有效"""
# 必須有姓名
if not row.get('name', '').strip():
return False
# 郵箱必須有效(如果有的話)
email = row.get('email', '')
if email and '@' not in email:
return False
return True
def create_dirty_data():
"""創(chuàng)建包含臟數(shù)據(jù)的測試文件"""
dirty_data = [
['name', 'email', 'phone', 'join_date', 'amount'],
[' 張三 ', 'ZHANGSAN@EXAMPLE.COM', '138-0013-8000', '2023-01-15', '¥1,000.50'],
['李四', 'lisi.example.com', '010-62345678', '2023/02/20', '2,500.00'],
['', 'wangwu@example.com', '13912345678', '2023年03月15日', '1500'],
['趙六', 'zhaoliu@example', '123-4567', '無效日期', 'ABC'],
['錢七', 'qianqi@example.com', '15012345678', '20/04/2023', '3,000.00'],
]
with open('dirty_data.csv', 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerows(dirty_data)
print("臟數(shù)據(jù)測試文件已創(chuàng)建: dirty_data.csv")
if __name__ == "__main__":
# 創(chuàng)建測試數(shù)據(jù)
create_dirty_data()
# 清洗數(shù)據(jù)
clean_csv_file('dirty_data.csv', 'cleaned_data.csv')
# 顯示清洗結果
print("\n清洗前后對比:")
print("-" * 50)
with open('dirty_data.csv', 'r', encoding='utf-8') as f:
print("原始數(shù)據(jù):")
for line in f:
print(" ", line.strip())
print("\n清洗后數(shù)據(jù):")
with open('cleaned_data.csv', 'r', encoding='utf-8') as f:
for line in f:
print(" ", line.strip())
2.3 合并和拆分文件
工作中經(jīng)常需要處理多個CSV文件,比如每月的數(shù)據(jù)分開存儲,但分析時需要合并:
# merge_split_csv.py
import csv
import os
from glob import glob
def merge_csv_files(pattern, output_file):
"""
合并多個CSV文件
參數(shù):
pattern: 文件匹配模式,如 'data/*.csv'
output_file: 合并后的輸出文件
"""
all_data = []
headers = None
for filename in glob(pattern):
print(f"處理文件: {filename}")
with open(filename, 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
# 檢查表頭是否一致
if headers is None:
headers = reader.fieldnames
elif headers != reader.fieldnames:
print(f"警告: {filename} 的表頭不一致,跳過")
continue
# 讀取數(shù)據(jù)
for row in reader:
all_data.append(row)
if not all_data:
print("沒有找到可合并的數(shù)據(jù)")
return
# 寫入合并后的文件
with open(output_file, 'w', encoding='utf-8', newline='') as file:
writer = csv.DictWriter(file, fieldnames=headers)
writer.writeheader()
writer.writerows(all_data)
print(f"合并完成: 共 {len(all_data)} 行數(shù)據(jù),保存到 {output_file}")
def split_csv_by_column(input_file, column_name, output_dir):
"""
按某一列的值拆分CSV文件
參數(shù):
input_file: 輸入文件
column_name: 按此列拆分
output_dir: 輸出目錄
"""
# 確保輸出目錄存在
os.makedirs(output_dir, exist_ok=True)
# 按列值分組數(shù)據(jù)
groups = {}
with open(input_file, 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
headers = reader.fieldnames
for row in reader:
key = row.get(column_name, 'unknown')
if key not in groups:
groups[key] = []
groups[key].append(row)
# 為每個組創(chuàng)建文件
for key, rows in groups.items():
# 創(chuàng)建安全的文件名
safe_key = "".join(c for c in str(key) if c.isalnum() or c in (' ', '-', '_')).rstrip()
output_file = os.path.join(output_dir, f"{safe_key}.csv")
with open(output_file, 'w', encoding='utf-8', newline='') as file:
writer = csv.DictWriter(file, fieldnames=headers)
writer.writeheader()
writer.writerows(rows)
print(f"創(chuàng)建文件: {output_file} ({len(rows)} 行)")
print(f"拆分完成: 共創(chuàng)建 {len(groups)} 個文件")
def create_monthly_data():
"""創(chuàng)建月度測試數(shù)據(jù)"""
months = ['2023-01', '2023-02', '2023-03']
for month in months:
filename = f"monthly_data_{month}.csv"
data = [
['date', 'product', 'sales', 'region'],
[f'{month}-10', 'Product-A', '1000', 'North'],
[f'{month}-15', 'Product-B', '1500', 'South'],
[f'{month}-20', 'Product-A', '800', 'East'],
]
with open(filename, 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerows(data)
print(f"創(chuàng)建: {filename}")
if __name__ == "__main__":
print("=== CSV文件合并與拆分演示 ===\n")
# 創(chuàng)建測試數(shù)據(jù)
print("1. 創(chuàng)建月度測試數(shù)據(jù)...")
create_monthly_data()
# 合并文件
print("\n2. 合并月度數(shù)據(jù)...")
merge_csv_files('monthly_data_*.csv', 'merged_quarterly.csv')
# 拆分文件
print("\n3. 按地區(qū)拆分數(shù)據(jù)...")
split_csv_by_column('merged_quarterly.csv', 'region', 'split_by_region')
# 顯示結果
print("\n" + "=" * 50)
print("生成的文件:")
for file in ['merged_quarterly.csv', 'split_by_region']:
if os.path.exists(file):
if os.path.isdir(file):
print(f"目錄: {file}/")
for f in os.listdir(file):
print(f" - {f}")
else:
print(f"文件: {file}")
三、高級技巧
3.1 處理特殊格式的CSV
不是所有的CSV文件都那么"標準"。讓我們看看如何處理各種特殊情況:
# special_csv_formats.py
import csv
def read_excel_csv():
"""
處理從Excel導出的CSV
Excel導出的CSV常有BOM和特殊編碼
"""
files_to_try = [
('utf-8-sig', 'excel_utf8_bom.csv'), # UTF-8 with BOM
('gbk', 'excel_gbk.csv'), # GBK編碼(中文Windows)
('utf-8', 'excel_utf8.csv'), # 普通UTF-8
]
for encoding, filename in files_to_try:
try:
with open(filename, 'r', encoding=encoding) as file:
reader = csv.reader(file)
data = list(reader)
print(f"成功讀取 {filename} (編碼: {encoding})")
print(f" 表頭: {data[0]}")
print(f" 行數(shù): {len(data)}")
break
except (UnicodeDecodeError, FileNotFoundError):
continue
def handle_custom_delimiters():
"""處理自定義分隔符"""
# 用分號分隔的文件
with open('european_format.csv', 'w', encoding='utf-8') as f:
f.write("name;age;city\n")
f.write("張三;30;北京\n")
f.write("李四;25;上海\n")
# 讀取時指定分隔符
with open('european_format.csv', 'r', encoding='utf-8') as file:
reader = csv.reader(file, delimiter=';')
for row in reader:
print(f"分隔符';': {row}")
# TSV文件(制表符分隔)
with open('data.tsv', 'w', encoding='utf-8') as f:
f.write("name\tage\tcity\n")
f.write("張三\t30\t北京\n")
f.write("李四\t25\t上海\n")
with open('data.tsv', 'r', encoding='utf-8') as file:
reader = csv.reader(file, delimiter='\t')
for row in reader:
print(f"分隔符'\\t': {row}")
def handle_quoted_data():
"""處理帶引號的數(shù)據(jù)"""
# 創(chuàng)建包含逗號和引號的數(shù)據(jù)
tricky_data = [
['name', 'description', 'price'],
['Apple', 'Red, delicious apple', '5.8'],
['Banana', 'Yellow "sweet" banana', '3.2'],
['Orange Juice', 'Fresh, 100% pure orange "juice"', '12.5'],
]
with open('quoted_data.csv', 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file, quoting=csv.QUOTE_ALL) # 所有字段都加引號
writer.writerows(tricky_data)
print("\n帶引號的數(shù)據(jù)文件已創(chuàng)建")
# 讀取時處理引號
with open('quoted_data.csv', 'r', encoding='utf-8') as file:
reader = csv.reader(file)
for row in reader:
print(f"原始行: {row}")
def handle_large_files():
"""處理大文件的內存友好方式"""
print("\n大文件處理策略:")
print("1. 流式處理,一次一行,不加載到內存")
print("2. 分批處理,特別是需要排序或聚合時")
print("3. 使用生成器避免內存累積")
def process_large_file(filename, chunk_size=1000):
"""分批處理大文件"""
with open(filename, 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
chunk = []
for i, row in enumerate(reader, 1):
chunk.append(row)
if len(chunk) >= chunk_size:
yield chunk
chunk = []
if chunk: # 最后一批
yield chunk
# 模擬處理
print("\n模擬處理大文件(分塊):")
for chunk_num, chunk in enumerate(process_large_file('large_data.csv', chunk_size=2), 1):
print(f" 處理第{chunk_num}塊: {len(chunk)} 行")
# 這里可以處理每個數(shù)據(jù)塊
# 比如寫入數(shù)據(jù)庫、進行聚合計算等
if __name__ == "__main__":
print("=== 特殊CSV格式處理 ===\n")
# 處理自定義分隔符
print("1. 自定義分隔符處理:")
handle_custom_delimiters()
# 處理帶引號的數(shù)據(jù)
print("\n2. 帶引號數(shù)據(jù)處理:")
handle_quoted_data()
# 大文件處理
print("\n3. 大文件處理策略:")
handle_large_files()
3.2 性能優(yōu)化技巧
# csv_performance.py
import csv
import time
import random
from memory_profiler import profile
def create_large_csv(filename, num_rows=100000):
"""創(chuàng)建大型測試CSV文件"""
print(f"創(chuàng)建大型測試文件: {filename} ({num_rows} 行)")
with open(filename, 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
# 寫入表頭
writer.writerow(['id', 'name', 'value', 'category', 'timestamp'])
# 寫入數(shù)據(jù)
for i in range(num_rows):
writer.writerow([
i + 1,
f'Item-{random.randint(1, 1000)}',
random.uniform(1, 1000),
random.choice(['A', 'B', 'C', 'D']),
f'2023-{random.randint(1,12):02d}-{random.randint(1,28):02d}'
])
print("文件創(chuàng)建完成")
def naive_processing(filename):
"""原始方法:一次性加載所有數(shù)據(jù)"""
print("\n方法1: 一次性加載所有數(shù)據(jù)")
start = time.time()
with open(filename, 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
all_data = list(reader) # 這里會加載所有數(shù)據(jù)到內存
# 處理數(shù)據(jù)
total = 0
for row in all_data:
total += float(row['value'])
elapsed = time.time() - start
print(f" 處理了 {len(all_data)} 行數(shù)據(jù)")
print(f" 總和: {total:.2f}")
print(f" 耗時: {elapsed:.2f} 秒")
return elapsed
def stream_processing(filename):
"""流式處理:一次處理一行"""
print("\n方法2: 流式處理(一次一行)")
start = time.time()
total = 0
count = 0
with open(filename, 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
for row in reader:
total += float(row['value'])
count += 1
elapsed = time.time() - start
print(f" 處理了 {count} 行數(shù)據(jù)")
print(f" 總和: {total:.2f}")
print(f" 耗時: {elapsed:.2f} 秒")
return elapsed
def batch_processing(filename, batch_size=10000):
"""批量處理:分批處理數(shù)據(jù)"""
print(f"\n方法3: 批量處理(每批 {batch_size} 行)")
start = time.time()
total = 0
count = 0
batch = []
with open(filename, 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
for row in reader:
batch.append(float(row['value']))
count += 1
if len(batch) >= batch_size:
# 處理一個批次
total += sum(batch)
batch = [] # 清空批次
# 處理最后一批
if batch:
total += sum(batch)
elapsed = time.time() - start
print(f" 處理了 {count} 行數(shù)據(jù)")
print(f" 總和: {total:.2f}")
print(f" 耗時: {elapsed:.2f} 秒")
return elapsed
def optimized_writing(filename, num_rows=10000):
"""優(yōu)化寫入性能"""
print(f"\n寫入性能優(yōu)化 ({num_rows} 行):")
# 方法1: 逐行寫入
start = time.time()
with open('slow_write.csv', 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerow(['id', 'value'])
for i in range(num_rows):
writer.writerow([i, i * 1.5]) # 每次調用writerow
elapsed1 = time.time() - start
print(f" 逐行寫入: {elapsed1:.2f} 秒")
# 方法2: 批量寫入
start = time.time()
with open('fast_write.csv', 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerow(['id', 'value'])
# 準備所有數(shù)據(jù)
all_data = [[i, i * 1.5] for i in range(num_rows)]
writer.writerows(all_data) # 一次寫入所有行
elapsed2 = time.time() - start
print(f" 批量寫入: {elapsed2:.2f} 秒")
print(f" 速度提升: {elapsed1/elapsed2:.1f} 倍")
if __name__ == "__main__":
print("=== CSV性能優(yōu)化技巧 ===\n")
# 創(chuàng)建測試數(shù)據(jù)
test_file = 'large_test_data.csv'
create_large_csv(test_file, num_rows=50000)
# 測試不同處理方法的性能
times = []
times.append(('一次性加載', naive_processing(test_file)))
times.append(('流式處理', stream_processing(test_file)))
times.append(('批量處理', batch_processing(test_file)))
# 寫入性能測試
optimized_writing('test_write.csv', num_rows=10000)
# 總結
print("\n" + "=" * 50)
print("性能總結:")
for method, t in times:
print(f" {method}: {t:.2f} 秒")
3.3 實戰(zhàn):構建CSV處理工具
讓我們構建一個實用的CSV處理工具,集成各種常用功能:
# csv_toolkit.py
"""
CSV處理工具箱
一個實用的命令行工具,集成各種CSV處理功能
"""
import csv
import argparse
import sys
import os
from pathlib import Path
class CSVToolkit:
"""CSV處理工具箱"""
def __init__(self):
self.commands = {
'view': self.view_csv,
'head': self.head_csv,
'tail': self.tail_csv,
'stats': self.csv_stats,
'filter': self.filter_csv,
'select': self.select_columns,
'sample': self.sample_csv,
}
def run(self):
"""運行工具箱"""
parser = argparse.ArgumentParser(description='CSV處理工具箱')
parser.add_argument('command', choices=self.commands.keys(),
help='要執(zhí)行的操作')
parser.add_argument('file', help='CSV文件路徑')
parser.add_argument('-o', '--output', help='輸出文件路徑')
parser.add_argument('-n', '--lines', type=int, default=10,
help='顯示的行數(shù)(用于head/tail)')
parser.add_argument('-c', '--columns', help='選擇的列,用逗號分隔')
parser.add_argument('-f', '--filter', help='過濾條件,格式: 列名=值')
args = parser.parse_args()
# 檢查文件是否存在
if not os.path.exists(args.file):
print(f"錯誤: 文件 '{args.file}' 不存在")
sys.exit(1)
# 執(zhí)行命令
self.commands[args.command](args)
def view_csv(self, args):
"""查看CSV文件內容"""
with open(args.file, 'r', encoding='utf-8') as file:
reader = csv.reader(file)
for i, row in enumerate(reader):
# 美化輸出
formatted = ' | '.join(f'{cell:<20}' for cell in row)
print(formatted)
# 顯示表頭分隔線
if i == 0:
print('-' * len(formatted))
def head_csv(self, args):
"""顯示文件前n行"""
with open(args.file, 'r', encoding='utf-8') as file:
reader = csv.reader(file)
for i, row in enumerate(reader):
if i >= args.lines + 1: # +1 為了包含表頭
break
print(', '.join(row))
def tail_csv(self, args):
"""顯示文件后n行"""
with open(args.file, 'r', encoding='utf-8') as file:
lines = file.readlines()
# 顯示最后n行
for line in lines[-args.lines:]:
print(line.rstrip())
def csv_stats(self, args):
"""顯示CSV文件統(tǒng)計信息"""
with open(args.file, 'r', encoding='utf-8') as file:
reader = csv.reader(file)
data = list(reader)
if not data:
print("文件為空")
return
headers = data[0]
rows = data[1:]
print(f"文件: {args.file}")
print(f"行數(shù): {len(rows)} 行(不含表頭)")
print(f"列數(shù): {len(headers)} 列")
print(f"列名: {', '.join(headers)}")
# 每列的數(shù)據(jù)類型推測
if rows:
print("\n列信息:")
for i, header in enumerate(headers):
sample = rows[0][i] if len(rows[0]) > i else ''
print(f" {header}: 示例='{sample}'")
def filter_csv(self, args):
"""過濾CSV文件"""
if not args.filter:
print("錯誤: 需要指定過濾條件(-f 列名=值)")
return
# 解析過濾條件
col_name, value = args.filter.split('=', 1)
with open(args.file, 'r', encoding='utf-8') as infile:
reader = csv.DictReader(infile)
headers = reader.fieldnames
# 收集匹配的行
matching_rows = []
for row in reader:
if row.get(col_name) == value:
matching_rows.append(row)
# 輸出結果
if args.output:
with open(args.output, 'w', encoding='utf-8', newline='') as outfile:
writer = csv.DictWriter(outfile, fieldnames=headers)
writer.writeheader()
writer.writerows(matching_rows)
print(f"過濾結果已保存到: {args.output} ({len(matching_rows)} 行)")
else:
# 打印到控制臺
writer = csv.DictWriter(sys.stdout, fieldnames=headers)
writer.writeheader()
writer.writerows(matching_rows)
def select_columns(self, args):
"""選擇特定列"""
if not args.columns:
print("錯誤: 需要指定要選擇的列(-c 列1,列2,...)")
return
selected = [col.strip() for col in args.columns.split(',')]
with open(args.file, 'r', encoding='utf-8') as infile:
reader = csv.DictReader(infile)
# 檢查列是否存在
for col in selected:
if col not in reader.fieldnames:
print(f"警告: 列 '{col}' 不存在")
# 只保留選中的列
filtered_rows = []
for row in reader:
filtered_row = {col: row[col] for col in selected if col in row}
filtered_rows.append(filtered_row)
# 輸出結果
if args.output:
with open(args.output, 'w', encoding='utf-8', newline='') as outfile:
writer = csv.DictWriter(outfile, fieldnames=selected)
writer.writeheader()
writer.writerows(filtered_rows)
print(f"結果已保存到: {args.output}")
else:
writer = csv.DictWriter(sys.stdout, fieldnames=selected)
writer.writeheader()
writer.writerows(filtered_rows)
def sample_csv(self, args):
"""隨機抽樣"""
import random
with open(args.file, 'r', encoding='utf-8') as file:
reader = csv.reader(file)
data = list(reader)
if len(data) <= 1:
print("文件數(shù)據(jù)不足")
return
headers = data[0]
rows = data[1:]
# 隨機抽樣
sample_size = min(args.lines, len(rows))
sampled_rows = random.sample(rows, sample_size)
# 輸出結果
output_data = [headers] + sampled_rows
if args.output:
with open(args.output, 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerows(output_data)
print(f"抽樣結果已保存到: {args.output} ({sample_size} 行)")
else:
for row in output_data:
print(', '.join(row))
def create_example_csv():
"""創(chuàng)建示例CSV文件"""
data = [
['id', 'name', 'age', 'city', 'salary'],
['1', '張三', '30', '北京', '50000'],
['2', '李四', '25', '上海', '45000'],
['3', '王五', '35', '廣州', '60000'],
['4', '趙六', '28', '深圳', '55000'],
['5', '錢七', '32', '杭州', '52000'],
['6', '孫八', '29', '南京', '48000'],
['7', '周九', '31', '成都', '51000'],
['8', '吳十', '27', '武漢', '47000'],
]
with open('employees.csv', 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerows(data)
print("示例文件已創(chuàng)建: employees.csv")
def demo_toolkit():
"""演示工具箱功能"""
print("=== CSV工具箱功能演示 ===\n")
# 創(chuàng)建示例文件
create_example_csv()
print("\n1. 查看文件內容:")
print("-" * 50)
toolkit = CSVToolkit()
# 模擬命令行參數(shù)
class Args:
pass
args = Args()
args.file = 'employees.csv'
args.lines = 5
args.columns = 'name,city,salary'
args.filter = 'city=北京'
args.output = None
print("\n2. 顯示前3行:")
print("-" * 50)
args.lines = 3
toolkit.head_csv(args)
print("\n3. 文件統(tǒng)計:")
print("-" * 50)
toolkit.csv_stats(args)
print("\n4. 選擇特定列:")
print("-" * 50)
toolkit.select_columns(args)
print("\n5. 過濾數(shù)據(jù):")
print("-" * 50)
toolkit.filter_csv(args)
print("\n6. 隨機抽樣:")
print("-" * 50)
args.lines = 2
toolkit.sample_csv(args)
if __name__ == "__main__":
if len(sys.argv) > 1:
# 命令行模式
toolkit = CSVToolkit()
toolkit.run()
else:
# 演示模式
demo_toolkit()
print("\n" + "=" * 50)
print("使用說明:")
print(" 命令行使用: python csv_toolkit.py <命令> <文件> [選項]")
print("\n可用命令:")
for cmd in CSVToolkit().commands:
print(f" {cmd}")
四、注意事項
4.1 使用限制
- 不適合嵌套數(shù)據(jù):CSV是扁平結構,不適合存儲復雜嵌套數(shù)據(jù)
- 類型信息丟失:所有值都是字符串,需要手動轉換類型
- 無模式驗證:需要自己驗證數(shù)據(jù)完整性和一致性
- 性能限制:對于數(shù)GB的文件,建議使用數(shù)據(jù)庫或專業(yè)工具
- 并發(fā)訪問:CSV文件不適合多進程同時讀寫
4.2 常見問題
Q: 讀取CSV時遇到編碼錯誤怎么辦?
A: 按順序嘗試這些編碼:utf-8-sig→ gbk→ utf-8→ latin-1
encodings = ['utf-8-sig', 'gbk', 'utf-8', 'latin-1']
for enc in encodings:
try:
with open('file.csv', 'r', encoding=enc) as f:
content = f.read()
print(f"使用編碼: {enc}")
break
except UnicodeDecodeError:
continue
Q: 數(shù)據(jù)中有逗號,導致列錯位怎么辦?
A: 使用csv.reader而不是手動分割,它會正確處理引號內的逗號。
Q: 文件太大,內存不夠怎么辦?
A: 使用流式處理,一次處理一行:
with open('large.csv', 'r') as file:
reader = csv.DictReader(file)
for row in reader: # 一次只加載一行到內存
process(row)
Q: 如何跳過CSV文件中的空行?
A: csv.reader會自動跳過完全空白的行,但對于只有空格的行,需要在代碼中處理。
Q: 處理CSV時性能很慢怎么辦?
A: 1. 使用csv.writerows()批量寫入
- 考慮使用
pandas處理復雜操作 - 對于超大數(shù)據(jù),考慮使用數(shù)據(jù)庫
4.3 替代方案
- pandas:適合復雜的數(shù)據(jù)分析和處理
- 數(shù)據(jù)庫:適合大量數(shù)據(jù)和復雜查詢
- Excel/Google Sheets:適合需要手動查看和編輯的場景
- JSON/YAML:適合嵌套和結構化數(shù)據(jù)
- Parquet/Feather:適合大數(shù)據(jù)和機器學習場景
何時選擇替代方案:
- 需要復雜的數(shù)據(jù)分析和處理 → pandas
- 數(shù)據(jù)量極大,需要高效查詢 → 數(shù)據(jù)庫
- 需要多人協(xié)作和手動編輯 → Excel/Google Sheets
- 數(shù)據(jù)結構復雜,有嵌套 → JSON/YAML
- 大數(shù)據(jù)和機器學習場景 → Parquet/Feather
五、總結
通過本文的學習,你應該已經(jīng)掌握了:
- ? csv模塊基礎:如何讀寫各種格式的CSV文件
- ? 數(shù)據(jù)處理技巧:清洗、轉換、合并、拆分
- ? 性能優(yōu)化:流式處理、批量操作
- ? 實戰(zhàn)工具:構建自己的CSV處理工具箱
- ? 最佳實踐:編碼處理、錯誤處理、性能考慮
csv模塊的核心價值:
- 簡單直接:處理表格數(shù)據(jù)最直接的方式
- 通用性好:幾乎所有系統(tǒng)都支持CSV導入導出
- 零成本:Python自帶,無需額外依賴
- 透明可控:純文本格式,出現(xiàn)問題容易排查
- 生態(tài)豐富:與其他工具鏈完美集成
以上就是Python使用csv模塊進行文件讀寫的操作詳解的詳細內容,更多關于Python csv模塊文件讀寫的資料請關注腳本之家其它相關文章!
相關文章
Python結合FFmpeg實現(xiàn)為視頻添加內嵌字幕SRT的完整教程
這篇文章主要為大家詳細介紹了如何使用?Python?調用?FFmpeg,將?視頻文件(MP4)與字幕文件(SRT)進行無損合并,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下2025-11-11
python函數(shù)參數(shù)*args**kwargs用法實例
python當函數(shù)的參數(shù)不確定時,可以使用*args和**kwargs。*args沒有key值,**kwargs有key值,下面看例子2013-12-12
pandas數(shù)據(jù)的合并與拼接的實現(xiàn)
Pandas包的merge、join、concat方法可以完成數(shù)據(jù)的合并和拼接,本文主要介紹了這三種實現(xiàn)方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12

