java网页并发爬虫高效关键在于精准控并发、合理任务粒度及反爬与资源保护机制;需手动构建threadpoolexecutor,设core/maxpoolsize为5~15(推荐8),keepalivetime=0l,优选synchronousqueue或小容量linkedblockingqueue,自定义拒绝策略,仅网络请求入池,解析入库等cpu操作在当前线程完成,并叠加域名限流、随机延迟、响应驱动降级、semaphore控qps,配合监控与安全退出。

Java 中用线程池实现高效网页并发爬虫,关键不在“开多少线程”,而在于**精准控并发、任务粒度合理、配合反爬与资源保护机制**。固定大小线程池是基础,但必须搭配限速、域名隔离、响应反馈和优雅退出,才能真正稳定高效。
线程池选型与核心参数配置
不用 Executors 工厂方法,直接使用 ThreadPoolExecutor 手动构建:
- corePoolSize = maximumPoolSize:设为 5~15(推荐起始值 8),代表你允许同时发出的 HTTP 请求数;两者相等可禁用动态扩容,防止突发流量打爆目标或本地连接数
- keepAliveTime = 0L:非核心线程不保留,避免空闲线程干扰活跃数统计
- BlockingQueue:优先选 SynchronousQueue(无缓冲,任务直达线程),或小容量 LinkedBlockingQueue(100) 防瞬间堆积
- 拒绝策略必须自定义:比如记录 URL + 状态 + 时间戳,并加入退避重试队列,而不是直接抛异常中断整个爬取流程
任务划分要分层:只让网络请求进池子
常见错误是把下载、解析、存库全塞进线程池——这会让「并发数」失真,无法反映真实对外压力:
- 仅将 HTTP 请求发起(如 HttpClient.execute() 或 OkHttp.newCall().execute())封装为 Runnable/Callable 提交到线程池
- HTML 解析、DOM 提取、链接去重、数据入库等 CPU 密集操作,全部在当前线程内完成,不占用线程池额度
- 这样能确保 getActiveCount() ≈ 当前正在发出去的请求数,便于监控和限流
必须叠加多层限速与反爬适配
单靠线程池数量控制远远不够,需结合运行时信号动态调节:
- 域名级限流:对每个 host 维护独立计数器 + 滑动窗口(如每秒最多 3 请求),可用 Guava RateLimiter 或自研
- 随机延迟:每次请求前 sleep(100–400ms),降低节奏规律性,减少被识别为机器流量的风险
- 响应码驱动降级:监听 429、503、超时、Connection refused 等信号,触发临时下调 corePoolSize(如减 1),恢复后缓慢回升
- 双层限速更稳:线程池控最大并发数,再加一个 Semaphore 控 QPS(如每秒最多 8 个许可),acquire()/release() 必须包裹在 try-finally 中
生产环境必备:监控与安全退出
没有可观测性和可控终止的爬虫,上线即事故:
- 定期打印 getActiveCount()、getCompletedTaskCount()、getQueue().size(),接入日志或 Prometheus
- 关闭前调用 shutdown(),再用 awaitTermination(30, TimeUnit.SECONDS) 等待活跃请求结束
- 务必同步关闭底层 HTTP 客户端连接池(如 CloseableHttpClient.close() 或 OkHttp Dispatcher.shutdown()),否则残留连接会泄漏
- 建议设置 JVM shutdown hook,在进程被 kill 时执行上述清理逻辑
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











