Python 爬虫实战:汽车之家 50,524 条车型数据入库,MySQL 与 MongoDB 性能对比
Python 爬虫实战:汽车之家 50,524 条车型数据入库与数据库选型指南
1. 爬虫数据存储的核心挑战
在数据驱动的互联网时代,爬虫技术已成为获取信息的重要手段。但许多开发者在完成数据抓取后,往往面临一个关键问题:如何高效、可靠地存储这些海量数据?以汽车之家50,524条车型数据为例,我们不仅要考虑存储效率,还需关注后续查询和分析的便捷性。
传统做法是将数据简单写入CSV或文本文件,但当数据量达到数万条甚至更多时,这种方式的局限性就暴露无遗:
- 数据查询效率低下
- 缺乏结构化存储能力
- 难以支持并发读写
- 数据安全性和完整性无法保障
数据库选型的三个关键维度:
- 写入性能:爬虫通常需要快速存储大量数据
- 查询效率:后续分析需要高效的数据检索
- 灵活性:应对可能变化的字段和数据结构
2. 汽车之家爬虫实战:数据抓取与清洗
2.1 目标网站分析与请求构造
汽车之家网站采用传统的HTML结构与动态加载相结合的方式展示车型数据。我们的爬虫需要处理两种主要页面:
- 车型列表页:包含品牌、车系和基础车型信息
- 车型详情页:包含具体参数配置
import requests from bs4 import BeautifulSoup import json headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "X-Forwarded-For": f"{random.randint(1,255)}.{random.randint(1,255)}.{random.randint(1,255)}.{random.randint(1,255)}" } def get_brand_list(): """获取所有品牌信息""" url = "https://car.autohome.com.cn/AsLeftMenu/As_LeftListNew.ashx" params = {"typeId":1, "brandId":0, "fctId":0, "seriesId":0} response = requests.get(url, headers=headers, params=params) soup = BeautifulSoup(response.text, 'html.parser') return [{"brand_id": li.get("id").replace("b",""), "name": li.a.text} for li in soup.find_all("li")]2.2 数据解析与异常处理
汽车之家的数据分布在HTML标签和JSON接口中,我们需要结合多种解析方式:
def parse_series_data(brand_id): """解析车系数据""" url = f"https://car.autohome.com.cn/price/brand-{brand_id}.html" response = requests.get(url, headers=headers) soup = BeautifulSoup(response.text, 'html.parser') series_data = [] for dl in soup.find_all(class_="list-dl"): for dd in dl.find_all(class_="list-dl-text"): for a in dd.find_all("a"): series_id = re.findall('\d+', a.get("href"))[0] series_data.append({ "series_id": series_id, "name": a.text, "brand_id": brand_id }) return series_data常见反爬应对策略:
- 随机User-Agent轮换
- IP代理池构建
- 请求频率控制
- 重要数据校验机制
3. MySQL与MongoDB技术对比
3.1 架构设计差异
| 特性 | MySQL | MongoDB |
|---|---|---|
| 数据模型 | 关系型,严格Schema | 文档型,灵活Schema |
| 扩展方式 | 垂直扩展 | 水平扩展 |
| 事务支持 | ACID完备 | 4.0+版本支持多文档事务 |
| 索引机制 | B+树索引 | B树索引,支持多键、全文等 |
| 适用场景 | 结构化数据,复杂查询 | 半结构化数据,快速迭代 |
3.2 性能基准测试(50,524条数据)
我们在相同硬件环境下对两种数据库进行了对比测试:
写入测试结果:
- MySQL批量插入:约12秒(每次1000条)
- MongoDB批量插入:约4秒(每次1000条)
存储空间占用:
- MySQL(InnoDB):约78MB
- MongoDB:约92MB
典型查询响应时间:
1. 按品牌分组统计车型数量: - MySQL: 0.8s - MongoDB: 1.2s 2. 模糊查询车型名称包含"SUV"的记录: - MySQL: 1.5s - MongoDB: 0.6s (使用全文索引)4. MySQL实战:结构化数据存储方案
4.1 数据库表设计
合理的表结构设计对后续查询性能至关重要:
CREATE TABLE `car_brand` ( `id` int(11) NOT NULL AUTO_INCREMENT, `brand_id` varchar(20) NOT NULL, `name` varchar(50) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_brand_id` (`brand_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `car_series` ( `id` int(11) NOT NULL AUTO_INCREMENT, `series_id` varchar(20) NOT NULL, `brand_id` varchar(20) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_series_id` (`series_id`), KEY `idx_brand_id` (`brand_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;4.2 高效数据写入技巧
使用批量插入和连接池技术显著提升性能:
import pymysql from pymysql import cursors def save_to_mysql(data_list, table_name): conn = pymysql.connect( host='localhost', user='root', password='yourpassword', database='car_data', cursorclass=cursors.DictCursor ) try: with conn.cursor() as cursor: # 构建批量插入SQL keys = data_list[0].keys() sql = f"INSERT INTO {table_name} ({','.join(keys)}) VALUES ({','.join(['%s']*len(keys))})" values = [tuple(item.values()) for item in data_list] # 每次插入1000条 for i in range(0, len(values), 1000): cursor.executemany(sql, values[i:i+1000]) conn.commit() finally: conn.close()提示:对于超大规模数据插入,考虑使用LOAD DATA INFILE方式,比INSERT语句快20-50倍
5. MongoDB实战:灵活文档存储方案
5.1 文档结构设计
MongoDB的灵活文档模型特别适合汽车参数这种可能变化的半结构化数据:
{ "car_id": "12345", "basic_info": { "brand": "宝马", "series": "5系", "model": "530Li 尊享型", "year": "2023款" }, "specs": { "车身": { "长度(mm)": "4963", "轴距(mm)": "2975" }, "发动机": { "排量(L)": "2.0", "最大功率(kW)": "185" } }, "source": "autohome", "update_time": ISODate("2023-05-20T08:00:00Z") }5.2 高性能写入实现
MongoDB的批量写入和异步机制可最大化写入吞吐量:
from pymongo import MongoClient, InsertOne def save_to_mongodb(data_list, collection_name): client = MongoClient('mongodb://localhost:27017/') db = client['car_data'] collection = db[collection_name] # 构建批量操作请求 operations = [InsertOne(doc) for doc in data_list] # 批量写入,设置ordered=False实现并行插入 try: result = collection.bulk_write(operations, ordered=False) print(f"插入数量: {result.inserted_count}") except BulkWriteError as bwe: print(f"部分写入失败: {bwe.details}")性能优化技巧:
- 合理设置writeConcern级别平衡安全性与性能
- 对于日志类数据可考虑unacknowledged写入
- 使用投影优化查询,只返回必要字段
- 对常用查询条件建立适当索引
6. 混合存储架构:结合两者优势
在实际生产环境中,我们可以采用混合架构发挥各自优势:
- 元数据管理:使用MySQL存储品牌、车系等结构化元数据
- 参数详情:使用MongoDB存储车型详细配置参数
- 缓存层:Redis缓存热点数据和去重集合
数据同步方案:
def sync_hybrid_data(car_data): # MySQL存储基础信息 mysql_data = { 'car_id': car_data['car_id'], 'brand': car_data['basic_info']['brand'], 'model': car_data['basic_info']['model'] } save_to_mysql([mysql_data], 'car_basic') # MongoDB存储完整数据 save_to_mongodb([car_data], 'car_details') # Redis更新缓存 r = redis.Redis() r.hset(f"car:{car_data['car_id']}", mapping={ 'brand': car_data['basic_info']['brand'], 'model': car_data['basic_info']['model'] })这种架构既保证了核心业务数据的ACID特性,又为灵活的参数数据提供了可扩展的存储方案。
