Scrapy 分布式爬虫:大规模采集汽车之家电车评论

汽车之家电车评论包含车型体验、续航表现等关键信息,是产品分析与市场调研的核心数据源。单台机器运行Scrapy爬虫易触发反爬、效率低下,分布式爬虫通过多机器协同,可有效解决这一问题。本文将精简讲解Scrapy分布式爬虫的搭建、配置、开发及部署,附带完整可运行代码,助力开发者快速实现大规模评论采集。

一、核心技术栈与环境准备

搭建Scrapy分布式爬虫需多组件协同,核心配置如下:

1.1 核心技术选型

  • Scrapy:核心爬虫框架,负责请求、解析与调度,支持中间件扩展。
  • Scrapy-Redis:分布式核心,替代本地调度器,实现多机器任务共享与去重。
  • Redis:存储待爬URL、去重集合,支持高并发访问。
  • Python 3.8+ :确保依赖兼容性;MySQL:结构化存储评论数据。
  • 亿牛云代理www.16yun.cn):动态切换IP,规避反爬封禁。

1.2 环境搭建步骤

  1. 安装依赖pip install scrapy scrapy-redis redis pymysql requests
  2. Redis配置 :安装后修改redis.conf,注释bind 127.0.0.1、关闭protected-mode no,允许分布式节点访问。
  3. MySQL准备 :创建数据库和表,SQL语句如下: CREATE DATABASE IF NOT EXISTS car_home_ev;USE car_home_ev;CREATE TABLE IF NOT EXISTS ev_comments (id INT PRIMARY KEY AUTO_INCREMENT,car_model VARCHAR(50) NOT NULL,user_name VARCHAR(30),comment_time VARCHAR(20),comment_content TEXT NOT NULL,score INT,mileage VARCHAR(20),crawl_time DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  4. 代理配置:注册亿牛云代理,获取服务器地址、账号密码备用。

二、Scrapy分布式爬虫核心原理

传统Scrapy爬虫数据存储于单台机器内存,无法协同。Scrapy-Redis重写调度器与去重器,将核心数据存入Redis,实现多机器共享任务:

  1. 任一节点启动爬虫,将初始URL存入Redis待爬队列(scrapy:requests)。
  2. 所有节点连接Redis,获取URL执行爬取,解析数据与新URL。
  3. 新URL经Redis去重(scrapy:dupefilter)后加入队列,数据存入MySQL。
  4. 待爬队列为空时,爬虫终止,单节点故障不影响整体任务。

三、实战开发:Scrapy分布式爬虫搭建(附代码)

以下代码可直接复制运行,仅需修改代理、数据库等个人配置。

3.1 创建项目与分布式配置

3.1.1 创建项目
bash 复制代码
# 创建项目
scrapy startproject car_home_ev_spider
cd car_home_ev_spider
# 创建爬虫(指定域名)
scrapy genspider ev_comment autohome.com.cn
3.1.2 settings.py核心配置
python 复制代码
# -*- coding: utf-8 -*-
BOT_NAME = 'car_home_ev_spider'
SPIDER_MODULES = ['car_home_ev_spider.spiders']
NEWSPIDER_MODULE = 'car_home_ev_spider.spiders'

ROBOTSTXT_OBEY = False  # 关闭robots协议
# 分布式核心配置
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_URL = 'redis://192.168.1.100:6379/0'  # 替换自身Redis地址
SCHEDULER_PERSIST = False
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'

# 反爬与请求配置
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    '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',
    'Referer': 'https://www.autohome.com.cn/',
    'Cookie': '填写自身汽车之家Cookie'
}
DOWNLOAD_DELAY = 1.5
CONCURRENT_REQUESTS = 16

# 代理与数据库配置
PROXY_URL = 'http://www.16yun.cn:8080'
PROXY_USER = '你的代理用户名'
PROXY_PASS = '你的代理密码'
MYSQL_HOST = 'localhost'
MYSQL_USER = 'root'
MYSQL_PASSWORD = '你的MySQL密码'
MYSQL_DB = 'car_home_ev'
MYSQL_PORT = 3306

# 启用Pipeline
ITEM_PIPELINES = {
    'car_home_ev_spider.pipelines.CarHomeEvSpiderPipeline': 300,
}
LOG_LEVEL = 'INFO'
LOG_FILE = 'car_home_ev.log'

3.2 数据模型(items.py

python 复制代码
# -*- coding: utf-8 -*-
import scrapy

class CarHomeEvSpiderItem(scrapy.Item):
    car_model = scrapy.Field()  # 车型
    user_name = scrapy.Field()  # 用户名
    comment_time = scrapy.Field()  # 评论时间
    comment_content = scrapy.Field()  # 评论内容
    score = scrapy.Field()  # 评分
    mileage = scrapy.Field()  # 行驶里程
    crawl_time = scrapy.Field()  # 爬取时间

3.3 代理中间件(middlewares.py

python 复制代码
# -*- coding: utf-8 -*-
import base64
from scrapy import signals
from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware
from car_home_ev_spider.settings import PROXY_URL, PROXY_USER, PROXY_PASS

class CarHomeEvSpiderMiddleware:
    @classmethod
    def from_crawler(cls, crawler):
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s
    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

# 亿牛云代理中间件
class ProxyMiddleware(HttpProxyMiddleware):
    def __init__(self, auth_encoding='latin-1'):
        self.auth_encoding = auth_encoding
        self.proxy_auth = base64.b64encode(f"{PROXY_USER}:{PROXY_PASS}".encode(self.auth_encoding)).decode(self.auth_encoding)
    def process_request(self, request, spider):
        request.meta['proxy'] = PROXY_URL
        request.headers['Proxy-Authorization'] = f'Basic {self.proxy_auth}'

# 反反爬中间件
class AntiAntiCrawlMiddleware:
    def process_request(self, request, spider):
        request.headers['Cookie'] = spider.settings.get('DEFAULT_REQUEST_HEADERS')['Cookie']
        return None

在settings.py中添加中间件配置:

python 复制代码
DOWNLOADER_MIDDLEWARES = {
    'car_home_ev_spider.middlewares.ProxyMiddleware': 543,
    'car_home_ev_spider.middlewares.AntiAntiCrawlMiddleware': 544,
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': None,
}

3.4 核心爬虫(ev_comment.py)

python 复制代码
# -*- coding: utf-8 -*-
import scrapy
from car_home_ev_spider.items import CarHomeEvSpiderItem

class EvCommentSpider(scrapy.Spider):
    name = 'ev_comment'
    allowed_domains = ['autohome.com.cn']
    start_urls = ['https://www.autohome.com.cn/electric/']  # 电车列表页

    def parse(self, response):
        # 解析车型列表,获取评论页URL
        car_list = response.xpath('//div[@class="list-cont"]/div[@class="cont-box"]')
        for car in car_list:
            car_model = car.xpath('.//h3/a/text()').extract_first().strip()
            comment_url = car.xpath('.//div[@class="btn-box"]/a[contains(text(), "口碑")]/@href').extract_first()
            if comment_url:
                yield scrapy.Request(
                    url=response.urljoin(comment_url),
                    callback=self.parse_comment_list,
                    meta={'car_model': car_model},
                    dont_filter=True
                )
        # 列表页分页
        next_page = response.xpath('//a[@class="page-item-next"]/@href').extract_first()
        if next_page:
            yield scrapy.Request(url=response.urljoin(next_page), callback=self.parse, dont_filter=True)

    def parse_comment_list(self, response):
        # 解析评论数据
        car_model = response.meta['car_model']
        comment_list = response.xpath('//div[@class="koubei-list"]/div[@class="koubei-item"]')
        for comment in comment_list:
            item = CarHomeEvSpiderItem()
            item['car_model'] = car_model
            item['user_name'] = comment.xpath('.//div[@class="user-info"]/a/text()').extract_first() or '未知用户'
            item['comment_time'] = comment.xpath('.//div[@class="user-info"]/span/text()').extract_first() or ''
            comment_content = comment.xpath('.//div[@class="content"]/p/text()').extract()
            item['comment_content'] = ''.join([text.strip() for text in comment_content]) if comment_content else ''
            score_class = comment.xpath('.//div[@class="score"]/span/@class').extract_first()
            item['score'] = int(score_class.replace('star', '')) if score_class else None
            item['mileage'] = comment.xpath('.//div[@class="info"]/span[1]/text()').extract_first().strip() if comment.xpath('.//div[@class="info"]/span[1]/text()').extract_first() else '未知'
            item['crawl_time'] = None
            yield item
        # 评论页分页
        next_comment_page = response.xpath('//a[@class="page-next"]/@href').extract_first()
        if next_comment_page:
            yield scrapy.Request(
                url=response.urljoin(next_comment_page),
                callback=self.parse_comment_list,
                meta={'car_model': car_model},
                dont_filter=True
            )

3.5 数据存储Pipeline(pipelines.py

python 复制代码
# -*- coding: utf-8 -*-
import pymysql
from datetime import datetime
from car_home_ev_spider.settings import MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB, MYSQL_PORT

class CarHomeEvSpiderPipeline:
    def __init__(self):
        # 初始化数据库连接
        self.conn = pymysql.connect(
            host=MYSQL_HOST, user=MYSQL_USER, password=MYSQL_PASSWORD,
            database=MYSQL_DB, port=MYSQL_PORT, charset='utf8mb4'
        )
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        item['crawl_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        # 插入数据,避免重复
        sql = """
        INSERT INTO ev_comments (car_model, user_name, comment_time, comment_content, score, mileage, crawl_time)
        VALUES (%s, %s, %s, %s, %s, %s, %s)
        ON DUPLICATE KEY UPDATE comment_time = VALUES(comment_time), score = VALUES(score)
        """
        try:
            self.cursor.execute(sql, (item['car_model'], item['user_name'], item['comment_time'],
                                      item['comment_content'], item['score'], item['mileage'], item['crawl_time']))
            self.conn.commit()
        except Exception as e:
            self.conn.rollback()
            spider.logger.error(f"插入失败:{str(e)}")
        return item

    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()

四、分布式部署与运行

4.1 节点准备

多台机器需与Redis服务器同局域网,安装相同环境,复制项目并确保所有节点REDIS_URL、代理、数据库配置一致。

4.2 启动步骤

  1. 启动Redis服务,通过redis-cli ping测试连接。
  2. 各节点进入项目目录,执行scrapy crawl ev_comment启动爬虫。
  3. 通过redis-cli keys *查看任务队列,通过MySQL查询实时查看爬取数据。

五、反爬应对与优化

  • 控制请求频率,调整DOWNLOAD_DELAY,节点数量控制在5-10台。
  • 定期更新Cookie,及时适配汽车之家页面变化,调整XPath表达式。
  • 增加异常捕获与重试机制,通过日志排查问题,可实现增量爬取减少重复工作。

六、总结与扩展

本文实现了Scrapy分布式爬虫采集汽车之家电车评论,解决了单机器爬取效率低、易反爬的问题。后续可扩展情感分析、数据可视化、定时爬取等功能,适配多汽车平台,进一步提升数据价值。分布式爬虫的核心是协同高效,需根据反爬强度灵活调整策略,确保稳定采集。

相关推荐
HSunR2 小时前
java springboot3 后端 基础框架
java·开发语言
Mr_Xuhhh2 小时前
算法刷题笔记:从滑动窗口到哈夫曼编码,我的算法进阶之路
开发语言·算法
七夜zippoe2 小时前
Java技术未来展望:GraalVM、Quarkus、Helidon等新趋势探讨
java·开发语言·python·quarkus·graaivm·helidon
枫叶落雨2222 小时前
ClassPathXmlApplicationContext
java·开发语言
跨境麦香鱼2 小时前
Playwright vs Puppeteer:2026自动化任务与爬虫工具如何选?
运维·爬虫·自动化
北冥有羽Victoria2 小时前
OpenCLI 操作网页 从0到1完整实操指南
vscode·爬虫·python·github·api·ai编程·opencli
深蓝电商API2 小时前
反向海淘商品信息爬虫开发实战
爬虫·跨境电商·反向海淘
十五年专注C++开发2 小时前
Oat++: 一个轻量级、高性能、零依赖的 C++ Web 框架
开发语言·c++·web服务·oatpp
小恰学逆向2 小时前
【爬虫JS逆向之旅】某球网参数“md5__1038”逆向
javascript·爬虫