Python爬虫把自己爬死了,如何排查和解决?
我弄了一个爬虫程序, 没有把服务器爬死,倒是把自己网络经常弄挂,
大概就是用 gevent 创建一个 Pool(50) , 50 个并发,
用 Requests 来迭代 (有打 m onkey patch )
url 大概是接近 10000 个,
速度其实还挺快的, 一秒能处理接近 300 个 requests , for 迭代,没有 sleep
但是现在的问题是,每次连续处理 3000 个左右还好,再多了,经常就把本地网络弄挂了,导致程序也 timeout 退出。
一两分钟内,经常网络也连不上。不知道是不是我程序写的太耗系统资源,可能要稍微在一段任务后休息一下。
现在的解决办法就是, 把任务分成小块,每块大概 2000 个, 每爬了 2000 个就休息几秒。
有没有更科学的方法,或者是我哪里使用不当?
Python爬虫把自己爬死了,如何排查和解决?
这个问题我遇到过,典型的爬虫把自己搞死的情况。核心原因就几个:内存泄漏、递归死循环、或者请求爆炸把资源耗尽了。
先上诊断代码,跑一下就能看到问题在哪:
import tracemalloc
import threading
import psutil
import time
from collections import defaultdict
class SpiderMonitor:
def __init__(self):
self.request_count = defaultdict(int)
self.active_threads = []
def start_memory_tracking(self):
"""追踪内存泄漏"""
tracemalloc.start()
def check_memory_leak(self):
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[内存诊断]")
for stat in top_stats[:10]:
print(f"{stat.traceback.format()[:200]} -> {stat.size/1024:.2f} KB")
def check_deadlock(self):
"""检测死锁"""
for thread in threading.enumerate():
if thread.is_alive():
print(f"[线程状态] {thread.name}: {thread.ident}")
def monitor_requests(self, url):
"""监控请求频率"""
self.request_count[url] += 1
if self.request_count[url] > 100: # 单个URL请求超过100次
print(f"[警告] {url} 可能陷入循环,已请求{self.request_count[url]}次")
return False
return True
# 在爬虫关键位置插入监控
monitor = SpiderMonitor()
monitor.start_memory_tracking()
# 在你的请求函数里加这个
def make_request(url):
if not monitor.monitor_requests(url):
raise Exception(f"检测到循环请求: {url}")
# ... 原来的请求代码
常见死法及解法:
- 递归解析把自己递归死了 - 检查解析逻辑,确保有终止条件,别无限解析子页面
- 内存泄漏 - 用上面的监控代码,看哪个对象一直不释放
- 请求风暴 - 控制并发数,加个简单的限流:
from threading import Semaphore
concurrent_limit = Semaphore(10) # 最多10个并发
with concurrent_limit:
# 你的请求代码
- 被自己绕进去了 - 检查URL去重,用BloomFilter或者简单set:
visited_urls = set()
if url in visited_urls:
return
visited_urls.add(url)
跑一下监控代码,看输出就知道死在哪了。多数情况是URL去重没做好,在几个页面里来回爬。
总结:加监控,控制并发,做好去重。
感觉是:请求太多了,一次发不出那么多请求,全排路由缓存里了,然后把缓存挤爆了后面的都排不进去了就超时了,然后处理这些请求花了一两分钟,换个加宽带宽,然后异步请求更好一点
我也怀疑是路由器,这个是华为的 3000 多的路由器,按说不至于这么脆弱啊。
我确实在阿里云上测试过,毫无压力。
说错了, netgear 的路由器
NetGear 3000+的路由器,,那只可能是 R9000 了?
R8500
现在在外面一个小茶楼, 居然这边的网络也毫无压力。。。。
看来是我家里有设备有问题,我中间搭配了几个光猫,还有个苹果的 Airport Extreme
肯定是路由器设置问题. 找找一般抗攻击之类的设置.
不要开 Qos , 网件的 Qos 好奇怪,连测速的都给搞一下。
我没有开 Qos
记得以前用 zmap ,速度过快就全堆内存里了, 24G 内存堆满就报错,网卡还要处理十分钟
楼主广东人?
把家里的网络拓扑图贴出来让大家看看呗?
端口刷太多,然后不释放,锁死了??
家用宽带应该有最大连接数限制吧。
本机 gevent+requests 跑百万 url 没问题,路由器应该事设置有问题吧
求个 python 爬虫工程师,两年爬虫经验。
暴风体育 [email protected]
跪求人才,欢迎咨询。
用软路由试试吧!
量太大了,家用路由器扛不住的,我的 R8000 和 6300 被我跑崩好多次
最后发现, 路由器表示不背锅, 是我的程序写的有问题
在 gevent 调用的函数里面,我直接用的 Requests.get 来下载页面,这个方式下不能复用连接,也没有主动去调用关闭,所以程序保持了几千个服务器之间的连接,最后把路由器玩死了。
现在修改成用 session 来 get ,一切完美了,速度也快了好多。
nCount = 50
connection_limit = nCount
adapter = requests.adapters.HTTPAdapter(pool_connections=connection_limit,
pool_maxsize=connection_limit)
session = requests.session()
session.mount(‘http://’, adapter)
fetchpool = Pool(nCount)
for job in jobs:
fetchpool.spawn(self.foobar, session, job)
fetchpool.join()
def foobar(self, session, job):
session.get(…)
不过,之前也没留心,我的路由器也有一些问题,非常不稳定,尤其是 wifi ,干扰严重,即使我已经选择了别人都没有用的 channel
几千的连接就能跑挂…
我是建议先把渣渣网件固件换了
啊 8500 啊,那再见…
网络搞挂就只能升级网络了啦~
说到爬虫,我这里给分享个爬虫的学习笔记
http://log4geek.cc/2017/03/零基础 12 天从入门到精通 python 爬虫 /


