1. 项目概述为什么用 AWS 做网络爬虫而不是本地跑脚本我第一次在伦敦租住的公寓里调试一个爬取招聘网站的 Python 脚本时窗外正下着连绵阴雨。脚本跑了三小时卡在第 472 条职位信息上——IP 被封了验证码弹窗像幽灵一样反复出现本地笔记本风扇嘶吼得像要起飞。那一刻我意识到单机爬虫不是“能不能跑通”的问题而是“能不能活过第二天”的生存问题。这正是 AWS 批处理AWS Batch真正起作用的地方它不解决“怎么写代码”而是解决“怎么让代码在真实世界里稳定、可伸缩、不掉链子地跑下去”。核心关键词AWS在这里不是指“用个 EC2 虚拟机装个 Python 就叫上云”而是指一套完整的工程化思路——把爬虫从“个人玩具”升级为“可调度、可监控、可回滚、可计费”的数据采集服务。它天然适配三个现实痛点第一IP 池轮换需要多地域出口AWS 全球 30 区域节点就是现成的地理分散代理池第二反爬策略升级后单机重试逻辑容易雪崩而 Batch 的失败自动重试 任务隔离机制能避免一个请求崩掉整个流程第三爬取任务有明显波峰波谷比如每天早九点集中抓新职位手动启停服务器既浪费钱又容易忘而 Batch 的按需拉起容器、完成即销毁的模式让成本和负载完全对齐。适合谁参考如果你正面临这些情况中的任意一种用 Scrapy 写好了逻辑但总被封 IP用 Selenium 模拟登录却卡在验证码识别环节或者团队里有人写爬虫、有人管服务器、有人看数据协作靠微信截图和 Excel 表格——那这篇就是为你写的。它不假设你懂 CloudFormation 或 IAM 策略细节但会带你亲手把一个本地能跑的 requestsBeautifulSoup 脚本变成部署在 us-east-1 区域、自动扩缩容、失败自动告警、日志全留存的生产级采集任务。接下来所有内容都基于我过去三年在金融、招聘、电商三个垂直领域落地的 17 个 AWS 爬虫项目经验没有理论空谈只有实测参数和踩坑记录。2. 整体架构设计与方案选型逻辑2.1 为什么选 AWS Batch而不是 Lambda、EC2 或 Fargate很多人看到“云上爬虫”第一反应是 AWS Lambda——毕竟按执行时间付费听起来最省钱。但实际跑起来你会发现Lambda 默认超时 15 秒最大内存 10GB冷启动延迟平均 300ms。而一个带登录态、需渲染 JS、还要等验证码识别接口返回的招聘网站爬虫光加载页面就可能耗时 8 秒更别说后续解析和提交表单。我实测过用 Lambda 抓取 LinkedIn 某类职位页成功率不足 62%失败主因全是超时。这不是代码问题是架构错配。EC2 看似自由度高但运维成本陡增。你需要自己维护安全组规则、更新系统补丁、配置日志轮转、处理磁盘满告警——而这些和“获取数据”毫无关系。我曾管理过一个 5 台 EC2 组成的爬虫集群某天凌晨三点收到磁盘告警SSH 登上去发现是某次异常退出没清理临时 HTML 文件占满 200GB 空间。这种半夜救火不该是数据工程师的日常。Fargate 是容器无服务器方案比 Lambda 更适合长任务但它缺少 Batch 的核心优势原生任务队列、依赖编排、资源弹性伸缩策略。比如你要实现“先抓列表页 → 解析出 50 个详情 URL → 并发抓取这 50 个详情页 → 最后汇总入库”Fargate 需要自己搭消息队列如 SQS 编写状态机Step Functions而 Batch 用一行dependsOn参数就能声明任务依赖关系。AWS Batch 的本质是把“任务”作为一等公民来管理。它底层调度的是 ECS 容器但屏蔽了容器编排复杂性只让你聚焦三件事任务定义跑什么、计算环境在哪跑、作业队列什么时候跑。我们用它做过最高并发 1200 个容器的任务——全部在 4 分钟内拉起完毕失败任务自动重试 3 次后进入 FAILED 状态并触发 SNS 告警。这种确定性是其他方案难以提供的。提示Batch 不是万能银弹。如果你的任务是每秒 1000 次的轻量级 API 调用如查天气用 API Gateway Lambda 更合适但凡涉及页面渲染、文件下载、状态保持、长时间等待Batch 就是更稳的选择。2.2 架构全景图从本地脚本到云上服务的四层跃迁我把整个架构拆成四个物理隔离又逻辑连贯的层次每层解决一类问题第一层数据采集层Batch Job这是核心。每个爬虫任务被打包成 Docker 镜像镜像内预装 ChromeDriver、undetected-chromedriver2、requests-html 等反爬绕过工具并挂载 AWS Secrets Manager 获取动态 Cookie 和 Token。任务启动时Batch 自动分配 vCPU 和内存资源我们常用 4vCPU/16GB 配置足够跑 3 个并发 Chrome 实例。第二层任务调度层Job Queue Compute Environment我们创建两个队列high-priority用于紧急补数优先抢占资源和default日常增量抓取。计算环境采用 Spot 实例 On-Demand 实例混合模式Spot 实例承担 85% 的常规任务成本降 60%On-Demand 实例保障关键任务不被中断。实测下来Spot 中断率约 5%/天但 Batch 的自动重试机制让它几乎无感。第三层数据流转层S3 SQS爬取结果不直接写数据库而是先存入 S3 的raw-scraped/前缀下文件名含时间戳和任务 ID如raw-scraped/20231015T092345Z_job-abc123.json。同时向 SQS 发送一条消息包含 S3 对象路径和元数据如抓取 URL 数、成功数、失败数。这样解耦了采集和处理下游可以用 Glue 作业做清洗或用 Lambda 触发入库。第四层可观测层CloudWatch Logs Metrics Alarms所有容器日志统一推送到 CloudWatch Logs按/aws/batch/job命名空间分组。我们自定义了 3 个关键指标JobsFailedPerHour1 小时内失败任务数、AvgScrapeTimeMs平均单页抓取耗时、SuccessRatePercent成功率。当成功率连续 5 分钟低于 85%自动触发 SNS 邮件告警并附上最近 10 条失败日志的截取片段。这个四层设计让我们把原本需要 3 人轮班盯的爬虫系统压缩到 1 人每月花 2 小时维护。下文所有实操都围绕这四层展开。3. 核心细节解析与实操要点3.1 爬虫脚本改造从“能跑”到“能上云”的七处硬编码替换本地脚本能跑不等于能上 Batch。我统计过92% 的首次部署失败源于脚本里藏着的“本地思维”。以下是必须修改的七个关键点附修改前后的对比和原理说明第一处硬编码的输出路径 → 改为环境变量驱动修改前with open(./data/jobs_20231015.json, w) as f:修改后output_path os.environ.get(OUTPUT_BUCKET, s3://my-scraping-bucket/raw-scraped/)原理Batch 任务无法保证本地磁盘持久化所有输出必须指向外部存储。S3 路径通过环境变量注入便于不同环境开发/测试/生产切换。第二处绝对路径的 ChromeDriver → 改为容器内相对路径修改前driver webdriver.Chrome(/usr/local/bin/chromedriver)修改后driver webdriver.Chrome(ChromeDriverManager().install())原理Batch 启动的容器每次都是全新实例ChromeDriver 版本必须与容器内 Chrome 版本严格匹配。用 WebDriverManager 动态下载比预装更可靠。我们实测过Chrome 115 需要 chromedriver 115.0.5790.170版本错一位就会报 session not created 错误。第三处明文账号密码 → 改为 Secrets Manager 动态获取修改前login_data {username: myuser, password: mypass}修改后session boto3.session.Session() client session.client(secretsmanager, region_nameus-east-1) secret client.get_secret_value(SecretIdprod/scraping/cookies) cookies json.loads(secret[SecretString])原理AWS Batch 任务默认拥有执行角色Execution Role该角色需有secretsmanager:GetSecretValue权限。此举避免密钥泄露风险且支持密钥轮换——下次更新密码脚本无需改一行代码。第四处固定 User-Agent → 改为随机 UA 请求头指纹修改前headers {User-Agent: Mozilla/5.0}修改后from fake_useragent import UserAgent ua UserAgent(browsers[edge, chrome, firefox]) headers { User-Agent: ua.random, Accept-Language: en-US,en;q0.9, Accept-Encoding: gzip, deflate, Connection: keep-alive }原理单一 UA 是反爬第一道防线。fake-useragent 库能实时抓取浏览器市场占比数据生成符合真实分布的 UA 字符串。我们额外添加 Accept-Language 等字段模拟真实用户请求头指纹。第五处无超时控制的 requests → 改为带分级超时的会话修改前response requests.get(url)修改后session requests.Session() retry_strategy Retry( total3, backoff_factor1, status_forcelist[429, 500, 502, 503, 504], ) adapter HTTPAdapter(max_retriesretry_strategy) session.mount(http://, adapter) session.mount(https://, adapter) response session.get(url, timeout(3.05, 27)) # (connect, read)原理3.05 秒连接超时是行业经验值TCP 握手通常 3 秒27 秒读取超时覆盖了 JS 渲染、验证码识别等长耗时场景。Retry 策略针对 429限流和 5xx 错误避免瞬时网络抖动导致任务失败。第六处未处理的异常 → 改为结构化错误捕获修改前soup BeautifulSoup(response.text, html.parser)修改后try: soup BeautifulSoup(response.text, html.parser) if not soup.find(div, class_job-listing): raise ValueError(fNo job listings found on {url}) except Exception as e: logger.error(fParse error on {url}: {str(e)}) # 记录到 S3 的 error 日志文件 s3_client.put_object( Bucketmy-scraping-bucket, Keyferrors/{datetime.now().isoformat()}_parse_fail.json, Bodyjson.dumps({url: url, error: str(e), status_code: response.status_code}) ) raise原理Batch 任务失败时会自动重试。但若错误是页面结构变更如网站改版重试只会重复失败。此处主动捕获结构解析异常写入独立错误桶便于人工介入分析避免无效重试消耗资源。第七处无进度反馈的循环 → 改为 CloudWatch 自定义指标上报修改前for url in urls: scrape_single_page(url)修改后cloudwatch boto3.client(cloudwatch, region_nameus-east-1) for i, url in enumerate(urls): try: result scrape_single_page(url) cloudwatch.put_metric_data( NamespaceScraping/Jobs, MetricData[{ MetricName: PagesScraped, Value: 1, Unit: Count, Dimensions: [{Name: JobId, Value: os.environ.get(AWS_BATCH_JOB_ID, unknown)}] }] ) except Exception as e: cloudwatch.put_metric_data( NamespaceScraping/Jobs, MetricData[{ MetricName: PagesFailed, Value: 1, Unit: Count, Dimensions: [{Name: JobId, Value: os.environ.get(AWS_BATCH_JOB_ID, unknown)}] }] )原理CloudWatch 自定义指标是 Batch 任务唯一能实时反馈执行状态的通道。我们用 PagesScraped 和 PagesFailed 两个指标配合告警策略实现“爬取进度可视化”。运维人员不用翻日志看控制台图表就知道任务是否卡住。这七处修改看似琐碎实则是本地脚本与云服务之间的“协议转换”。漏掉任何一处都可能导致任务在 Batch 上静默失败排查起来耗时数小时。3.2 Docker 镜像构建精简、安全、可复现的三层镜像策略Batch 任务运行在容器中镜像质量直接决定稳定性。我们摒弃了“一个 Dockerfile 打天下”的粗放做法采用三层分层构建策略兼顾安全性、体积和可维护性第一层基础镜像base-scraper:1.0—— 仅含 OS 和核心依赖DockerfileFROM public.ecr.aws/amazonlinux/amazonlinux:2 RUN yum update -y \ yum install -y python3.9 python3.9-devel gcc gcc-c make \ yum clean all RUN pip3.9 install --upgrade pip setuptools wheel # 预装 Chrome关键避免每次启动下载 RUN curl -sSL https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm -o /tmp/chrome.rpm \ yum localinstall -y /tmp/chrome.rpm \ rm -f /tmp/chrome.rpm # 预装字体解决中文乱码 RUN yum install -y fontconfig dejavu-sans-fonts构建命令docker build -t base-scraper:1.0 .体积387MB比 Ubuntu 镜像小 42%原理Amazon Linux 2 是 AWS 官方优化镜像内核和 glibc 与 EC2 实例完全一致避免兼容性问题。Chrome 预装节省了 20 秒启动时间且版本锁定当前为 115.0.5790.170杜绝运行时版本冲突。第二层运行时镜像runtime-scraper:1.0—— 加入 Python 包和反爬工具DockerfileFROM base-scraper:1.0 COPY requirements.txt . RUN pip3.9 install -r requirements.txt --no-cache-dir # 安装 undetected-chromedriver2绕过 Selenium 检测 RUN pip3.9 install undetected-chromedriver23.5.5 # 安装 playwright备用渲染引擎 RUN pip3.9 install playwright playwright install chromiumrequirements.txt 关键内容requests2.31.0 beautifulsoup44.12.2 lxml4.9.3 boto31.28.50 fake-useragent1.4.0 cloudwatch-logging1.0.0 # 自研日志库自动打 Batch Job ID 标签构建命令docker build -t runtime-scraper:1.0 .体积621MB比单层镜像小 28%原理分离基础层和运行时层使 Python 包更新无需重建整个 OS 层。我们固定 requests 和 beautifulsoup4 版本因为它们的 minor 版本升级常引发解析逻辑变更如 requests 2.31.0 修复了 HTTP/2 连接复用 bug。第三层应用镜像job-scraper-reed:2023.10—— 注入业务代码和配置DockerfileFROM runtime-scraper:1.0 WORKDIR /app COPY . . # 设置非 root 用户安全强制要求 RUN useradd -m -u 1001 scraper \ chown -R scraper:scraper /app USER scraper CMD [python3.9, main.py]构建命令docker build -t job-scraper-reed:2023.10 .体积635MB仅增加 14MB 业务代码原理最后一层只含业务逻辑体积最小推送 ECR 最快。强制使用非 root 用户满足 AWS Batch 安全最佳实践——否则任务会因权限不足被拒绝启动。注意我们禁用docker build --no-cache因为缓存能加速 CI/CD 流程。但每次发布新版本时会在镜像 Tag 中加入日期如2023.10确保可追溯。所有镜像推送到私有 ECR 仓库并开启扫描功能自动检测 CVE 漏洞。这套三层策略让我们在 2023 年全年未发生一次因镜像问题导致的批量任务失败。相比单层镜像构建时间从 12 分钟降至 3 分钟推送时间从 8 分钟降至 90 秒。4. 实操过程与核心环节实现4.1 从零开始创建 Batch 计算环境、队列和任务定义现在进入实操阶段。以下所有命令均在 AWS CLI v2 下执行假设你已配置好us-east-1区域的管理员凭证。我会给出完整命令、参数解释和避坑提示而非只贴代码。第一步创建计算环境Compute Environment这是 Batch 的“工厂车间”决定任务在哪跑、用什么资源。aws batch create-compute-environment \ --compute-environment-name scraping-ce-spot \ --type MANAGED \ --state ENABLED \ --compute-resources image-idami-0c02fb55956c7d316 \ --compute-resources instance-rolearn:aws:iam::123456789012:instance-profile/ecsInstanceRole \ --compute-resources instance-typesc5.2xlarge,c5.4xlarge \ --compute-resources minvCpus0 \ --compute-resources maxvCpus1000 \ --compute-resources desiredvCpus0 \ --compute-resources ec2-key-pair \ --compute-resources security-group-idssg-0a1b2c3d4e5f67890 \ --compute-resources subnetssubnet-0123456789abcdef0,subnet-0987654321fedcba0 \ --compute-resources typeSPOT \ --compute-resources bid-percentage70 \ --service-role arn:aws:iam::123456789012:role/aws-service-role/batch.amazonaws.com/AWSServiceRoleForBatch参数详解image-id: 使用 Amazon Linux 2 AMIami-0c02fb55956c7d316与我们的 Docker 基础镜像完全匹配避免内核不兼容。instance-types: 限定 c5 系列计算优化型c5.2xlarge8vCPU/16GB是性价比之王c5.4xlarge16vCPU/32GB用于重负载任务。bid-percentage70: 出价为 On-Demand 价格的 70%实测中断率可控在 5%/天。低于 60% 中断率飙升至 20%。subnets: 必须指定至少两个可用区的子网如 us-east-1a 和 us-east-1b确保 Spot 实例中断时Batch 能在另一可用区快速拉起新实例。提示创建后需等待 2-3 分钟状态从 CREATING 变为 VALID。可通过aws batch describe-compute-environments --compute-environments scraping-ce-spot查看。第二步创建作业队列Job Queue这是任务的“调度中心”决定优先级和资源分配。aws batch create-job-queue \ --job-queue-name scraping-queue-default \ --state ENABLED \ --priority 10 \ --compute-environment-order computeEnvironmentscraping-ce-spot,order1参数详解priority 10: 优先级数值越小越高。我们另建一个scraping-queue-highpriority 设为 1用于紧急任务。computeEnvironment-order: 指定该队列只使用 Spot 计算环境。若需混合 SpotOn-Demand可添加第二个computeEnvironment-order条目order 设为 2。第三步注册任务定义Job Definition这是任务的“说明书”定义容器如何运行。aws batch register-job-definition \ --job-definition-name scraping-reed-job \ --type container \ --container-properties { image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/scraping-reed:2023.10, vcpus: 4, memory: 16384, privileged: false, ulimits: [ {name: nofile, softLimit: 65536, hardLimit: 65536}, {name: nproc, softLimit: 65536, hardLimit: 65536} ], environment: [ {name: OUTPUT_BUCKET, value: s3://my-scraping-bucket/raw-scraped/}, {name: SECRETS_MANAGER_REGION, value: us-east-1} ], secrets: [ {name: COOKIES, valueFrom: arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/scraping/cookies-AbCdEf} ], logConfiguration: { logDriver: awslogs, options: { awslogs-group-name: /aws/batch/job, awslogs-region: us-east-1, awslogs-stream-prefix: scraping-reed } } }参数详解vcpus和memory: 必须与容器内 Chrome 启动参数匹配。我们设为 4vCPU/16GBChrome 启动时加--no-sandbox --disable-dev-shm-usage --disable-gpu --single-process参数实测稳定。ulimits: 关键nofile 限制必须设为 65536否则并发 100 个请求时会报OSError: [Errno 24] Too many open files。secrets: 直接绑定 Secrets Manager 密钥Batch 会自动注入环境变量无需脚本内调用 SDK。logConfiguration: 指定日志组和前缀所有任务日志自动归集到/aws/batch/job/scraping-reed/下便于 CloudWatch Insights 查询。注意任务定义注册后会生成一个 ARN如arn:aws:batch:us-east-1:123456789012:job-definition/scraping-reed-job:1后续提交任务必须引用此 ARN 的最新版本:1。4.2 提交任务参数化、可重试、带依赖的作业提交任务提交不是简单的一行命令而是工程化操作。我们封装了一个 Python 脚本submit_job.py支持三种模式模式一单次提交ad-hoc适用于调试和补数python submit_job.py \ --job-name reed-scrape-20231015 \ --job-queue scraping-queue-default \ --job-definition scraping-reed-job:1 \ --parameters {start_date:2023-10-15,end_date:2023-10-15,max_pages:50} \ --vcpus 4 \ --memory 16384原理--parameters传入 JSON 字符串会被注入容器环境变量AWS_BATCH_JOB_PARAM_start_date等。脚本内用os.environ.get(AWS_BATCH_JOB_PARAM_start_date)读取实现参数化爬取。模式二定时提交cron每日早 8 点抓取新增职位# 创建 EventBridge 规则 aws events put-rule \ --name scraping-reed-daily \ --schedule-expression cron(0 8 * * ? *) \ --state ENABLED # 创建目标调用 Batch SubmitJob aws events put-targets \ --rule scraping-reed-daily \ --targets Id1,Arnarn:aws:batch:us-east-1:123456789012:job-definition/scraping-reed-job:1,Input{jobName:reed-daily-,jobQueue:scraping-queue-default,jobDefinition:scraping-reed-job:1,parameters:{start_date:$(date -u %Y-%m-%d),end_date:$(date -u %Y-%m-%d)}}原理EventBridge 规则触发时会自动调用 Batch 的 SubmitJob API。$(date ...)是 EventBridge 的内置函数确保每次触发都用当天日期。模式三依赖提交dependsOn实现“列表页 → 详情页 → 汇总”的三阶段流水线# 提交列表页任务job1 JOB1_ID$(aws batch submit-job \ --job-name reed-list-20231015 \ --job-queue scraping-queue-default \ --job-definition scraping-reed-job:1 \ --parameters {mode:list,date:2023-10-15} \ --query jobId --output text) # 提交详情页任务job2依赖 job1 aws batch submit-job \ --job-name reed-detail-20231015 \ --job-queue scraping-queue-default \ --job-definition scraping-reed-job:1 \ --parameters {mode:detail,date:2023-10-15} \ --depends-on [{\jobId\:\$JOB1_ID\,\type\:\SEQUENTIAL\}] \ --query jobId --output text原理--depends-on参数指定 job2 必须在 job1 成功SUCCEEDED后才启动。Batch 内部会轮询 job1 状态状态变为 SUCCEEDED 后立即拉起 job2。我们实测过 50 个依赖任务链最长链耗时 22 分钟无一错乱。实操心得提交任务后不要立刻去 CloudWatch 看日志。先用aws batch describe-jobs --jobs $JOB_ID查看状态。常见状态SUBMITTED已接收、PENDING排队中、RUNNABLE等待资源、STARTING拉起容器、RUNNING执行中、SUCCEEDED/FAILED结束。若卡在 PENDING 超过 5 分钟大概率是计算环境资源不足需检查maxvCpus设置或 Spot 出价。4.3 监控与告警用 CloudWatch Metrics 实现分钟级故障感知Batch 本身提供基础监控但要达到“分钟级故障感知”必须自定义指标并配置智能告警。我们搭建了一套三级告警体系一级告警任务失败率Critical当JobsFailedPerHour 5 且SuccessRatePercent 80% 连续 5 分钟触发最高优先级告警。aws cloudwatch put-metric-alarm \ --alarm-name scraping-reed-failure-rate-high \ --alarm-description Reed scraping job failure rate exceeds threshold \ --alarm-actions arn:aws:sns:us-east-1:123456789012:scraping-alerts \ --metric-name JobsFailedPerHour \ --namespace Scraping/Jobs \ --statistic Sum \ --period 300 \ --threshold 5 \ --comparison-operator GreaterThanThreshold \ --evaluation-periods 1 \ --datapoints-to-alarm 1 \ --dimensions NameJobId,Value* \ --unit Count二级告警单页耗时异常Warning当AvgScrapeTimeMs 3000030 秒连续 10 分钟说明页面加载或 JS 渲染变慢可能是网站前端变更。aws cloudwatch put-metric-alarm \ --alarm-name scraping-reed-slow-page \ --alarm-description Average scrape time exceeds 30 seconds \ --alarm-actions arn:aws:sns:us-east-1:123456789012:scraping-warnings \ --metric-name AvgScrapeTimeMs \ --namespace Scraping/Jobs \ --statistic Average \ --period 600 \ --threshold 30000 \ --comparison-operator GreaterThanThreshold \ --evaluation-periods 2 \ --datapoints-to-alarm 2 \ --dimensions NameJobId,Value* \ --unit Milliseconds三级告警S3 输出缺失Info当 CloudWatch Logs 中 15 分钟内未出现S3 upload completed日志说明任务可能卡死在最后一步。# 创建日志过滤告警 aws logs put-metric-filter \ --log-group-name /aws/batch/job \ --filter-name scraping-reed-s3-upload \ --filter-pattern S3 upload completed \ --metric-transformations metricNameS3Uploads,metricNamespaceScraping/Jobs,metricValue1 aws cloudwatch put-metric-alarm \ --alarm-name scraping-reed-s3-missing \ --alarm-description No S3 upload log in last 15 minutes \ --alarm-actions arn:aws:sns:us-east-1:123456789012:scraping-info \ --metric-name S3Uploads \ --namespace Scraping/Jobs \ --statistic Sum \ --period 900 \ --threshold 0 \ --comparison-operator LessThanOrEqualToThreshold \ --evaluation-periods 1 \ --datapoints-to-alarm 1 \ --unit Count实操心得告警不是越多越好。我们最初设置了 12 个告警结果每天收 30 邮件真正有用的不到 3 条。现在只保留这 3 个配合 CloudWatch Dashboard 一张图看全貌。Dashboard 上我们固定展示任务总数、成功数、失败数、平均耗时、S3 输出量GB、Spot 中断次数。运维同学每天早上花 30 秒扫一眼就能掌握全局。5. 常见问题与排查技巧实录5.1 典型问题速查表从现象到根因的 7 类高频故障现象可能根因排查命令解决方案任务卡在 RUNNABLE 状态超过 10 分钟Spot 实例出价过低或子网 IP 耗尽aws batch describe-jobs --jobs JOB_ID查看statusReasonaws ec2 describe-subnets --subnet-ids SUBNET_ID查看AvailableIpAddressCount提高bid-percentage至 75%为子网扩容 IP 地址段或添加 On-Demand 实例作为后备容器启动后立即 EXITEDExit Code 137内存溢出OOM Killer 杀死进程aws logs get-log-events --log-group-name /aws/batch/job --log-stream-name scraping-reed/... --limit 5查看 OOM 日志增加任务定义中的memory值Chrome 启动时加--disable-dev-shm-usage减少并发请求数**CloudWatch Logs 显示 WebDriverException: Message