4
数据存储:CSV / JSON / MySQL
Storing Scraped Data
爬下来的数据总得存。小数据存 CSV/JSON 用 Excel 打开就行,大数据进数据库。这章把常用存储方式过一遍。
CSV 存储
import csv
# csv.writer:按行写列表
with open("data.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f)
w.writerow(["名字", "年龄"]) # 表头
w.writerow(["小明", 18])
w.writerow(["小红", 20])
# utf-8-sig:Excel 打开不乱码
# Pandas 一把梭:df.to_csv("data.csv", index=False)
JSON 存储
import json
data = [{"title": "Python", "views": 100},
{"title": "爬虫", "views": 200}]
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# ensure_ascii=False:中文不转义;indent=2:缩进好看
数据库存储:MySQL
pip install pymysql
import pymysql
conn = pymysql.connect(host="localhost", user="root",
password="123", database="spider",
charset="utf8mb4")
cur = conn.cursor()
# 建表
cur.execute("""CREATE TABLE IF NOT EXISTS articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200),
url VARCHAR(500),
UNIQUE KEY uk_url (url)
)""")
# 插入(去重:用 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE)
sql = "INSERT IGNORE INTO articles (title, url) VALUES (%s, %s)"
cur.execute(sql, ("Python入门", "https://a.com/1"))
conn.commit()
cur.close()
conn.close()
完整案例:数据存入 MySQL
# crawl_to_mysql.py
# 1. 建表 SQL
CREATE_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
url VARCHAR(500) UNIQUE,
author VARCHAR(50),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
"""
# 2. 插入(INSERT IGNORE 去重)
INSERT_SQL = "INSERT IGNORE INTO articles (title, url, author) VALUES (%s, %s, %s)"
# 3. 爬虫主流程
import pymysql, requests
from bs4 import BeautifulSoup
conn = pymysql.connect(host="localhost", user="root",
password="123", database="spider",
charset="utf8mb4")
cur = conn.cursor()
cur.execute(CREATE_TABLE_SQL)
resp = requests.get("https://example.com/news")
soup = BeautifulSoup(resp.text, "lxml")
for item in soup.select(".item"):
title = item.select_one(".title").text
url = item.select_one("a")["href"]
cur.execute(INSERT_SQL, (title, url, ""))
conn.commit()
print(f"插入 {cur.rowcount} 行")
SQL 注入与参数化
永远用 %s 占位符,别自己拼 SQL 字符串。f"INSERT ... VALUES ('{title}')"——title 里要是带个引号,SQL 就坏了,还可能被注入。cur.execute(sql, (title, url)) 让库帮你转义,安全又省心。
MongoDB:存"形状不固定"的数据
爬来的数据经常字段不统一:这条有"评论数"那条没有,这条多俩"标签"。硬塞进 MySQL 表结构反而别扭。MongoDB 是文档型数据库,每条记录就是一个 JSON 文档,字段随便加,特别适合存爬虫结果。
论大白话:MySQL 是 Excel,MongoDB 是一堆 dict
MySQL 要求先定表头(建表),每行列数固定;MongoDB 像一个大 list,里面每个元素是个 dict,各 dict 的 key 可以不一样。pymongo 就是它的 Python 客户端。
用 pymongo 插入和查询
pip install pymongo # 以官网最新稳定版为准;MongoDB 服务要先起来
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client["spider"] # 选库,不存在会自动建
col = db["movies"] # 选集合(相当于表)
# 插入一条(就是个 dict)
col.insert_one({"title": "霸王别姬", "score": 9.6, "tags": ["经典", "爱情"]})
# 批量插入
col.insert_many([
{"title": "活着", "score": 9.4},
{"title": "无间道", "score": 9.3, "region": "香港"}, # 多个 region 字段,MySQL 会报错,MongoDB 随便
])
# 查询:score 大于 9.3 的,按分数倒序
for m in col.find({"score": {"$gt": 9.3}}).sort("score", -1):
print(m["title"], m["score"])
# 霸王别姬 9.6
# 活着 9.4
防坑:去重靠唯一索引
爬虫重跑会重复插入。先给 title 建唯一索引:col.create_index("title", unique=True),之后重复 insert_one 会报错,配合 update_one(..., upsert=True) 就能"有则更新、无则插入",天然支持增量爬取。
练习:选库小测(点开对答案)
问:爬商品评论,每条评论字段经常变、数量千万级,用 MySQL 还是 MongoDB?
答:MongoDB。字段不固定、文档型、水平扩容方便,正好吃这场景。但如果你后续要按复杂条件做多表 join 统计报表,关系型的 MySQL 反而更顺手。
其他存储一览
| 存储 | 什么时候用 |
|---|---|
| CSV | 小数据、给 Excel 看 |
| JSON | 嵌套结构、给程序读 |
| SQLite | 单文件数据库,零配置,小项目首选 |
| MySQL | 生产环境、多人查 |
| MongoDB | 非结构化数据、文档型 |
| Excel | openpyxl / Pandas to_excel |