资讯详情

资讯详情

基于scrapy-redis的高可用新闻资讯采集系统架构与工程实践

简介本资源是一个基于Scrapy-Redis构建的分布式多平台新闻资讯采集系统面向Python爬虫开发者、数据采集工程师及大数据初学者旨在解决单机Scrapy在高并发、跨平台、去重与任务调度方面的瓶颈问题。系统完整集成Redis作为调度中心与去重中间件支持对多个新闻网站的并行抓取、动态解析、结构化清洗及多后端存储含MySQL写入脚本兼顾反爬适配与可扩展架构设计。压缩包共41个文件以27个Python核心模块如spiders、pipelines、middlewares、push.py等为主体辅以6个XML配置、2个CFG/YML环境配置、Dockerfile与docker-compose.yml容器化部署支持整体仅32KB轻量但结构完整。目前已有41人学习下载提供开箱即用的工程目录、清晰的模块划分如test.py用于快速验证、process_items.py专注数据处理、真实可用的headers与存储适配逻辑是理解分布式爬虫原理与落地实践的优质参考样本。1. 这不是个“爬虫脚本”而是一套可落地的新闻资讯数据管道你搜“scrapy-redis 新闻采集”刷出来的大多是零散代码片段、过时的教程或者直接甩个 GitHub 链接让你自己啃。但真正跑起来一个能扛住多平台、不崩、不丢数据、还能随时加节点的新闻采集系统光靠抄几行start_urls和parse()根本不行。我去年帮一家区域媒体做内容聚合中台从零搭起这套基于 scrapy-redis 的多平台新闻资讯采集系统现在每天稳定抓取 37 家主流媒体、21 个地方政务站、8 类垂直行业门户的首页、栏目页和详情页峰值并发 1200 请求Redis 队列积压始终控制在 300 条以内——这不是理论模型是实打实压在生产环境跑了一年半的系统。核心关键词就五个scrapy-redis、新闻资讯采集系统、scrapy.cfg、docker-compose.yml、Dockerfile。它解决的不是“能不能抓到”而是“能不能持续、可控、可扩展地抓到”。适合三类人一是想把爬虫从玩具级升级成工程级的 Python 工程师二是需要对接内容中台、做舆情监控或竞品分析的产品/运营同学三是技术负责人要评估这套架构能否放进现有 CI/CD 流水线、是否兼容 MySQL 数据库生态。它不教你怎么写 XPath而是告诉你当搜狐首页刷新频率变成每 90 秒一次当澎湃新闻的反爬策略突然升级 JS 渲染当本地测试跑得好好的一上 Docker 就报ConnectionRefusedError: [Errno 111] Connection refused——你该动哪根线、改哪行配置、查哪个日志段。这套系统真正的价值不在“采集”本身而在“管道化”URL 入队、去重、调度、下载、解析、存储、监控每个环节都解耦、可替换、可度量。比如你今天用 MySQL 存正文明天想换 Elasticsearch 做全文检索只需改pipelines.py里两行代码不用碰调度器、不用重写中间件。再比如某家媒体突然加了滑动验证你只需要在middlewares.py里新增一个SeleniumDownloaderMiddleware把它插进下载器中间件栈其他所有站点照常运行。这才是 scrapy-redis 给你的底气——它不是让你写更多代码而是让你少写重复代码把精力聚焦在业务逻辑上怎么定义“有效新闻”标题含“突发”“快讯”的优先级加权多少同一事件不同信源的去重粒度设为“标题发布时间±5分钟”还是“正文前200字哈希”这些才是真实世界里的决策点而不是卡在pipelines.py里纠结item[content] response.css(div#article p::text).getall()抓不到数据。2. 整体架构设计为什么必须用 scrapy-redis而不是单机 Scrapy2.1 单机 Scrapy 的天花板在哪先说清楚我们放弃单机 Scrapy 的具体原因。不是它不好而是新闻采集场景天然踩中它的三个硬伤第一调度器Scheduler内存绑定。Scrapy 默认调度器把待爬 URL 存在 Python 的set或deque里进程一挂队列全丢。你凌晨三点跑着采集服务器重启一下几百个未抓链接就永远消失了。更糟的是它无法跨进程共享——你想开两个scrapy crawl news_spider实例并行跑不行因为两个实例的调度器互不知情会重复抓同一 URL或者漏掉某些 URL。这在新闻场景下是致命的热点事件爆发时每分钟都有新链接产生漏抓一条可能就是漏掉关键信源。第二去重DupeFilter不可持久化。默认RFPDupeFilter把指纹存在内存里重启即清空。这意味着系统重启后昨天刚抓过的人民网首页今天又会当作新 URL 再抓一遍。新闻站首页更新频繁但内容主体变化不大反复抓不仅浪费带宽和 IP更会导致下游存储层写入大量重复数据清洗成本飙升。第三扩展性为零。想提升吞吐量只能堆机器但每台机器都要独立维护一套 Scrapy 环境、独立配置代理池、独立管理 Cookies。运维复杂度呈指数增长。我们试过用scrapyd部署多个单机实例结果发现当某个媒体反爬变严所有实例的请求都被限速而你根本不知道是哪个实例触发了风控排查像大海捞针。2.2 scrapy-redis 如何破局把调度器和去重器“搬出进程”scrapy-redis 的核心思想非常朴素把 Scrapy 最脆弱的两个组件——调度器和去重器——从 Python 进程里剥离出来放到 Redis 这个外部、持久化、支持多客户端共享的存储里。它不是重写 Scrapy而是给 Scrapy “装上 Redis 插件”。具体怎么装看这张简化的数据流图文字描述[新闻源列表] → [Spider 生成初始URL] → [Redis DUPEFILTER 检查指纹] → [若未重复存入 Redis SCHEDULER QUEUE] → [多个 Scrapy Worker 同时监听该 Queue] → [Worker 取出 URL → 下载 → 解析 → Pipeline 存 MySQL] → [Pipeline 成功后通知 Redis 更新状态]这里的关键跃迁有三点调度器 Redis 化所有 Spider 生成的 URL不再塞进本地内存队列而是统一推到 Redis 的 List 或 Sorted Set我们用zset便于按优先级排序。任何数量的 WorkerScrapy 进程都可以BRPOP或ZPOPMIN从同一个队列取任务。Worker 挂了没关系URL 还在 Redis 里其他 Worker 接着干。这就是真正的“任务队列高可用”。去重器 Redis 化RFPDupeFilter被替换为RedisDupeFilter它计算 URL 指纹默认是scrapy.utils.request.request_fingerprint后存到 Redis 的set结构里。所有 Worker 共享同一个set自然实现全局去重。而且 Redis 的set是持久化的开启 AOF 或 RDB重启不丢数据。Request 对象序列化Scrapy 的Request对象不能直接存 Redisscrapy-redis 提供了pickle序列化方案默认或json方案需自定义。我们选pickle因为它能完整保留Request的meta、cookies、headers等所有属性这对需要携带登录态或 Referer 的新闻站至关重要。提示不要迷信“JSON 更轻量”。新闻采集常需传递复杂上下文比如meta{source: people, category: politics, priority: 10}pickle一行搞定json得自己写default函数处理datetime、bytes等类型反而增加出错概率。实测下来pickle在千级 QPS 下序列化开销可忽略。2.3 为什么选择 Redis 而不是 Kafka 或 RabbitMQ有人问消息队列不是更专业吗Kafka 吞吐无敌RabbitMQ 可靠性高。但在新闻采集场景Redis 是更优解理由很实在延迟极低Redis 是内存数据库LPUSHBRPOP的 P99 延迟在 0.5ms 以内。Kafka 单次写入延迟通常在 5~20ms对毫秒级响应的新闻抓取来说积压风险更高。我们曾用 Kafka 替代 Redis 做调度结果在突发流量下Worker 拿到 URL 的平均延迟从 1.2ms 涨到 18ms导致部分时效性强的快讯抓取超时。运维简单一个redis-server进程配好maxmemory和maxmemory-policy我们用allkeys-lru基本不用管。Kafka 需要 ZooKeeper、Broker 集群、Topic 分区管理运维成本翻倍。对中小团队省下的运维时间够你多优化两个解析规则。功能刚好够用新闻采集不需要 Kafka 的分区重平衡、精确一次语义Exactly-Once。我们需要的是URL 不丢、不重复、能按优先级取、能快速查看队列长度。Redis 的List/ZSet/Set原语完美覆盖。ZSet的score字段天然支持动态优先级——比如把“突发”类新闻的score设为当前时间戳的负值保证最新事件永远排在队首。与 Scrapy 生态无缝集成scrapy-redis 本身就是为 Redis 量身定制文档、社区、坑都明明白白。换成 Kafka得自己写KafkaScheduler、KafkaDupeFilter调试成本远高于收益。3. 核心细节解析从 scrapy.cfg 到 docker-compose.yml 的每一行都在解决什么问题3.1 scrapy.cfg不只是配置文件它是部署契约scrapy.cfg常被当成启动参数的集合但它实际是 Scrapy 项目的“部署契约”定义了项目如何被scrapyd或scrapyCLI 识别和加载。我们的scrapy.cfg长这样[settings] default news_spider.settings [deploy] # scrapyd 部署用但我们不用 scrapyd所以注释掉 # project news_spider [deploy:prod] # 指向生产环境的 scrapyd server同样不用 # url http://prod-scrapyd:6800/ # username user # password pass # 关键自定义命令入口 [commands] crawl news_spider.commands.crawl_command:CrawlCommand重点在最后三行。我们没用scrapyd而是自己写了CrawlCommand原因很现实scrapy crawl spider_name启动时会加载settings.py但settings.py里REDIS_URL等敏感配置如果写死就无法适配不同环境开发/测试/生产。CrawlCommand让我们能在启动时动态注入配置# news_spider/commands/crawl_command.py from scrapy.cmdline import execute import os import sys class CrawlCommand: def run(self, args, opts): # 从环境变量读取配置而非 settings.py 硬编码 os.environ.setdefault(REDIS_URL, redis://redis:6379/0) os.environ.setdefault(MYSQL_URL, mysqlpymysql://user:passmysql:3306/news_db) os.environ.setdefault(SCRAPY_SETTINGS_MODULE, news_spider.settings) # 构建 scrapy 命令 cmd [scrapy, crawl, args[0]] args[1:] execute(cmd)这样启动命令就变成python -m scrapy crawl people_spider --set REDIS_URLredis://prod-redis:6379/1完全绕过settings.py的硬编码。scrapy.cfg里的[commands]就是告诉 Scrapy“当你看到scrapy crawl请调用我写的这个类而不是默认逻辑。”注意scrapy.cfg必须放在项目根目录且文件名不能改。Scrapy 启动时会向上遍历目录找它找不到就报Not a Scrapy project。我们吃过亏有次把项目打包进 Docker忘了把scrapy.cfg放进WORKDIR容器启动直接失败日志只显示ImportError: No module named news_spider查了两小时才发现是scrapy.cfg缺失。3.2 settings.pyscrapy-redis 的灵魂开关settings.py是整个系统的“神经中枢”80% 的稳定性问题都源于这里配置错误。我们只列出最关键的 12 行并解释每行背后的战场经验# 1. 启用 scrapy-redis 调度器 SCHEDULER scrapy_redis.scheduler.Scheduler # 2. 启用 scrapy-redis 去重器 DUPEFILTER_CLASS scrapy_redis.dupefilter.RFPDupeFilter # 3. 持久化开关TrueRedis队列不自动清空False爬完就删慎用 SCHEDULER_PERSIST True # 4. Redis连接URL必须指向Docker网络内的服务名 REDIS_URL os.getenv(REDIS_URL, redis://redis:6379/0) # 5. 调度队列类型ZSet支持优先级List是FIFO SCHEDULER_QUEUE_CLASS scrapy_redis.queue.SpiderPriorityQueue # 6. 并发数不是越大越好要匹配目标网站承受力 CONCURRENT_REQUESTS 32 # 7. 下载延迟新闻站首页通常允许1s详情页可设0.5s DOWNLOAD_DELAY 1 # 8. 自动限速根据响应时间动态调整并发数比固定DELAY更智能 AUTOTHROTTLE_ENABLED True AUTOTHROTTLE_START_DELAY 1 AUTOTHROTTLE_MAX_DELAY 3 # 9. User-Agent轮换避免被识别为爬虫 DOWNLOADER_MIDDLEWARES { scrapy.downloadermiddlewares.useragent.UserAgentMiddleware: None, news_spider.middlewares.RandomUserAgentMiddleware: 400, } # 10. 代理中间件新闻站对IP要求严必须用 scrapy.downloadermiddlewares.retry.RetryMiddleware: 90, news_spider.middlewares.ProxyMiddleware: 100, # 11. Pipeline链先去重清洗再存MySQL最后发消息 ITEM_PIPELINES { news_spider.pipelines.DeduplicatePipeline: 200, news_spider.pipelines.MySQLPipeline: 300, news_spider.pipelines.KafkaPipeline: 400, # 可选 } # 12. 日志级别生产环境用INFODEBUG只在本地调试开 LOG_LEVEL INFO逐条拆解SCHEDULER_PERSIST True是生命线。设为False爬虫结束时 Redis 队列自动清空。线上环境绝对禁止我们曾因误设此值导致一次全站重抓MySQL 写入压力暴增主库 CPU 100%差点引发雪崩。SCHEDULER_QUEUE_CLASS选SpiderPriorityQueue基于 ZSet不是FifoQueue。新闻采集必须优先级人民网突发新闻 地方日报常规报道 行业论坛转载帖。我们在start_requests()里给Request加priority参数yield scrapy.Request(url, priority100)priority值越大越早被取出。CONCURRENT_REQUESTS 32是经过压测的平衡点。设太高目标站返回 429设太低吞吐跟不上。我们用ab工具对目标站做压力测试ab -n 1000 -c 50 http://example.com/观察响应时间拐点。32 是多数新闻站的甜蜜点。AUTOTHROTTLE_ENABLED True比DOWNLOAD_DELAY更可靠。它根据Response.time动态调整并发遇到慢响应自动降并发快响应自动提并发。START_DELAY和MAX_DELAY控制调整范围避免抖动过大。DOWNLOADER_MIDDLEWARES的顺序是关键。ProxyMiddleware必须在RetryMiddleware之后序号 100 90否则重试时不会走代理IP 被封风险大增。3.3 Dockerfile不是“把代码扔进容器”而是构建可复现的运行时环境Dockerfile的使命是让任何人在任何机器上执行docker build得到和你生产环境一模一样的 Python 运行时。我们的Dockerfile没用FROM python:3.9-slim而是选FROM continuumio/miniconda3:4.12.0原因有三依赖隔离强Conda 的environment.yml比requirements.txt更精准控制包版本尤其对lxml、cryptography这类 C 扩展pip install常因系统库版本不匹配编译失败Conda 直接装预编译二进制。科学计算友好后续可能加 NLP 清洗如标题分类、情感分析Conda 的pytorch、transformers一键安装pip得折腾 CUDA 版本。镜像体积可控miniconda3基础镜像 350MB比python:3.9-slim120MB稍大但省去apt-get install build-essential libxml2-dev libxslt-dev等编译依赖最终镜像大小反而小 15%。FROM continuumio/miniconda3:4.12.0 # 设置工作目录 WORKDIR /app # 复制环境文件先装依赖利用Docker layer cache COPY environment.yml . RUN conda env create -f environment.yml \ conda clean --all -f -y # 激活环境 SHELL [conda, run, -n, news_env, bash, -c] # 复制代码 COPY . . # 创建非root用户安全强制要求 RUN useradd -m -u 1001 -G root -d /home/newsuser newsuser \ chown -R newsuser:root /app \ chmod -R 775 /app USER newsuser # 暴露端口Scrapy本身不占端口但健康检查需要 EXPOSE 6800 # 启动命令 CMD [conda, run, -n, news_env, python, -m, scrapy, crawl, news_spider]关键细节environment.yml显式声明scrapy-redis0.7.2不是0.7.0因为 0.7.3 有个 Redis 连接池 bug会导致高并发下ConnectionResetError。我们踩过这个坑回滚到 0.7.2 后稳定运行。USER newsuser强制非 root 运行。Docker 安全最佳实践避免容器内提权攻击。chown确保用户有/app目录权限。CMD用conda run而不是python确保激活news_env环境。python命令可能调用系统 Python而非 Conda 环境。3.4 docker-compose.yml定义服务间的“外交关系”docker-compose.yml不是简单的服务列表它是定义redis、mysql、scrapy-worker之间网络、依赖、健康检查的“外交协议”。我们的版本version: 3.8 services: # Redis调度中心 redis: image: redis:7.2-alpine container_name: news-redis restart: always command: redis-server --appendonly yes --maxmemory 2gb --maxmemory-policy allkeys-lru ports: - 6379:6379 volumes: - ./redis-data:/data healthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 5s retries: 5 # MySQL存储中心 mysql: image: mysql:8.0-oracle container_name: news-mysql restart: always environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: news_db MYSQL_USER: news_user MYSQL_PASSWORD: news_pass ports: - 3306:3306 volumes: - ./mysql-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -u, root, -prootpass] interval: 10s timeout: 5s retries: 5 depends_on: redis: condition: service_healthy # Scrapy Worker采集引擎 worker: build: . container_name: news-worker restart: always environment: - REDIS_URLredis://redis:6379/0 - MYSQL_URLmysqlpymysql://news_user:news_passmysql:3306/news_db - SCRAPY_SETTINGS_MODULEnews_spider.settings volumes: - ./logs:/app/logs depends_on: redis: condition: service_healthy mysql: condition: service_healthy # 关键健康检查确保Worker能连通Redis和MySQL healthcheck: test: [CMD, python, -c, import redis; rredis.Redis(hostredis, port6379); r.ping(); import pymysql; pymysql.connect(hostmysql, usernews_user, passwordnews_pass, databasenews_db)] interval: 30s timeout: 10s retries: 3核心设计点healthcheck是灵魂。redis和mysql的健康检查确保它们启动完成才启动workerworker的健康检查则验证它能否连通两个依赖服务。没有它worker可能因 Redis 未就绪而启动失败Docker 会不断重启日志刷屏。depends_on的condition: service_healthy比condition: service_started严格得多。后者只等容器启动前者等健康检查通过。我们曾用started结果worker启动时mysql还在初始化报pymysql.err.OperationalError: (1045, Access denied)查了半小时才发现是依赖条件太松。volumes映射./logs:/app/logs让日志落到宿主机方便docker logs news-worker查看也方便 ELK 收集。command里--appendonly yes开启 AOF 持久化--maxmemory 2gb限制内存--maxmemory-policy allkeys-lru防止 OOM。新闻采集队列可能堆积必须防止单个 Redis 实例吃光内存。4. 实操过程从本地调试到生产部署的七步通关4.1 第一步本地最小闭环验证5分钟别急着写 Spider先验证 scrapy-redis 能否跑通。创建最简test_spider.pyimport scrapy from scrapy_redis.spiders import RedisSpider class TestSpider(RedisSpider): name test_spider def parse(self, response): self.logger.info(fSuccess! Status: {response.status}, URL: {response.url}) yield {url: response.url, status: response.status}然后在本地启动 Redisredis-server执行# 1. 启动Scrapy Worker监听Redis队列 scrapy crawl test_spider # 2. 另开终端向Redis队列推一个测试URL echo http://httpbin.org/get | redis-cli -x lpush test_spider:start_urls如果worker终端打印Success! Status: 200...说明 scrapy-redis 基础链路通了。这步必须做90% 的线上问题都能在本地复现。实操心得lpush命令里的test_spider:start_urls必须和 Spider 的name一致。我们曾把name写成testspider少下划线队列名变成testspider:start_urlsworker一直空转查日志全是DEBUG: Crawled 0 pages折腾半天才发现队列名不匹配。4.2 第二步编写新闻 Spider聚焦“可维护性”新闻站结构千差万别但 Spider 设计有通用模式。以“人民日报”为例我们不写死 XPath而是用Selectorcss方法链class PeopleSpider(scrapy.Spider): name people allowed_domains [people.cn] def start_requests(self): # 首页新闻列表 yield scrapy.Request( http://www.people.cn/, callbackself.parse_index, meta{source: people, category: index} ) def parse_index(self, response): # 提取所有新闻链接带优先级 for href in response.css(div#main-content a::attr(href)).getall(): if href.startswith(http): # 突发新闻优先级50 priority 50 if 突发 in response.css(a::text).get() else 0 yield scrapy.Request( href, callbackself.parse_article, meta{source: people, category: article, priority: priority} ) def parse_article(self, response): item {} item[title] response.css(h1::text).get(default).strip() item[content] \n.join(response.css(div#p_content p::text).getall()).strip() item[publish_time] response.css(meta[namepublishdate]::attr(content)).get() item[url] response.url item[source] response.meta[source] yield item关键设计meta传递上下文source、category、priority让 Pipeline 能做差异化处理。callback明确分离parse_index只负责找链接parse_article只负责提正文职责单一改一个站不影响其他。get(default)避免NoneType错误新闻站 HTML 结构常有变动容错必须强。4.3 第三步Pipeline 实现去重与存储MySQLpipelines.py是数据落库前的最后一道闸门。我们的DeduplicatePipeline不是简单if item in seen而是用 MySQL 的INSERT IGNOREclass DeduplicatePipeline: def __init__(self, mysql_url): self.mysql_url mysql_url classmethod def from_crawler(cls, crawler): return cls(crawler.settings.get(MYSQL_URL)) def open_spider(self, spider): # 连接MySQL用连接池 self.engine create_engine(self.mysql_url, pool_pre_pingTrue) self.Session sessionmaker(bindself.engine) def process_item(self, item, spider): session self.Session() try: # 基于URL和标题哈希去重 url_hash hashlib.md5(item[url].encode()).hexdigest() title_hash hashlib.md5(item[title].encode()).hexdigest() # INSERT IGNORE冲突则跳过 sql INSERT IGNORE INTO news_articles (url_hash, title_hash, title, content, publish_time, source, url) VALUES (:url_hash, :title_hash, :title, :content, :publish_time, :source, :url) session.execute(text(sql), { url_hash: url_hash, title_hash: title_hash, title: item[title], content: item[content], publish_time: item[publish_time], source: item[source], url: item[url] }) session.commit() except Exception as e: session.rollback() spider.logger.error(fDedup error: {e}) finally: session.close() return item为什么用INSERT IGNORE而不是先SELECT再INSERT因为高并发下两次查询间可能有其他 Worker 插入同一条导致重复。INSERT IGNORE是原子操作MySQL 层面保证唯一性。4.4 第四步Docker 构建与本地测试在项目根目录执行# 构建镜像 docker build -t news-worker . # 启动全套服务后台 docker-compose up -d # 查看worker日志 docker logs -f news-worker # 手动推一个测试URL到Redis echo http://www.people.cn/ | docker exec -i news-redis redis-cli -x lpush people:start_urls如果日志出现Crawled (200)和Scraped from 200 http://www.people.cn/说明 Docker 环境跑通。此时docker ps应看到news-redis、news-mysql、news-worker三个容器都在Up状态。注意docker-compose.yml里worker的environment必须用redis和mysql作为 host 名这是 Docker 内置 DNS 解析的 service name不是localhost。本地测试时localhost指向宿主机而容器内localhost指向自己必须用 service name。4.5 第五步生产环境部署阿里云 ECS生产环境不用docker-compose up而是用docker stack deploySwarm 模式或 Kubernetes。我们用 Swarm因为轻量# 初始化Swarm docker swarm init # 创建 overlay 网络 docker network create --driver overlay --attachable news-net # 部署stack docker stack deploy -c docker-compose-prod.yml newsdocker-compose-prod.yml和本地版区别在于redis和mysql改为外部云服务阿里云 Redis、RDSworker只连它们的内网地址。worker增加deploy配置deploy: replicas: 3 resources: limits: memory: 2G cpus: 1.0 restart_policy: condition: on-failure delay: 5s max_attempts: 3移除volumes日志用logging驱动发到阿里云 SLS。这样3 个worker实例自动负载均衡一个挂了Swarm 30 秒内拉起新实例。4.6 第六步监控与告警Prometheus Grafana没有监控的采集系统等于裸奔。我们在worker容器里加 Prometheus Exporter# 在settings.py里加 EXTENSIONS { scrapy_prometheus.PrometheusMetrics: 100, } # docker-compose.yml里暴露/metrics端口 worker: ports: - 9000:9000 # Prometheus抓取端口Grafana 看板监控 5 个黄金指标指标查询语句告警阈值说明队列积压redis_queue_length{queuepeople:start_urls} 5000说明Worker处理不过来需扩容抓取成功率rate(scrapy_response_status_count{status200}[1h]) / rate(scrapy_response_status_count[1h]) 0.95网络或反爬问题MySQL写入延迟mysql_up{jobmysql} 01MySQL宕机Redis内存使用率redis_memory_used_bytes{instanceredis:6379} / redis_memory_max_bytes{instanceredis:6379} 0.85需清理或扩容Worker存活数count(container_state{staterunning, name~news-worker.*}) 3Swarm调度异常4.7 第七步日常运维扩缩容与故障恢复扩容 Workerdocker service scale news_worker5Swarm 自动分配任务。清理 Redis 队列docker exec news-redis redis-cli DEL people:start_urls慎用先确认无重要任务。故障恢复若worker全挂先docker service logs news_worker查错常见是 MySQL 连接超时执行docker service update --env-add MYSQL_URLmysql://new-host:3306/news_db news_worker动态更新配置。实操心得我们给每个 Spider 配置独立队列people:start_urls,xinhua:start_urls这样停掉某个站的采集只DEL对应队列不影响其他。曾经澎湃站点升级反爬我们DEL pengpai:start_urls修好后再推新 URL全程其他站无感知。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 问题速查表现象可能原因排查命令解决方案worker启动后立即退出日志无输出Dockerfile中USER权限不足docker exec -it news-worker ls -l /appchown -R newsuser:root /app确保用户有读权限redis-cli lrange people:start_urls 0 -1返回空但worker日志显示Crawled 0 pagesSCHEDULER_QUEUE_CLASS配置错误或name不匹配docker exec news-redis redis-cli keys *检查keys输出本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →