基于Scrapy-Redis的亚马逊Best Sellers榜单分布式爬虫实战指南 1. 项目概述为什么需要分布式爬取亚马逊榜单做数据抓取的朋友尤其是盯上亚马逊这类大型电商平台的估计都经历过单机爬虫的“阵痛期”。当你兴致勃勃地写了个Scrapy爬虫准备把亚马逊Best Sellers排行榜我们简称Best榜的数据一网打尽时最初可能很顺利。但随着抓取深度的增加——比如不仅要榜单列表还要每个商品详情页的评论、价格历史、库存状态——问题就来了。IP被封、请求速率限制、海量URL管理混乱、一台机器跑几天几夜也抓不完…… 这就像一个人想搬空一个巨型仓库效率低下且容易累垮。这时候“分布式爬虫”就成了必然选择。而scrapy-redis这个组件正是将Scrapy这个强大的爬虫框架与Redis这个高性能的内存数据库连接起来的桥梁让多个爬虫实例可以协同工作。简单说它解决了两个核心问题统一的任务调度去重与分发和统一的数据存储。所有爬虫从同一个Redis队列里领取任务URL并将抓取到的数据推送到同一个Redis管道再由一个统一的进程进行后续处理如存入数据库。这样你可以轻松地增加爬虫节点机器来提升抓取速度并且单个节点的失效不会导致任务丢失。抓取亚马逊Best榜恰恰是分布式爬虫一个非常典型且高价值的应用场景。这个榜单更新频繁品类繁多商品数量巨大且页面结构复杂反爬措施严格。通过分布式架构我们不仅能以更快的速度获取全量数据还能更稳定地应对IP封锁等问题实现7x24小时不间断的监控。接下来我就结合自己搭建这套系统的实际经验从设计思路到避坑细节完整地拆解一遍。2. 核心架构与组件选型解析在动手写代码之前理清架构是关键。一个基于scrapy-redis的分布式爬虫系统通常由以下几部分组成2.1 核心组件职责分工Redis服务器系统的“大脑”和“中枢神经”。它主要负责请求队列存储所有待抓取的URLScrapy中的Request对象。scrapy-redis默认使用PriorityQueue优先级队列确保任务有序执行。请求去重指纹集合利用Redis的Set类型存储所有已调度URL的指纹通常是SHA1哈希实现高效的全局去重避免多个爬虫节点重复抓取同一页面。数据管道临时存储爬虫抓取到的Item数据等待后续的持久化处理。统计信息存储爬虫运行状态如已抓取数量、剩余请求数等。多个Scrapy爬虫节点Worker系统的“四肢”。这些是运行在不同机器或进程上的、一模一样的Scrapy爬虫项目。它们不再拥有自己的本地任务队列而是从共享的Redis请求队列中获取下一个要抓取的URL。执行网页下载、解析。将解析出的新URL比如“下一页”或商品详情链接的Request对象放回共享的Redis请求队列。将解析出的数据Item推送到共享的Redis数据管道。数据后处理进程可选系统的“消化系统”。这是一个独立的进程可以用Scrapy的Item Pipeline扩展也可以单独写脚本持续监听Redis中的数据管道将Item取出并进行后续操作如清洗、验证、存入MySQL/MongoDB等数据库。2.2 为什么是scrapy-redis而不是其他分布式爬虫方案有很多比如纯手工用消息队列RabbitMQ, Kafka自己封装调度器。但scrapy-redis有不可替代的优势与Scrapy无缝集成它通过重写Scrapy的Scheduler调度器和DupeFilter去重过滤器几乎零成本地将一个普通Scrapy项目改造为分布式。你绝大部分的爬虫代码Spider, Item, Pipeline都不需要改变。开箱即用的健壮性它已经处理了分布式环境下的很多细节比如连接重试、空队列等待、数据序列化等。自己从头实现一套容易在边界条件上出错。社区成熟资料丰富作为最经典的Scrapy分布式方案遇到问题很容易找到解决方案和社区讨论。关于Redis版本的选择建议使用Redis 5.0及以上版本。它更稳定功能也更完善。对于这个项目我们主要用到其基础的数据结构功能无需特殊模块。3. 环境准备与项目初始化3.1 基础环境搭建假设我们使用两台Linux服务器可以是云主机作为爬虫节点一台作为Redis服务器也可以与其中一台爬虫节点共用但生产环境建议分离。在Redis服务器上# 安装Redis sudo apt-get update sudo apt-get install redis-server -y # 编辑配置文件允许远程连接并设置密码强烈建议 sudo vim /etc/redis/redis.conf需要修改的关键配置bind 0.0.0.0 # 监听所有网络接口允许远程连接 protected-mode no # 关闭保护模式在配置了bind和密码后更安全 requirepass your_strong_password_here # 设置一个强密码 daemonize yes # 以守护进程运行# 重启Redis使配置生效 sudo systemctl restart redis-server # 检查是否运行并监听6379端口 sudo netstat -tlnp | grep 6379在所有爬虫节点上# 安装Python3、pip及虚拟环境工具 sudo apt-get install python3 python3-pip -y pip3 install virtualenv # 创建项目目录并进入 mkdir -p ~/projects/amazon_distributed_spider cd ~/projects/amazon_distributed_spider # 创建虚拟环境并激活 virtualenv venv source venv/bin/activate # 安装核心依赖 pip install scrapy scrapy-redis redis注意scrapy-redis库的活跃度需要关注。如果遇到安装或兼容性问题可以指定稍旧但稳定的版本如pip install scrapy-redis0.7.2。同时根据亚马逊页面的实际情况你可能还需要安装用于解析动态内容的库如selenium或playwright但初期建议先尝试用scrapy直接抓取因为Best榜页面大多是静态或服务端渲染的。3.2 创建Scrapy项目并集成scrapy-redis# 在虚拟环境中创建标准的Scrapy项目 scrapy startproject amazon_best cd amazon_best接下来是改造项目的核心步骤。我们不需要修改爬虫逻辑但要修改配置文件settings.py和创建基于RedisSpider的爬虫文件。修改amazon_best/settings.py# 启用scrapy-redis调度器 SCHEDULER scrapy_redis.scheduler.Scheduler # 启用scrapy-redis去重过滤器 DUPEFILTER_CLASS scrapy_redis.dupefilter.RFPDupeFilter # 指定Redis服务器连接信息 REDIS_HOST 你的Redis服务器IP地址 # 例如 192.168.1.100 REDIS_PORT 6379 REDIS_PASSWORD your_strong_password_here # 与redis.conf中设置的一致 # 是否在爬虫关闭时保留Redis中的请求队列和去重集合。 # 设为True时暂停爬虫后下次启动会接着上次的进度爬。非常适合长期运行的监控爬虫。 SCHEDULER_PERSIST True # 使用优先级队列进行调度默认 SCHEDULER_QUEUE_CLASS scrapy_redis.queue.PriorityQueue # 将爬取到的Item暂存到Redis中供后续处理 ITEM_PIPELINES { scrapy_redis.pipelines.RedisPipeline: 300, } # 非常重要设置一个唯一的项目名用于在Redis中生成key的前缀 # 这允许多个不同的爬虫项目使用同一个Redis服务器而互不干扰 REDIS_PARAMS { password: REDIS_PASSWORD, } REDIS_KEY_PREFIX amazon_best:spider # 调整Scrapy其他设置以适应分布式和反爬 CONCURRENT_REQUESTS 32 # 全局并发请求数可根据机器和网络调整 DOWNLOAD_DELAY 1.5 # 下载延迟礼貌爬取避免被封。分布式下每个节点都应遵守。 ROBOTSTXT_OBEY False # 亚马逊的robots.txt通常禁止爬虫我们需要谨慎地忽略它 USER_AGENT 你的自定义User-Agent字符串 # 务必设置一个真实的浏览器UA DOWNLOADER_MIDDLEWARES { # 可以在这里添加代理中间件、重试中间件等 scrapy.downloadermiddlewares.retry.RetryMiddleware: 90, } RETRY_TIMES 5 # 失败重试次数4. 爬虫Spider逻辑设计与实现这是项目的核心业务逻辑。我们的目标是抓取亚马逊某个站点如 amazon.com的Best Sellers排行榜。4.1 分析目标页面结构以https://www.amazon.com/Best-Sellers/zgbs为例。通常Best榜页面是分页的并且有清晰的品类树状结构。我们的爬虫策略可以采用“广度优先”入口从总榜页面或几个顶级品类页面开始。解析列表页提取当前页面上所有商品的ASIN亚马逊标准识别号和详情页链接并生成商品详情页的抓取任务。翻页找到“下一页”的链接将其作为新的请求放回队列。解析详情页从商品详情页提取我们需要的具体信息如标题、价格、评分、评论数、卖家信息等。品类挖掘同时从页面侧边栏或底部提取子品类的链接作为新的列表页抓取起点从而实现全品类覆盖。4.2 编写RedisSpider在amazon_best/spiders/目录下创建amazon_best_spider.py。import scrapy from scrapy_redis.spiders import RedisSpider from amazon_best.items import AmazonBestItem # 需要先定义Item from urllib.parse import urljoin, urlparse import re class AmazonBestSpider(RedisSpider): 继承自RedisSpider。 关键区别它不会从start_urls读取初始URL而是从Redis列表中读取。 初始URL需要通过redis-cli手动推送到Redis队列或者通过start_urls属性配合next_requests方法实现批量初始化。 name amazon_best # 爬虫名称必须唯一 redis_key amazon_best:start_urls # 用于从Redis读取起始URL的Key # 如果你有一批确定的起始URL也可以在这里定义爬虫启动时会自动将它们推送到Redis队列。 # 但更灵活的方式是通过外部脚本或redis-cli推送。 # start_urls [https://www.amazon.com/Best-Sellers/zgbs] def parse(self, response): 解析Best Sellers列表页如 https://www.amazon.com/Best-Sellers/zgbs # 1. 提取当前页面的商品卡片 product_cards response.css(#zg-ordered-list li.zg-item-immersion) self.logger.info(fParsing list page: {response.url}, found {len(product_cards)} products.) for card in product_cards: # 提取ASIN和详情页链接 # 亚马逊的链接结构可能变化需要根据实际情况调整选择器 relative_url card.css(a.a-link-normal::attr(href)).get() if relative_url: product_url urljoin(https://www.amazon.com, relative_url) # 从URL中提取ASIN商品详情页URL通常包含/dp/ASIN/或/gp/product/ASIN/ asin_match re.search(r/dp/([A-Z0-9]{10}), product_url) or re.search(r/gp/product/([A-Z0-9]{10}), product_url) asin asin_match.group(1) if asin_match else None if asin: # 构造一个请求去抓取商品详情页回调函数为parse_product # 注意这里使用meta传递ASIN等信息避免在回调中再次解析 yield scrapy.Request( urlproduct_url, callbackself.parse_product, meta{asin: asin, category: response.meta.get(category, Unknown)}, # 可以在这里添加代理、优先级等参数 priority10, # 商品详情页优先级可以设高一些 ) # 2. 处理翻页查找“下一页”按钮 next_page_url response.css(li.a-last a::attr(href)).get() if next_page_url: next_page_full_url urljoin(response.url, next_page_url) # 将下一页的请求放回Redis队列优先级可以设低一些 yield scrapy.Request( urlnext_page_full_url, callbackself.parse, # 回调自己继续解析列表页 priority1, meta{category: response.meta.get(category, Unknown)} ) # 3. 提取子品类链接扩展抓取范围 sub_category_links response.css(#zg_browseRoot ul a::attr(href)).getall() for link in sub_category_links: full_link urljoin(response.url, link) # 可以在这里对链接进行去重判断或者依赖Redis的全局去重 # 生成新的列表页请求 category_name link.split(/)[-1] # 简单提取品类名 yield scrapy.Request( urlfull_link, callbackself.parse, meta{category: category_name}, priority5 ) def parse_product(self, response): 解析商品详情页 item AmazonBestItem() item[asin] response.meta[asin] item[url] response.url item[category] response.meta[category] # 使用CSS选择器或XPath提取数据这里需要根据亚马逊实际页面结构调整 # 示例选择器可能已过时务必自行检查更新 item[title] response.css(#productTitle::text).get().strip() price_whole response.css(.a-price-whole::text).get().strip().replace(,, ) price_fraction response.css(.a-price-fraction::text).get() item[price] f{price_whole}.{price_fraction} if price_whole else None item[rating] response.css(#acrPopover::attr(title)).get().split()[0] # 如 “4.5 out of 5 stars” item[review_count] response.css(#acrCustomerReviewText::text).get().split()[0].replace(,, ) item[seller] response.css(#merchantInfo .a-size-small::text).get() item[rank] response.css(#productDetails_detailBullets_sections1 tr:contains(Best Sellers Rank) td::text).get() item[crawl_time] datetime.datetime.utcnow().isoformat() # 记录抓取时间 # 处理可能缺失的字段 for key in item.fields: item.setdefault(key, None) self.logger.debug(fParsed product: {item[asin]} - {item[title][:50]}...) yield item # 这个Item会被scrapy-redis的RedisPipeline推送到Redis # 可选进一步抓取评论页 # reviews_url response.css(a[data-hooksee-all-reviews-link]::attr(href)).get() # if reviews_url: # yield scrapy.Request(urlurljoin(response.url, reviews_url), # callbackself.parse_reviews, # meta{asin: item[asin]})4.3 定义数据模型Item在amazon_best/items.py中定义我们要抓取的数据结构。import scrapy class AmazonBestItem(scrapy.Item): # 商品唯一标识 asin scrapy.Field() # 商品URL url scrapy.Field() # 所属品类 category scrapy.Field() # 商品标题 title scrapy.Field() # 当前价格 price scrapy.Field() # 评分星级 rating scrapy.Field() # 评论数量 review_count scrapy.Field() # 卖家信息 seller scrapy.Field() # 销售排名 rank scrapy.Field() # 抓取时间戳 crawl_time scrapy.Field() # 可以继续添加其他字段如图片URL、描述、规格等5. 分布式部署与运行实战5.1 启动Redis并注入种子URL首先确保Redis服务器正在运行。然后我们需要将初始的爬虫入口URL推送到Redis队列中。可以在任意一台能连接Redis的机器上操作。方法一使用redis-cli命令行简单测试redis-cli -h your_redis_host -p 6379 -a your_password # 进入交互界面后使用LPUSH命令将URL推送到爬虫定义的redis_key中 LPUSH amazon_best:start_urls https://www.amazon.com/Best-Sellers/zgbs LPUSH amazon_best:start_urls https://www.amazon.com/Best-Sellers-Electronics/zgbs/electronics # 可以推送多个起始URL方法二使用Python脚本推荐便于管理创建一个seed_urls.py脚本import redis redis_client redis.Redis( hostyour_redis_host, port6379, passwordyour_password, decode_responsesTrue # 返回字符串而不是bytes ) start_urls [ https://www.amazon.com/Best-Sellers/zgbs, https://www.amazon.com/Best-Sellers-Electronics/zgbs/electronics, https://www.amazon.com/Best-Sellers-Home-Kitchen/zgbs/home-garden, # ... 添加更多品类入口 ] key amazon_best:start_urls for url in start_urls: redis_client.lpush(key, url) print(fPushed: {url}) print(fTotal {len(start_urls)} seed URLs pushed to Redis key: {key})5.2 在多台机器上启动爬虫节点在第一台爬虫服务器上cd ~/projects/amazon_distributed_spider/amazon_best source venv/bin/activate # 使用 scrapy crawl 命令启动爬虫-s 参数可以覆盖settings.py中的设置如果需要 scrapy crawl amazon_best -s LOG_FILEnode1.log在第二台爬虫服务器上执行完全相同的命令确保项目代码和虚拟环境一致cd ~/projects/amazon_distributed_spider/amazon_best source venv/bin/activate scrapy crawl amazon_best -s LOG_FILEnode2.log现在两个爬虫节点都会连接到同一个Redis服务器从amazon_best:requests队列中争抢任务URL并将抓取到的Item放入amazon_best:items队列。你可以通过查看日志文件node1.log和node2.log来观察它们的抓取进度。5.3 监控爬虫状态查看Redis中的关键信息redis-cli -h your_redis_host -a your_password # 查看待抓取请求队列长度通常这个key是 爬虫名:requests LLEN amazon_best:spider:requests # 查看已抓取Item队列长度key是 爬虫名:items LLEN amazon_best:spider:items # 查看去重集合的大小已调度过的唯一请求数 SCARD amazon_best:spider:dupefilter通过Scrapy内置的Telnet控制台可选在settings.py中启用TELNETCONSOLE_ENABLED True然后可以在爬虫运行时通过telnet localhost 6023连接使用est()命令查看引擎状态。6. 数据持久化与后处理爬虫节点只负责抓取和解析并将Item推送到Redis。我们需要另一个独立的进程来消费这些Item并将其保存到数据库如MySQL、MongoDB、CSV文件等。6.1 编写数据消费脚本创建一个item_consumer.py脚本运行在任意一台能连接Redis的机器上。import redis import json import pymysql # 以MySQL为例也可以使用pymongo, sqlite3等 from datetime import datetime import time import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) class ItemConsumer: def __init__(self, redis_host, redis_port, redis_password, db_config): self.redis_client redis.Redis( hostredis_host, portredis_port, passwordredis_password, decode_responsesTrue ) self.items_key amazon_best:spider:items # 与settings.py中的前缀对应 # 初始化数据库连接 self.db_connection pymysql.connect(**db_config) self.cursor self.db_connection.cursor() self._create_table_if_not_exists() def _create_table_if_not_exists(self): create_table_sql CREATE TABLE IF NOT EXISTS amazon_best_sellers ( id INT AUTO_INCREMENT PRIMARY KEY, asin VARCHAR(20) NOT NULL UNIQUE, url TEXT, category VARCHAR(255), title TEXT, price DECIMAL(10, 2), rating FLOAT, review_count INT, seller VARCHAR(255), rank TEXT, crawl_time DATETIME, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_asin (asin), INDEX idx_category (category), INDEX idx_crawl_time (crawl_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; try: self.cursor.execute(create_table_sql) self.db_connection.commit() logger.info(Table checked/created successfully.) except Exception as e: logger.error(fFailed to create table: {e}) self.db_connection.rollback() def process_item(self, item_dict): 处理单个Item插入数据库 # 数据清洗和转换 try: price float(item_dict.get(price, 0)) if item_dict.get(price) else None rating float(item_dict.get(rating, 0).split()[0]) if item_dict.get(rating) else None review_count int(item_dict.get(review_count, 0).replace(,, )) if item_dict.get(review_count) else 0 crawl_time datetime.fromisoformat(item_dict.get(crawl_time)) if item_dict.get(crawl_time) else datetime.utcnow() except (ValueError, AttributeError) as e: logger.warning(fData conversion error for ASIN {item_dict.get(asin)}: {e}. Using defaults.) price rating None review_count 0 crawl_time datetime.utcnow() insert_sql INSERT INTO amazon_best_sellers (asin, url, category, title, price, rating, review_count, seller, rank, crawl_time) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE url VALUES(url), category VALUES(category), title VALUES(title), price VALUES(price), rating VALUES(rating), review_count VALUES(review_count), seller VALUES(seller), rank VALUES(rank), crawl_time VALUES(crawl_time) try: self.cursor.execute(insert_sql, ( item_dict.get(asin), item_dict.get(url), item_dict.get(category), item_dict.get(title), price, rating, review_count, item_dict.get(seller), item_dict.get(rank), crawl_time )) self.db_connection.commit() logger.info(fItem saved/updated: ASIN {item_dict.get(asin)}) return True except pymysql.Error as e: logger.error(fFailed to insert item {item_dict.get(asin)}: {e}) self.db_connection.rollback() return False def run(self): 主循环持续从Redis中取出并处理Item logger.info(fStarting item consumer, listening on Redis key: {self.items_key}) while True: try: # BRPOP是阻塞式弹出如果队列为空则等待。0表示无限等待。 # 返回一个元组 (key, value) _, item_json self.redis_client.brpop(self.items_key, timeout0) if item_json: item_dict json.loads(item_json) self.process_item(item_dict) except redis.exceptions.ConnectionError as e: logger.error(fRedis connection error: {e}. Reconnecting in 10 seconds...) time.sleep(10) # 可以在这里添加重连逻辑 except json.JSONDecodeError as e: logger.error(fFailed to decode JSON from Redis: {e}, data: {item_json[:100]}...) except KeyboardInterrupt: logger.info(Consumer stopped by user.) break except Exception as e: logger.error(fUnexpected error: {e}) time.sleep(5) self.cursor.close() self.db_connection.close() logger.info(Database connection closed.) if __name__ __main__: # 配置信息 REDIS_CONFIG { redis_host: your_redis_host, redis_port: 6379, redis_password: your_password, } DB_CONFIG { host: your_mysql_host, user: your_username, password: your_db_password, database: spider_data, charset: utf8mb4, } consumer ItemConsumer(**REDIS_CONFIG, db_configDB_CONFIG) consumer.run()6.2 运行消费进程# 在数据库服务器或另一台机器上运行 cd ~/projects/amazon_distributed_spider source venv/bin/activate python item_consumer.py这个进程会一直运行监听Redis中的Item队列并存入MySQL。你可以同时运行多个消费进程来提高处理速度但要注意数据库写入的并发控制。7. 高级调优与反爬策略分布式爬虫搭建起来只是第一步要让它在亚马逊这种网站上稳定、高效、长期地运行还需要一系列调优和反爬措施。7.1 请求管理与去重深度优化自定义去重规则scrapy-redis默认使用请求的指纹URL method body去重。但对于亚马逊同一个商品可能有多个URL如带不同查询参数。你可能需要重写dupefilter例如只根据ASIN去重。# 在settings.py中指定自定义的去重类 DUPEFILTER_CLASS amazon_best.dupefilters.ASINDupeFilter然后在项目中创建dupefilters.pyfrom scrapy_redis.dupefilter import RFPDupeFilter from urllib.parse import urlparse, parse_qs import re class ASINDupeFilter(RFPDupeFilter): def request_fingerprint(self, request): # 尝试从URL或meta中提取ASIN作为去重依据 asin request.meta.get(asin) if not asin: # 从URL中解析ASIN path urlparse(request.url).path asin_match re.search(r/dp/([A-Z0-9]{10}), path) or re.search(r/gp/product/([A-Z0-9]{10}), path) asin asin_match.group(1) if asin_match else None # 如果找到了ASIN就用ASIN生成指纹否则回退到默认的URL指纹 if asin: return self._fingerprint(asin.encode(utf-8)) return super().request_fingerprint(request)请求优先级管理在爬虫中通过Request(priority)参数可以控制抓取顺序。通常详情页高价值优先级高于列表页翻页低价值。7.2 应对反爬机制亚马逊的反爬非常严格分布式爬虫虽然能分摊请求压力但仍需谨慎。User-Agent轮换在settings.py的DOWNLOADER_MIDDLEWARES中启用并配置UserAgentMiddleware使用一个庞大的UA池。IP代理池这是分布式爬虫应对封IP的核心。你需要一个可靠的代理IP服务并在中间件中实现代理轮换逻辑。# middlewares.py import random class RandomProxyMiddleware: def __init__(self, proxy_list): self.proxy_list proxy_list classmethod def from_crawler(cls, crawler): # 从配置文件或外部API加载代理列表 proxy_list [...] return cls(proxy_list) def process_request(self, request, spider): proxy random.choice(self.proxy_list) request.meta[proxy] proxy在settings.py中启用它并设置较高的优先级。请求速率控制DOWNLOAD_DELAY和CONCURRENT_REQUESTS_PER_DOMAIN是关键。即使使用代理对单一域名的请求频率也不能太高。建议将DOWNLOAD_DELAY设置在1-3秒并根据代理IP数量调整并发数。处理验证码和重定向编写中间件检测响应内容是否包含验证码页面或意外重定向并触发相应的处理逻辑如更换代理、暂停任务、发送警报。7.3 系统监控与告警日志集中管理将各节点的日志通过scrapy的LOG_STDOUT和LOG_FILE设置输出到文件并可以使用像ELKElasticsearch, Logstash, Kibana这样的工具进行集中收集和查看。关键指标监控Redis队列长度LLEN如果持续快速增长说明消费跟不上生产如果快速减少到0可能爬虫停止了或没新任务了。爬虫节点日志中的抓取速度items/min和错误率。数据库写入速度。简易告警可以写一个定时脚本检查Redis队列长度或爬虫日志中特定错误关键词一旦异常就发送邮件或钉钉消息。8. 常见问题与故障排查实录在实际部署和运行中你几乎一定会遇到下面这些问题。这里记录了我的排查思路和解决方案。8.1 爬虫节点不抓取任务现象启动了爬虫Redis里也有start_urls但爬虫日志一直显示DEBUG: Crawled (200)很少或者没有新请求。排查步骤检查Redis连接在爬虫节点的Python交互环境中尝试连接Redis看密码、主机、端口是否正确。检查队列Key确认爬虫redis_key设置amazon_best:start_urls和你推送种子URL的Key是否完全一致包括前缀。用redis-cli的KEYS amazon_best:*命令查看所有相关Key。检查调度器状态scrapy-redis的待抓取队列Key是爬虫名:requests如amazon_best:spider:requests。用LLEN查看其长度。如果种子URL被消费了但此队列为空可能是爬虫解析后没有生成新的Request或者生成的Request因为去重被过滤了。检查爬虫的parse方法逻辑和去重集合大小SCARD amazon_best:spider:dupefilter。查看爬虫日志级别启动时加上-L DEBUG参数查看更详细的调度日志。8.2 数据重复或丢失现象数据库里出现了完全相同的记录或者有些商品明明看到了抓取日志但数据库里没有。原因与解决重复去重失效检查自定义的DupeFilter逻辑是否正确。确保ASIN提取逻辑覆盖所有商品URL模式。爬虫重启导致如果SCHEDULER_PERSIST False爬虫重启后会清空去重集合导致重新抓取。对于长期任务务必设为True。丢失Item Pipeline处理失败检查item_consumer.py脚本的日志。可能是数据格式错误导致插入数据库失败脚本应记录错误而不是静默丢弃。Redis内存不足如果Item堆积太多Redis可能触发淘汰策略或崩溃。监控Redis内存使用情况确保消费进程能跟上爬取速度。可以增加消费进程数量。网络波动爬虫节点与Redis之间、消费进程与Redis/数据库之间的网络不稳定可能导致数据在传输中丢失。增加重试机制和更完善的错误处理。8.3 爬虫被封锁403 Forbidden, 503 Service Unavailable现象大量请求返回403/503状态码或者返回的是验证码页面。应对策略立即降速大幅增加DOWNLOAD_DELAY减少CONCURRENT_REQUESTS。更换代理IP如果用了代理池确保当前代理IP是有效的、未被亚马逊封禁的住宅或数据中心IP。立即切换到新的IP段。检查请求头确保User-Agent,Accept-Language,Accept-Encoding等头部看起来像一个真实浏览器。可以考虑使用scrapy-fake-useragent库。模拟浏览器行为最后手段对于特别顽固的页面可以考虑在中间件中集成selenium或playwright。但这会极大降低抓取速度应仅用于最关键的数据并严格控制使用频率。最好将其作为备用解析方案当常规请求多次失败后再触发。8.4 性能瓶颈分析当爬虫速度达不到预期时按以下顺序排查可能瓶颈点检查方法优化建议网络延迟在爬虫节点ping Redis服务器和亚马逊。将爬虫节点部署在离目标网站和Redis服务器网络延迟低的区域如同云厂商同区域。Redis性能使用redis-cli --stat或INFO命令查看Redis的CPU、内存、连接数。升级Redis配置使用连接池避免频繁创建连接。对于超大规模队列考虑Redis集群。爬虫解析效率分析爬虫日志计算parse方法执行时间。优化CSS选择器/XPath避免复杂的字符串处理。将耗时的清洗操作移到后处理消费端。数据库写入监控消费进程的日志和数据库CPU/IO。优化数据库表结构、添加索引。消费端采用批量插入如每100条插入一次而非逐条插入。反爬限制观察请求错误率和响应时间。这是最主要的限速因素。优化代理池质量实施更精细的请求间隔和并发控制策略。搭建并维护一个针对亚马逊这类大型网站的分布式爬虫是一个持续迭代和对抗的过程。这套基于scrapy-redis的架构提供了坚实的基础。核心在于理解其“中心调度分布式执行”的思想并围绕稳定性、可扩展性和反爬应对这三个目标进行不断优化。从种子URL注入到多节点爬取再到数据消费入库每一个环节都需要根据实际情况进行监控和调整。记住礼貌爬取、尊重robots.txt在商业伦理和法律允许的范围内并准备好随时调整你的策略因为你的对手——网站的反爬系统——也一直在进化。