Python 网络爬虫:Scrapy 解析汽车之家报价与评测

在汽车消费数字化的当下,汽车之家作为国内头部汽车资讯平台,汇聚了海量的车型报价、用户评测、配置参数等核心数据。借助 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 表达式和爬虫逻辑,确保爬虫的稳定性。

相关推荐
秦苒&2 小时前
【C语言】字符函数和字符串函数:字符分类函数 、字符转换函数 、 strlen 、strcpy、 strcat、strcmp的使用和模拟实现
c语言·开发语言
-Excalibur-2 小时前
关于计算机网络当中的各种计时器
java·c语言·网络·c++·笔记·python·计算机网络
小宇的天下2 小时前
Calibre nmDRC 运行机制与规则文件(13-1)
java·开发语言·数据库
太一吾鱼水2 小时前
matplotlib的imshow显示图片颜色正常,用cv2保存颜色不对
python
tangweiguo030519872 小时前
Objective-C 核心语法深度解析:基本类型、集合类与代码块实战指南
开发语言·ios·objective-c
傻啦嘿哟2 小时前
Python实现Excel数据自动化处理:从繁琐操作到智能流程的蜕变
python·自动化·excel
我命由我123452 小时前
Java 开发 - 含有 null 值字段的对象排序(自定义 Comparator、使用 Comparator、使用 Stream API)
java·开发语言·学习·java-ee·intellij-idea·学习方法·intellij idea
Amelia1111112 小时前
day39
python
聆风吟º2 小时前
【C++藏宝阁】C++介绍:从发展历程到现代应用
开发语言·c++·应用领域·发展历程·起源