8.1 写入 CSV 文件:csv 模块与 pandas.to_csv
CSV(Comma-Separated Values)是一种轻量级的表格数据格式,非常适合存储结构化爬虫数据。Python 提供了原生 csv 模块,而 pandas 则提供了更高级的 to_csv() 方法。
csv 模块 vs pandas.to_csv
| 功能名称 | 调用方法 | 具体功能与注意事项 |
|---|---|---|
| 基础写入 | csv.writer(file).writerow(row) | 适合简单数据,需手动处理编码和表头 |
| 批量写入 | writer.writerows(rows) | 一次性写入多行,效率较高 |
| pandas导出 | df.to_csv('file.csv', index=False) | 自动处理编码、缺失值,支持更多参数 |
下面先看 csv 模块的基础用法:
python
# 导入csv模块,用于操作CSV文件
import csv
# 准备要写入的数据,每行是一个列表
data = [
['标题', '价格', '链接'], # 表头
['iPhone 15', '5999', 'https://example.com/1'],
['MacBook Pro', '12999', 'https://example.com/2']
]
try:
# 以写入模式打开文件,指定utf-8-sig编码避免Excel乱码
with open('products.csv', 'w', newline='', encoding='utf-8-sig') as f:
# 创建csv写入器对象
writer = csv.writer(f)
# 逐行写入数据
for row in data:
writer.writerow(row)
print("CSV文件写入成功!")
except IOError as e:
# 捕获文件操作异常
print(f"文件写入失败: {e}")再来看 pandas 的优雅写法:
python
# 导入pandas库,用于数据处理
import pandas as pd
# 创建DataFrame对象,这是pandas的核心数据结构
df = pd.DataFrame({
'标题': ['iPhone 15', 'MacBook Pro'],
'价格': [5999, 12999],
'链接': ['https://example.com/1', 'https://example.com/2']
})
try:
# 将DataFrame导出为CSV文件
# index=False表示不保存行索引,encoding指定编码格式
df.to_csv('products_pandas.csv', index=False, encoding='utf-8-sig')
print("pandas CSV导出成功!")
except Exception as e:
# 捕获所有可能的异常
print(f"pandas导出失败: {e}")使用 csv 模块适合简单的数据写入场景,而 pandas 更适合处理复杂的数据结构和批量操作。
8.2 存入 JSON 文件:ensure_ascii 与缩进格式
JSON(JavaScript Object Notation)是另一种常用的数据交换格式,特别适合存储嵌套结构的数据。Python 的 json 模块提供了强大的 JSON 处理能力。
json.dump() 关键参数
| 功能名称 | 调用方法 | 具体功能与注意事项 |
|---|---|---|
| 基础序列化 | json.dump(data, file) | 将Python对象转换为JSON并写入文件 |
| 中文支持 | ensure_ascii=False | 必须设置,否则中文会变成Unicode转义 |
| 格式美化 | indent=2 | 添加缩进使JSON文件可读性更好 |
| 排序键 | sort_keys=True | 按键名排序,便于版本控制对比 |
基础 JSON 写入示例:
python
# 导入json模块,用于处理JSON数据
import json
# 准备要存储的爬虫数据,包含嵌套结构
product_data = {
"商品信息": {
"名称": "华为Mate60",
"价格": 6999,
"规格": ["12GB+512GB", "曜金黑"],
"评价": {
"评分": 4.8,
"评论数": 12580
}
},
"爬取时间": "2024-01-15 10:30:00"
}
try:
# 以写入模式打开JSON文件
with open('product.json', 'w', encoding='utf-8') as f:
# 序列化并写入文件
# ensure_ascii=False确保中文正常显示
# indent=2添加缩进提高可读性
json.dump(product_data, f, ensure_ascii=False, indent=2)
print("JSON文件保存成功!")
except (TypeError, ValueError) as e:
# 捕获JSON序列化相关的异常
print(f"JSON序列化失败: {e}")
except IOError as e:
# 捕获文件操作异常
print(f"文件写入失败: {e}")读取 JSON 文件也很简单:
python
# 从JSON文件读取数据
try:
with open('product.json', 'r', encoding='utf-8') as f:
# 反序列化JSON数据
loaded_data = json.load(f)
print("商品名称:", loaded_data["商品信息"]["名称"])
except FileNotFoundError:
print("JSON文件不存在")
except json.JSONDecodeError as e:
print(f"JSON格式错误: {e}")JSON 格式特别适合存储具有层次结构的爬虫数据,比如商品详情页的完整信息。
8.3 连接 SQLite / MySQL 存储结构化数据
对于需要频繁查询和更新的大量数据,关系型数据库是更好的选择。SQLite 适合小型项目,MySQL 适合大型应用。
数据库连接方法对比
| 功能名称 | 调用方法 | 具体功能与注意事项 |
|---|---|---|
| SQLite连接 | sqlite3.connect('file.db') | 无需安装服务器,单文件数据库 |
| MySQL连接 | pymysql.connect(**config) | 需要安装MySQL服务器和pymysql库 |
| 创建表 | cursor.execute(CREATE_SQL) | 定义数据表结构 |
| 插入数据 | cursor.execute(INSERT_SQL, values) | 使用参数化查询防止SQL注入 |
先看 SQLite 的使用:
python
# 导入sqlite3模块,Python内置支持
import sqlite3
# 商品数据列表
products = [
('iPhone 15', 5999, '苹果'),
('Galaxy S24', 6499, '三星'),
('Mate 60', 6999, '华为')
]
try:
# 连接到SQLite数据库(如果不存在会自动创建)
conn = sqlite3.connect('products.db')
# 创建游标对象用于执行SQL语句
cursor = conn.cursor()
# 创建商品表(如果不存在)
cursor.execute('''
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price INTEGER,
brand TEXT
)
''')
# 插入多条数据
cursor.executemany(
'INSERT INTO products (name, price, brand) VALUES (?, ?, ?)',
products
)
# 提交事务
conn.commit()
print(f"成功插入 {cursor.rowcount} 条记录")
except sqlite3.Error as e:
# 捕获SQLite相关异常
print(f"数据库操作失败: {e}")
finally:
# 确保关闭数据库连接
if conn:
conn.close()MySQL 连接示例(需要先安装 pip install pymysql):
python
# 导入pymysql模块,用于连接MySQL
import pymysql
# 数据库配置信息
db_config = {
'host': 'localhost',
'user': 'root',
'password': 'your_password',
'database': 'crawler_db',
'charset': 'utf8mb4'
}
try:
# 建立MySQL连接
conn = pymysql.connect(**db_config)
cursor = conn.cursor()
# 创建表
cursor.execute('''
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price INT,
brand VARCHAR(100)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
''')
# 插入数据
sql = "INSERT INTO products (name, price, brand) VALUES (%s, %s, %s)"
cursor.executemany(sql, products)
conn.commit()
print(f"MySQL插入成功,影响行数: {cursor.rowcount}")
except pymysql.MySQLError as e:
print(f"MySQL错误: {e}")
except Exception as e:
print(f"其他错误: {e}")
finally:
if conn:
conn.close()数据库存储适合需要长期保存、频繁查询或与其他系统集成的爬虫项目。
8.4 图片与文件下载:stream 模式与二进制写入
爬虫经常需要下载图片、PDF 或其他二进制文件。使用 requests 的 stream=True 参数可以高效处理大文件下载。
文件下载关键参数
| 功能名称 | 调用方法 | 具体功能与注意事项 |
|---|---|---|
| 流式下载 | requests.get(url, stream=True) | 避免一次性加载大文件到内存 |
| 二进制写入 | open(file, 'wb').write(chunk) | 必须使用二进制模式写入 |
| 获取文件名 | url.split('/')[-1] | 从URL提取文件名,需处理特殊情况 |
| 进度监控 | response.headers.get('content-length') | 获取文件总大小用于进度显示 |
基础图片下载示例:
python
# 导入requests库用于HTTP请求
import requests
# 导入os库用于文件路径操作
import os
# 图片URL列表
image_urls = [
'https://example.com/image1.jpg',
'https://example.com/image2.png'
]
# 创建下载目录
os.makedirs('images', exist_ok=True)
for url in image_urls:
try:
# 发送GET请求,启用流式下载
response = requests.get(url, stream=True, timeout=10)
# 检查响应状态
response.raise_for_status()
# 从URL提取文件名
filename = url.split('/')[-1]
# 如果URL没有文件扩展名,可以设置默认扩展名
if '.' not in filename:
filename += '.jpg'
# 构建完整的文件路径
filepath = os.path.join('images', filename)
# 以二进制写入模式打开文件
with open(filepath, 'wb') as f:
# 分块读取并写入文件
for chunk in response.iter_content(chunk_size=8192):
if chunk: # 过滤掉keep-alive的新块
f.write(chunk)
print(f"图片下载成功: {filename}")
except requests.RequestException as e:
# 捕获网络请求相关异常
print(f"下载失败 {url}: {e}")
except IOError as e:
# 捕获文件写入异常
print(f"文件写入失败 {filename}: {e}")带进度显示的文件下载:
python
# 导入tqdm用于进度条显示
from tqdm import tqdm
import requests
import os
def download_file_with_progress(url, folder='downloads'):
"""带进度条的文件下载函数"""
try:
# 创建下载目录
os.makedirs(folder, exist_ok=True)
# 获取文件名
filename = url.split('/')[-1] or 'downloaded_file'
filepath = os.path.join(folder, filename)
# 发送HEAD请求获取文件大小
head_response = requests.head(url, timeout=5)
total_size = int(head_response.headers.get('content-length', 0))
# 发送GET请求下载文件
response = requests.get(url, stream=True, timeout=30)
response.raise_for_status()
# 使用tqdm创建进度条
with open(filepath, 'wb') as f, tqdm(
desc=filename,
total=total_size,
unit='B',
unit_scale=True,
unit_divisor=1024,
) as pbar:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
pbar.update(len(chunk))
return True
except Exception as e:
print(f"下载失败 {url}: {e}")
return False
# 使用示例
download_file_with_progress('https://example.com/large_file.pdf')流式下载模式特别重要,它能有效处理大文件而不会耗尽内存,是专业爬虫的必备技能。