在汽车消费数字化的当下,汽车之家作为国内头部汽车资讯平台,汇聚了海量的车型报价、用户评测、配置参数等核心数据。借助 Python 的 Scrapy 框架构建爬虫,能够高效、结构化地抓取这些数据,为汽车市场分析、消费趋势研究等场景提供数据支撑。本文将从环境搭建、爬虫架构设计、数据解析到持久化存储,完整讲解如何基于 Scrapy 实现汽车之家报价与评测数据的爬取。
一、技术基础与环境准备
1.1 核心技术选型
- Scrapy:作为 Python 生态中成熟的爬虫框架,内置了请求调度、数据解析、反爬处理等核心能力,相比原生 requests+BeautifulSoup 组合,具备更高的爬取效率和可扩展性。
- XPath/CSS Selector:汽车之家页面结构规整,通过 XPath 可精准定位报价、评测等核心数据节点,是解析 HTML 的最优选择。
- MySQL:用于结构化存储爬取的车型报价、评测内容等数据,支持后续的数据分析与查询。
1.2 环境搭建
首先确保已安装 Python(3.8 及以上版本)
二、Scrapy 爬虫项目搭建
2.1 创建项目
通过 Scrapy 命令行工具初始化爬虫项目
项目目录结构如下:
plaintext
plain
autohome_spider/
├── autohome_spider/
│ ├── __init__.py
│ ├── items.py # 定义数据结构
│ ├── middlewares.py # 中间件(反爬、请求处理)
│ ├── pipelines.py # 数据管道(存储到MySQL)
│ ├── settings.py # 全局配置
│ └── spiders/ # 爬虫脚本目录
└── scrapy.cfg # 项目配置文件
2.2 定义数据结构(items.py)
根据爬取目标,定义<font style="color:rgba(0, 0, 0, 0.85) !important;">CarQuoteItem</font>(车型报价)和<font style="color:rgba(0, 0, 0, 0.85) !important;">CarReviewItem</font>(车型评测)两个数据类,明确字段类型:
python
import scrapy
class CarQuoteItem(scrapy.Item):
"""车型报价数据结构"""
# 车型ID(唯一标识)
car_id = scrapy.Field()
# 车型名称
car_name = scrapy.Field()
# 官方指导价
guide_price = scrapy.Field()
# 经销商参考价
dealer_price = scrapy.Field()
# 车型级别(紧凑型/中型等)
car_level = scrapy.Field()
# 爬取时间
crawl_time = scrapy.Field()
class CarReviewItem(scrapy.Item):
"""车型评测数据结构"""
# 评测ID
review_id = scrapy.Field()
# 关联车型ID
car_id = scrapy.Field()
# 评测标题
review_title = scrapy.Field()
# 评测作者
review_author = scrapy.Field()
# 评测内容
review_content = scrapy.Field()
# 发布时间
publish_time = scrapy.Field()
三、核心爬虫逻辑实现
3.1 编写爬虫脚本(spiders/autohome_spider.py)
爬虫的核心逻辑分为三步:请求车型列表页、解析车型报价、请求评测页并解析评测内容。以汽车之家 "紧凑型车" 分类为例(URL:https://car.autohome.com.cn/xuanche/bigclass/3.html):
python
运行
python
import scrapy
import time
from autohome_spider.items import CarQuoteItem, CarReviewItem
class AutoHomeSpider(scrapy.Spider):
# 爬虫名称
name = "autohome"
# 允许爬取的域名
allowed_domains = ["car.autohome.com.cn"]
# 起始URL
start_urls = ["https://car.autohome.com.cn/xuanche/bigclass/3.html"]
def parse(self, response):
"""解析车型列表页,提取车型报价并请求评测页"""
# 定位车型列表节点(XPath通过浏览器开发者工具获取)
car_list = response.xpath('//div[@class="list-cont-main"]/div[@class="car-box"]')
for car in car_list:
# 1. 解析车型报价数据
quote_item = CarQuoteItem()
# 车型ID(从车型链接中提取)
car_link = car.xpath('.//a[@class="name"]/@href').extract_first()
quote_item["car_id"] = car_link.split('/')[-2] if car_link else ""
# 车型名称
quote_item["car_name"] = car.xpath('.//a[@class="name"]/text()').extract_first().strip()
# 官方指导价
quote_item["guide_price"] = car.xpath('.//div[@class="price"]/span[@class="guide-price"]/text()').extract_first().strip()
# 经销商参考价
quote_item["dealer_price"] = car.xpath('.//div[@class="price"]/span[@class="deal-price"]/text()').extract_first().strip()
# 车型级别
quote_item["car_level"] = "紧凑型车"
# 爬取时间
quote_item["crawl_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
# 输出报价数据(调试用)
self.logger.info(f"爬取报价:{quote_item['car_name']} - {quote_item['guide_price']}")
# 提交报价数据到管道
yield quote_item
# 2. 构造评测页URL并请求(评测页URL规则:https://k.autohome.com.cn/3123/ 其中3123为车型ID)
if quote_item["car_id"]:
review_url = f"https://k.autohome.com.cn/{quote_item['car_id']}/"
# 请求评测页,携带车型ID用于关联数据
yield scrapy.Request(
url=review_url,
callback=self.parse_review,
meta={"car_id": quote_item["car_id"]}
)
# 分页处理(解析下一页URL)
next_page = response.xpath('//a[@class="page-item-next"]/@href').extract_first()
if next_page:
next_url = response.urljoin(next_page)
yield scrapy.Request(url=next_url, callback=self.parse)
def parse_review(self, response):
"""解析评测页,提取评测内容"""
# 获取关联的车型ID
car_id = response.meta["car_id"]
# 定位评测列表节点
review_list = response.xpath('//div[@class="review-list"]/div[@class="review-item"]')
for review in review_list:
review_item = CarReviewItem()
# 评测ID
review_link = review.xpath('.//a[@class="review-title"]/@href').extract_first()
review_item["review_id"] = review_link.split('/')[-1].split('.')[0] if review_link else ""
# 关联车型ID
review_item["car_id"] = car_id
# 评测标题
review_item["review_title"] = review.xpath('.//a[@class="review-title"]/text()').extract_first().strip()
# 评测作者
review_item["review_author"] = review.xpath('.//span[@class="name"]/text()').extract_first().strip()
# 评测内容(截取前200字,完整内容可请求详情页)
review_content = review.xpath('.//div[@class="review-content"]/text()').extract_first()
review_item["review_content"] = review_content.strip()[:200] if review_content else ""
# 发布时间
review_item["publish_time"] = review.xpath('.//span[@class="time"]/text()').extract_first().strip()
# 输出评测数据(调试用)
self.logger.info(f"爬取评测:{review_item['review_title']} - {review_item['review_author']}")
# 提交评测数据到管道
yield review_item
3.2 配置全局参数(settings.py)
修改<font style="color:rgba(0, 0, 0, 0.85) !important;">settings.py</font>,配置请求头、并发数、管道优先级等核心参数,避免被反爬:
python
运行
python
# 爬虫名称
BOT_NAME = "autohome_spider"
# 爬虫模块路径
SPIDER_MODULES = ["autohome_spider.spiders"]
NEWSPIDER_MODULE = "autohome_spider.spiders"
# 遵守robots协议(正式爬取可改为False)
ROBOTSTXT_OBEY = True
# 请求头(模拟浏览器)
DEFAULT_REQUEST_HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
}
# 并发请求数(避免过高触发反爬)
CONCURRENT_REQUESTS = 5
# 每个域名的并发数
CONCURRENT_REQUESTS_PER_DOMAIN = 3
# 请求延迟(1秒)
DOWNLOAD_DELAY = 1
# 启用管道(数字越小优先级越高)
ITEM_PIPELINES = {
"autohome_spider.pipelines.AutohomeSpiderPipeline": 300,
}
# 日志级别(调试用DEBUG,正式爬取用INFO)
LOG_LEVEL = "INFO"
四、数据持久化存储(pipelines.py)
将爬取的报价和评测数据存储到 MySQL 数据库,首先在 MySQL 中创建两张表:
sql
sql
-- 创建车型报价表
CREATE TABLE car_quote (
id INT AUTO_INCREMENT PRIMARY KEY,
car_id VARCHAR(50) NOT NULL COMMENT '车型ID',
car_name VARCHAR(100) NOT NULL COMMENT '车型名称',
guide_price VARCHAR(20) COMMENT '官方指导价',
dealer_price VARCHAR(20) COMMENT '经销商参考价',
car_level VARCHAR(20) COMMENT '车型级别',
crawl_time DATETIME COMMENT '爬取时间',
UNIQUE KEY uk_car_id (car_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 创建车型评测表
CREATE TABLE car_review (
id INT AUTO_INCREMENT PRIMARY KEY,
review_id VARCHAR(50) NOT NULL COMMENT '评测ID',
car_id VARCHAR(50) NOT NULL COMMENT '车型ID',
review_title VARCHAR(200) NOT NULL COMMENT '评测标题',
review_author VARCHAR(50) COMMENT '评测作者',
review_content TEXT COMMENT '评测内容',
publish_time VARCHAR(20) COMMENT '发布时间',
UNIQUE KEY uk_review_id (review_id),
KEY idx_car_id (car_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
然后编写管道代码,实现数据插入 MySQL:
python
运行
python
import pymysql
from pymysql.err import IntegrityError
class AutohomeSpiderPipeline:
def __init__(self):
# 初始化数据库连接
self.conn = pymysql.connect(
host="localhost", # 数据库地址
port=3306, # 端口
user="root", # 用户名
password="123456", # 密码
database="autohome", # 数据库名
charset="utf8mb4"
)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
"""处理爬取的Item,插入对应数据表"""
try:
if isinstance(item, CarQuoteItem):
# 插入车型报价数据
sql = """
INSERT INTO car_quote (car_id, car_name, guide_price, dealer_price, car_level, crawl_time)
VALUES (%s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
guide_price=VALUES(guide_price), dealer_price=VALUES(dealer_price), crawl_time=VALUES(crawl_time)
"""
self.cursor.execute(
sql,
(item["car_id"], item["car_name"], item["guide_price"], item["dealer_price"], item["car_level"], item["crawl_time"])
)
elif isinstance(item, CarReviewItem):
# 插入车型评测数据
sql = """
INSERT INTO car_review (review_id, car_id, review_title, review_author, review_content, publish_time)
VALUES (%s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
review_title=VALUES(review_title), review_content=VALUES(review_content)
"""
self.cursor.execute(
sql,
(item["review_id"], item["car_id"], item["review_title"], item["review_author"], item["review_content"], item["publish_time"])
)
# 提交事务
self.conn.commit()
except IntegrityError as e:
# 主键冲突时回滚
self.conn.rollback()
spider.logger.error(f"数据插入失败:{e}")
except Exception as e:
self.conn.rollback()
spider.logger.error(f"数据库异常:{e}")
return item
def close_spider(self, spider):
"""爬虫结束时关闭数据库连接"""
self.cursor.close()
self.conn.close()
spider.logger.info("数据库连接已关闭")
五、反爬策略与优化建议
5.1 反爬应对
- 请求频率控制 :通过
<font style="color:rgb(0, 0, 0);">DOWNLOAD_DELAY</font>设置请求延迟,避免短时间内大量请求; - User-Agent 轮换 :在
<font style="color:rgb(0, 0, 0);">middlewares.py</font>中实现 User-Agent 池,随机切换请求头; - IP 代理池:若爬取量较大,可接入 IP 代理池,避免 IP 被封禁;推荐使用亿牛云隧道转发代理
- Cookie 处理 :部分页面需要登录,可在
<font style="color:rgb(0, 0, 0);">settings.py</font>中配置 Cookie。
5.2 性能优化
- 异步存储 :使用
<font style="color:rgb(0, 0, 0);">twisted</font>异步 MySQL 驱动替代<font style="color:rgb(0, 0, 0);">pymysql</font>,提升数据存储效率; - 增量爬取:记录已爬取的车型 ID 和评测 ID,避免重复爬取;
- 分布式爬取:基于 Scrapy-Redis 实现分布式爬虫,提升大规模数据爬取效率。
六、总结
本文基于 Scrapy 框架实现了汽车之家紧凑型车报价与评测数据的爬取,从项目搭建、数据解析到持久化存储,完整覆盖了爬虫开发的核心流程。通过 XPath 精准定位页面节点,结合 MySQL 实现结构化数据存储,最终得到可直接用于分析的汽车数据。
需要注意的是,爬取数据时需遵守网站的<font style="color:rgba(0, 0, 0, 0.85) !important;">robots.txt</font>协议和相关法律法规,不得用于商业侵权用途。同时,汽车之家的页面结构可能会更新,需定期维护 XPath 表达式和爬虫逻辑,确保爬虫的稳定性。