Skip to content

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 或其他二进制文件。使用 requestsstream=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')

流式下载模式特别重要,它能有效处理大文件而不会耗尽内存,是专业爬虫的必备技能。