一、批量扫描的痛点:当目标多的时候,你会不会卡?
很多开发者在做批量任务的时候,尤其是扫描类的,比如扫一批IP的端口、检查一批网站能不能访问,都会遇到“等好久结果才出来”的情况。比如要扫100个网站,每个扫一次要1秒,串行的话就要100秒,将近两分钟;要是1000个网站,就得十几分钟,别说效率了,连自己的耐心都快没了。而且这段时间里,电脑CPU可能只用到10%-20%,大部分时间都在等网络请求返回,相当于一边等外卖一边玩手机,手机电量只用了一点点,浪费资源不说,还耽误事。
1.1 你可能遇到的真实场景
举个肯定碰到过的例子:公司要检查一批服务器的80端口是否开放,运维给了你1000个IP的列表,让你做个小脚本扫一遍。你一开始写了个简单的串行脚本,跑了之后去喝了杯咖啡,回来还没跑完,一问才知道花了快半小时,而且电脑风扇转得飞起,说明资源没用到点子上。这就是典型的串行批量扫描的问题:只按顺序一个一个处理,没利用好空闲的网络等待时间,也没把CPU跑满。
1.2 传统串行扫描的问题
串行的逻辑说白了就是“做完这个再做下一个”,比如扫IP1,等1秒,扫IP2,等1秒,以此类推。这种方式的缺点很明显:第一是速度慢,总时间等于每个任务的时间之和;第二是资源浪费,当任务在等网络响应的时候,CPU是闲着的,没法处理其他任务;第三是扩展性差,目标数量多的时候,等待时间会线性增长,比如100个目标花100秒,1000个就花1000秒,完全撑不住。
二、用输入列表+并行解决,普通人也能会的优化办法
这里我们用Python的concurrent.futures库实现并行,这个库就像给你提供了一个“任务处理小组”,你把要做的任务(比如扫IP)和待处理的目标(输入列表)给它,它会安排几个人(线程)同时干活,不用手动管理线程,特别适合新手。
2.1 技术栈说明
本次示例用单一技术栈:Python 3.8+,核心用concurrent.futures.ThreadPoolExecutor实现并行,输入列表存储要扫描的目标IP,代码简洁,没有复杂的分布式或多进程知识,适合不同基础的开发者。
2.2 从串行到并行的代码对比
首先是串行扫描的代码,用来对比优化效果:
import socket
from datetime import datetime
# 输入列表:要扫描的100个本地IP
ip_list = ["192.168.1.{}".format(i) for i in range(1, 101)]
def scan_port(ip, port=80, timeout=2):
"""扫描指定IP的指定端口是否开放,返回结果字符串"""
try:
# 创建socket连接,设置超时时间2秒
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
# 连接指定IP和端口,返回0表示连接成功(端口开放)
result = sock.connect_ex((ip, port))
sock.close()
return f"{ip}:{port} 开放" if result == 0 else f"{ip}:{port} 关闭"
except Exception as e:
return f"{ip}:{port} 扫描出错,原因:{str(e)}"
# 串行扫描:逐个处理IP,统计耗时
start_time = datetime.now()
results = []
for ip in ip_list:
res = scan_port(ip)
results.append(res)
end_time = datetime.now()
print(f"串行扫描100个IP耗时:{(end_time - start_time).total_seconds():.2f}秒")
print("前5个结果示例:", results[:5])
这段代码跑起来大概需要200秒左右,因为每个IP扫描要等2秒,100个就是200秒,速度很慢。
然后是并行扫描的代码,优化后的版本:
import socket
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
# 输入列表:和串行一致,100个IP
ip_list = ["192.168.1.{}".format(i) for i in range(1, 101)]
def scan_port(ip, port=80, timeout=2):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((ip, port))
sock.close()
return f"{ip}:{port} 开放" if result == 0 else f"{ip}:{port} 关闭"
except Exception as e:
return f"{ip}:{port} 扫描出错,原因:{str(e)}"
# 并行扫描:用线程池控制并发,max_workers设为10(同时处理的任务数)
start_time = datetime.now()
results = []
# 使用with语句自动管理线程池,用完自动回收资源
with ThreadPoolExecutor(max_workers=10) as executor:
# 把每个IP的扫描任务提交给线程池,生成任务列表
futures = [executor.submit(scan_port, ip) for ip in ip_list]
# 遍历所有完成的任务,收集结果
for future in as_completed(futures):
results.append(future.result())
end_time = datetime.now()
print(f"并行扫描100个IP耗时:{(end_time - start_time).total_seconds():.2f}秒")
print("前5个结果示例:", results[:5])
这段代码跑起来大概只需要20秒左右,速度提升了10倍,CPU利用率也从10%左右升到了80%以上,完美解决了串行的资源浪费和效率低的问题。
2.3 输入列表的核心作用
刚才的ip_list就是输入列表,所有待处理的目标都存放在这里,不管是IP、URL还是其他内容,都可以用列表统一管理。比如要扫200个IP,只需要把range(1,101)改成range(1,201)就行,不用修改核心扫描逻辑,扩展性很强,是批量任务优化的基础载体。
三、实操中的注意事项,踩过的坑都告诉你
很多人用并行的时候会踩坑,比如并发数设太大导致程序崩溃,或者没处理异常导致整个任务挂掉,这里说三个最关键的注意点,帮你少走弯路。
3.1 并发数不是越大越好,要控制在合理范围
刚才的max_workers设成10,不是100,为什么?因为如果设成100,就是100个线程同时扫100个IP,相当于100个请求同时发向同一个服务器,对方可能会把你的IP拉黑,或者自己的网络带宽不够,导致每个请求都超时,反而更慢。就像你去买奶茶,平时10个人排队很快,要是突然100个人一起挤到柜台,反而没人买得成。一般来说,并发数设成10-20就够了,要是目标是不同的服务器,可以设高一点到50,但别超过100,不然会出问题。
3.2 异常处理必须加,不能省略
刚才的scan_port函数里加了try-except,要是没加的话,比如某个IP是无效的,扫的时候抛异常,整个并行任务就会挂掉,前面扫的结果都没了,白等半天。所以每个任务函数都要加异常处理,哪怕只返回个错误信息,也比整个程序崩溃好。比如扫网站的时候,网站可能超时、返回404,都要捕获异常,返回对应的结果,不影响其他任务继续执行。
3.3 资源回收要做好,避免内存泄漏
代码里用了with ThreadPoolExecutor,这个就是自动回收资源的,不用手动关线程池。要是不用with,就得最后加executor.shutdown(),不然线程池会一直占着内存,时间长了电脑会变慢。就像你用了一次性杯子,用完要扔,不然堆在桌上会乱,线程池用完也要关,with语句帮你自动管理,特别省心。
四、适用场景、优缺点和总结
4.1 到底什么时候用这个方案?
这个方案适合“有大量独立目标,每个目标的处理时间主要是网络等待”的场景,比如批量端口扫描、批量网站存活检测、批量API接口的状态检查、批量域名的DNS解析等。要是目标只有几个,或者每个目标的处理时间都很长(比如需要大量计算的),那并行的效果就不明显,甚至不如串行,因为并行会有一点管理线程的开销。
4.2 这个方案的优缺点
优点:第一是速度快,能把网络等待的时间利用起来,效率提升明显;第二是代码简单,用Python的ThreadPoolExecutor几行代码就能实现,不用复杂的知识;第三是资源利用率高,CPU和网络都能用到,不会浪费。缺点:第一是并发数控制不好的话,会被目标反爬,或者自己的网络出问题;第二是受限于网络带宽,要是带宽不够,并行再多也没用;第三是处理大量目标的时候,要是不做超时或结果去重,可能会漏掉一些结果。
4.3 总结
批量扫描遇到的效率瓶颈,本质上是没利用好并行的等待时间,用输入列表管理待扫描的目标,再用Python的线程池实现并行,注意并发数控制、异常处理和资源回收,就能轻松解决大部分问题。这个方案不用复杂的分布式或多进程,适合普通开发者快速上手优化批量扫描任务,不管是刚学Python的新手,还是有一定经验的开发者,都能快速落地使用。
评论
围绕“利用输入列表与并行机制优化批量扫描:应对大量目标时的效率瓶颈与资源消耗”参与讨论