Python爬虫把自己爬死了,如何排查和解决?

我弄了一个爬虫程序, 没有把服务器爬死,倒是把自己网络经常弄挂,

大概就是用 gevent 创建一个 Pool(50) , 50 个并发,

用 Requests 来迭代 (有打 m onkey patch )

url 大概是接近 10000 个,

速度其实还挺快的, 一秒能处理接近 300 个 requests , for 迭代,没有 sleep

但是现在的问题是,每次连续处理 3000 个左右还好,再多了,经常就把本地网络弄挂了,导致程序也 timeout 退出。
一两分钟内,经常网络也连不上。不知道是不是我程序写的太耗系统资源,可能要稍微在一段任务后休息一下。

现在的解决办法就是, 把任务分成小块,每块大概 2000 个, 每爬了 2000 个就休息几秒。

有没有更科学的方法,或者是我哪里使用不当?
Python爬虫把自己爬死了,如何排查和解决?


24 回复

换个好路由器.


这个问题我遇到过,典型的爬虫把自己搞死的情况。核心原因就几个:内存泄漏、递归死循环、或者请求爆炸把资源耗尽了。

先上诊断代码,跑一下就能看到问题在哪:

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}")
    # ... 原来的请求代码

常见死法及解法:

  1. 递归解析把自己递归死了 - 检查解析逻辑,确保有终止条件,别无限解析子页面
  2. 内存泄漏 - 用上面的监控代码,看哪个对象一直不释放
  3. 请求风暴 - 控制并发数,加个简单的限流:
from threading import Semaphore
concurrent_limit = Semaphore(10)  # 最多10个并发

with concurrent_limit:
    # 你的请求代码
  1. 被自己绕进去了 - 检查URL去重,用BloomFilter或者简单set:
visited_urls = set()
if url in visited_urls:
    return
visited_urls.add(url)

跑一下监控代码,看输出就知道死在哪了。多数情况是URL去重没做好,在几个页面里来回爬。

总结:加监控,控制并发,做好去重。

感觉是:请求太多了,一次发不出那么多请求,全排路由缓存里了,然后把缓存挤爆了后面的都排不进去了就超时了,然后处理这些请求花了一两分钟,换个加宽带宽,然后异步请求更好一点

1L+1
如果有在用路由,先尝试不用路由,再跑一下.如果有用光猫把光猫改成桥接,直连电脑然后电脑拨号跑一下试试.

我前段时间也是写爬虫,就是这个问题,最开始,1 秒撑死直接处理不到 1000 个请求,上行带宽只用了不到 10M.后来去掉路由有提升但是还是没跑满,又把光猫改成桥接电脑直接拨号,问题就解决了.

所以,如果你确定代码没问题的话,检查一下自己网络会经过的设备,挨个排查吧.

可以确认 LZ 不玩 P2P ,是个好孩子

我也怀疑是路由器,这个是华为的 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 爬虫 /

回到顶部