如果你正在追《Python安全攻防:渗透测试实战指南》这套系列笔记,看到这里应该已经撑过了前两篇。第一篇主要在铺路,讲环境准备和工具链认知;第二篇开始热身,把Python基础语法和网络协议基础过了一遍。到了第三篇,我想换个节奏——不再单独讲概念,而是把渗透测试里最常遇到的几个场景直接摆出来,用Python去解。很多人觉得"我会写爬虫"和"我能做测试"中间隔着十万八千里,这篇笔记主要就是补这段距离:环境配置里那些坑、如何把测试任务翻译成脚本逻辑、Web攻防中最常用的请求与数据处理操作、以及如何在Kali Linux里把脚本当成测试副驾。说实话,这阶段的练习不一定能直接让你"上手打点",但能让你在靶场里把每一步操作都变得可重复、可验证、可解释。
如果你已经装好了Python,看过一点基础语法,但总觉得"会写代码"和"能做测试"之间隔着一层窗户纸,这篇笔记就是给你准备的。我会尽量把当时的学习过程还原出来,包括那些让我抓狂的报错。
1. 先解决环境和依赖问题:这些安装细节最容易被忽视
很多初学者学着学着就卡在环境上,而不是卡在思路上。你看热搜里还有人搜"python安装random",这其实是没分清标准库和第三方库。random是Python自带的库,直接import random就能用,根本不需要安装。会搜这个词,说明基础阶段对"库"的概念还没建立起来。这不丢人,但我建议在学习安全攻防之前,先把环境这关彻底趟平,不然后面写脚本会非常痛苦。
1.1 版本选择:为什么我推荐3.10.x而不是最新版
我当时的电脑上其实装了两个Python版本,一个是系统自带的旧版本,一个是我手动装的3.10.6。做渗透测试相关的学习,3.10.x是我比较推荐的版本线。原因是很多安全工具和依赖库更新速度没那么快,特别是带C扩展的库,会在最新版本上出现"尚未适配"的尴尬情况。而3.8和3.9虽然稳定,但部分新语法特性没法用,写起来不够痛快。
安装的时候有一个细节特别容易翻车:Windows下安装Python,一定要在第一屏勾选Add Python to PATH。这句话看起来不起眼,但如果不勾选,后面在cmd里输入python会直接告诉你"不是内部或外部命令"。我在帮朋友排查这类问题时,十次里有八次是这个问题。装完以后务必开一个新的终端窗口验证一下:
python --version pip --version如果pip不是最新版,顺手升级一下:
python -m pip install --upgrade pip在Linux(包括Kali)里,情况略有不同。系统通常自带Python,但可能是2.7和3.x并存的老结构。这时候不建议折腾系统自带的Python,容易把系统搞坏。Kali本身有很多工具依赖特定版本的Python,你手动装一个独立的Python 3.10.x,或者直接用apt install python3,然后通过虚拟环境隔离,是最稳妥的做法。
1.2 虚拟环境:每个项目一套依赖,别当大锅乱炖
一开始我不爱用虚拟环境,觉得"多此一举"。直到有一次写Web测试脚本,需要requests库,而同一个系统里另一个项目用的是老版本Flask,两边依赖一冲突,删了装、装了删,整整折腾了一个下午。从那时候起,我就养成了一个习惯:只要是新建的Python项目,第一件事就是建虚拟环境。
# 创建虚拟环境 python -m venv venv # Windows激活 venv\Scripts\activate # Linux / macOS激活 source venv/bin/activate在Kali里我更推荐用pipenv或者virtualenv,因为Kali系统环境比较娇贵,某个工具链被依赖冲突搞乱了,网卡驱动或者扫描工具可能就跟着出问题。安全测试本身就是强依赖环境的工作,保持系统Python干净,是基本的职业素养。
1.3 pip换源:这不是偷懒,是省命
国内网络环境下载PyPI包,速度时快时慢。我不绕弯子,直接说最实用的做法:配置清华源或阿里源。配置文件在pip config里设置,或者使用命令指定:
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple如果想一劳永逸,可以在用户目录下创建pip.ini(Windows)或~/.pip/pip.conf(Linux),写入:
[global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple trusted-host = pypi.tuna.tsinghua.edu.cn至于安全测试里最常用到的库,我没有装很多,核心就这么几个:
| 库名 | 用途 | 安装命令 |
|---|---|---|
| requests | HTTP请求构造与响应处理 | pip install requests |
| beautifulsoup4 | 解析HTML,提取页面结构 | pip install beautifulsoup4 |
| lxml | 高性能解析器,配合BeautifulSoup使用 | pip install lxml |
| urllib3 | requests的底层依赖,偶尔直接用 | pip install urllib3 |
| opencv-python | 图像处理,注意import名是cv2 | pip install opencv-python |
| matplotlib | 数据可视化,画图时常用 | pip install matplotlib |
这里有个大坑要提醒一下:cv2不是一个能直接安装的库名,它的真正身份是opencv-python。热搜里搜"python下载cv2",就是因为没搞清这个关系。装完之后import cv2才有对应的模块。
1.4 安装失败的常见原因和处理姿势
我在学习过程中反复遇到安装问题,总结下来无非四种情况:
- 权限不足:Windows下进入终端时没有管理员权限,Linux下忘了
sudo。系统会报Permission denied。 - 网络超时:连接PyPI超时,报ReadTimeoutError。解决方法是换源加延长超时时间:
pip install 库名 --timeout 60。 - 依赖冲突:某个库要求A库版本不低于2.0,但另一个库要求不高于1.5。这种问题最头疼,直接进虚拟环境新建一个项目解决。
- 编译器缺失:个别带C扩展的库在Windows下需要编译环境,比如旧版的scrapy。解决方案是安装对应库的预编译wheel包,或者直接升级pip并重试。
把环境问题解决掉,后面写脚本的效率会高很多。很多教程一上来就让你pip install这个那个,却没人告诉你装完之后怎么验证、怎么判断装没装对。我的经验是:每装一个库,都先开一个Python交互环境,import一下,再关门走人。比如装完requests,就执行:
import requests print(requests.get("https://httpbin.org/get").status_code)能通,说明环境真的可以用。
2. 把渗透测试任务翻译成Python表达式:变量、函数与结构化数据
环境弄好之后,下一步就是从"看得懂Python"过渡到"用Python想问题"。这一步没法靠抄代码解决,得靠一种思维转换:把一个测试任务拆成输入、处理、输出三个环节。我在学第三篇时,明显感觉到自己开始用这种方式拆渗透测试里的动作了。
2.1 为什么安全脚本要多写函数,而不是堆一段顺序代码
刚开始写测试脚本,我喜欢一口气把逻辑全写在main里,看起来直截了当。但遇到稍微复杂点的任务,比如要对多个URL做请求、对结果去重、把命中的目标保存成文件,顺序代码立马变成一锅粥。写成函数之后,每个测试动作都有一个明确的名字,读代码的人(包括三天后的自己)一眼就知道这段在干什么。
import requests def get_headers(url, timeout=5): resp = requests.get(url, timeout=timeout) return resp.headers def check_status(url): try: resp = requests.get(url, timeout=5) return resp.status_code except requests.exceptions.RequestException as e: return f"error: {e}"把动作封装成函数,还有一个好处:可以单独测试每一个环节。比如check_status逻辑对不对,可以只调用这个函数,不需要把整个流程跑一遍。这正是渗透测试里经常需要的——你要确认的是"某个判断逻辑对不对",而不是"整个脚本跑不跑得通"。函数封装得越细,后续扩展就越省力。
2.2 类型转换:数据清洗是安全脚本的底线
渗透测试脚本里最容易出错的点,往往不是逻辑本身,而是数据类型。比如你从一个响应文本里拿到一个数字,这个数字在你眼里是数字,在Python里其实是个字符串。拿字符串和整数比较,永远返回False,你还找不到原因。
# 错误示范:字符串与数字比较 content_length = "1234" if content_length > 1000: print("长度符合条件") # TypeError: '>' not supported between instances of 'str' and 'int' # 正确做法:先转换再比较 content_length = int("1234") if content_length > 1000: print("长度符合条件")类似的还有bytes和str的混淆。用requests获取的resp.content是字节类型,resp.text是字符串类型。你要用正则去匹配内容,最好用resp.text;你要保存原始二进制数据,才用resp.content。这两者之间偶尔需要转换:str(bytes_data, "utf-8")或者bytes_text.encode("utf-8")。我见过不少人因为编码问题把本来能用的脚本搞到怀疑人生,所以建议一开始就养成习惯:凡是外部传入的数据,默认它是不可信的,先清洗再使用。
2.3 数组切片与结构化数据:批量目标怎么存怎么取
渗透测试里经常要批量处理目标,比如一堆IP、一堆域名、一堆URL。这批数据在Python里最自然的表达就是列表。
# 目标列表 targets = [ "192.168.1.1", "192.168.1.2", "192.168.1.3", "192.168.1.4", "192.168.1.5", ] # 只取前2个做小范围验证 probe_targets = targets[:2] print(probe_targets) # ['192.168.1.1', '192.168.1.2']数组切片看着基础,但在写测试脚本时非常实用。比如你要先在少量目标上验证某个策略是否可行,再放量跑,切片[:2]就派上用场了。结构再复杂一点,目标之间的关系可以用邻接矩阵来表达,Python里直接用嵌套列表就可以:
# 邻接矩阵示例:节点间是否连通 adj_matrix = [ [0, 1, 0], [1, 0, 1], [0, 1, 0], ]做子域名枚举、端口关联这类逻辑时,嵌套列表能非常直观地保存"哪个节点连着哪个节点",比用一堆变量到处传要清晰得多。用到字典和JSON解析也很常见。比如接口返回一个JSON结构的数据:
import json response_text = '{"status": "success", "data": {"ip": "192.168.1.1", "port": 8080}}' data = json.loads(response_text) print(data["data"]["port"])结构化数据是测试脚本的地基。目标列表、请求参数、响应结果,全部用合理的数据结构组织好,后面无论做循环、去重、统计还是比对,都会顺手很多。
2.4 队列与"不堵塞":扫描类任务的并发小课本
热搜里排着"python队列queue不堵塞",这句描述其实有点误导。queue本身就是堵塞的,因为get()方法在没有数据时确实会阻塞线程,等待新任务。真正让人困惑的,是"如何让多个任务同时生产、同时消费"。在渗透测试里,这种场景太常见了——你要扫一批目标,如果一个一个串行处理,效率低到想哭;如果乱开线程,又容易把目标网站请求打崩。
我当时学的标准做法是:用一个线程池做任务派发,任务对象全放进队列里,消费者从队列里取任务执行。
import queue import threading import time task_queue = queue.Queue() def worker(): while True: url = task_queue.get() if url is None: break print(f"processing: {url}") time.sleep(0.5) # 模拟请求耗时 task_queue.task_done() # 启动多个工作线程 threads = [] for i in range(3): t = threading.Thread(target=worker) t.start() threads.append(t) # 往队列里塞任务 for url in ["http://example.com", "http://testphp.vulnweb.com", "http://demo.testfire.net"]: task_queue.put(url) task_queue.join() # 等待所有任务完成 # 结束线程 for t in threads: task_queue.put(None) for t in threads: t.join()这里的关键是理解task_done()和join()的配合:每处理完一个任务就调用一次task_done(),队列的计数器减一;queue.join()会一直阻塞,直到所有任务都被标记完成。这样写的好处是不会出现"任务还没处理完,主线程就退出"的尴尬。如果你用asyncio写异步代码,那要小心requests库是同步库,不能直接在协程里调用,不然会阻塞整个事件循环。想要异步请求,要么用httpx这类自带异步支持的库,要么老老实实用线程池。我个人的建议是:初学阶段先用线程池把逻辑跑通,不要一上来就追求异步,否则坑会非常多。
2.5 布尔逻辑在靶场练习中的一次应用:程序如何"判断真假"
布尔盲注是一个很典型的Web安全测试概念,但我不建议初学者一上来就写"爆破脚本"。我更喜欢把它当成一道逻辑题去理解:程序根据某个条件成立与否,从服务器返回的不同响应(页面长度变化、关键字出现、响应时间差异)中做出判断。这个过程本质上就是Python里的True和False。
def judge_true(condition): # 假设response_true是条件成立时的响应 # 假设response_false是条件不成立时的响应 if len(response_true) == len(response_false): return True return False用布尔逻辑去理解漏洞原理,比背payload有价值得多。你真正要掌握的,是如何从响应中提取"真"与"假"的区别,然后把这种区别变成程序可以判断的条件。这在整个Web安全攻防里是最核心的能力之一。当然,这个东西一定是在自己搭的靶场或明确授权的环境里练,别对着任何未授权的站点做测试,这是底线。
3. Web攻防里的Python核心动作:请求构造、响应判断与信息提取
到了这一部分,才算是真正进入Web安全攻防的Python实战。第三篇学下来,我发现最常用的动作不过三件事:把请求发出去、判断响应的差异、从响应里提取想要的信息。所有花哨的技术,最后都能拆成这三件事的排列组合。
3.1 requests库的使用逻辑:Session、Cookie和请求头
requests库在渗透测试里几乎是最常用的HTTP库。它用起来非常简单,但细节非常重要。最典型的例子是Session的用法。Session对象会在多个请求之间保持Cookie,模拟浏览器的会话状态。很多Web应用登录之后才能看到某些页面,如果你每次requests.get()都是新建连接,Cookie根本带不过去,拿到的永远是未登录页面。
import requests session = requests.Session() login_url = "http://example.com/login" login_data = {"username": "admin", "password": "password"} # 登录,保持会话 session.post(login_url, data=login_data) # 后续请求复用会话 dashboard = session.get("http://example.com/dashboard") print(dashboard.status_code)另一个重点是伪装请求头。有些站会拦截不带浏览器标识的请求,你至少把User-Agent和Referer设置得看起来像正常浏览器:
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0", "Referer": "http://example.com/login", } resp = session.get("http://example.com/dashboard", headers=headers, timeout=5)设置超时也是一个好习惯。timeout=5表示5秒没响应就报错,而不是无限等下去。写测试脚本时,无限等待是最让人抓狂的,一个卡住的请求能让整个任务失效。做批量请求时,我还会加try/except捕获requests.exceptions.RequestException,避免一个异常直接让脚本崩溃。
3.2 响应差异的判断思路:状态码、长度和关键词
Web安全测试很多时候是在做"比较"——正常请求和异常请求、有参数和无参数、这个条件和那个条件。requests库拿到响应之后,最基本的三组差异化数据是:
| 判断维度 | 数据来源 | 典型用法 |
|---|---|---|
| HTTP状态码 | resp.status_code | 判断页面是否存在、鉴权是否生效 |
| 响应内容长度 | len(resp.text)或resp.headers.get("Content-Length") | 判断页面是否不同 |
这三者经常组合使用。比如状态码是200,但页面长度明显变短,说明返回了不同的页面;状态码相同、关键词不同,说明可能有跳转或内容替换。做一个自动化脚本之前,一定先手工拿几个请求对比一下响应差异,再让程序去判断。没有经过人工确认的自动化判断,基本都会翻车。
3.3 从HTML里提取信息:正则、BeautifulSoup和XPath
信息收集阶段,经常需要从HTML页面里提取URL、邮箱、链接、表单等。最直接的方法是正则,但HTML结构复杂时正则容易写错,也更脆弱。我的习惯是:简单的提取用正则,结构化解析用BeautifulSoup。
from bs4 import BeautifulSoup import requests resp = requests.get("http://example.com") soup = BeautifulSoup(resp.text, "lxml") # 提取所有链接 links = [a.get("href") for a in soup.find_all("a", href=True)] # 去重并过滤空值 unique_links = list(set([link for link in links if link]))import re # 从页面中提取邮箱 email_pattern = r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}" emails = re.findall(email_pattern, resp.text)这里有个容易被忽视的小坑:soup.find_all("a", href=True)拿到的是每个<a>标签的href属性,但很多href是相对路径,比如/dashboard。如果要拼成完整URL,得用urllib.parse.urljoin处理。我当时因为没处理相对路径,导致后续请求全部404,排查了半天才发现是路径拼接的问题。
3.4 自动化脚本的骨架结构:一个可复用的扫描模板
学完这些基础操作后,我把它们拼成了一套自己的脚本骨架。这套骨架几乎可以套用到后续所有的测试脚本里,结构如下:
import requests import argparse import csv def banner(): print("Simple Scanner") def load_targets(file_path): with open(file_path, "r") as f: return [line.strip() for line in f if line.strip()] def scan(target, results): try: resp = requests.get(target, timeout=5, headers={"User-Agent": "Mozilla/5.0"}) if resp.status_code == 200: results.append((target, len(resp.text), resp.status_code)) except requests.exceptions.RequestException as e: results.append((target, "error", str(e))) def main(): parser = argparse.ArgumentParser() parser.add_argument("-f", "--file", required=True, help="target list file") parser.add_argument("-o", "--output", default="result.csv") args = parser.parse_args() targets = load_targets(args.file) results = [] for target in targets: scan(target, results) with open(args.output, "w", newline="") as f: writer = csv.writer(f) writer.writerow(["target", "length", "status"]) writer.writerows(results) print(f"done, results save to {args.output}") if __name__ == "__main__": main()这个骨架的意义不是说它能扫描出什么漏洞,而是让你拥有一个"输入目标列表,输出结果文件"的完整闭环。后续你想往里面加单点检测、加并发、加数据提取,都是在固定的位置做扩充。养成这种习惯之后,写测试脚本的效率会提升很多。
4. 在Kali Linux里把脚本当成测试副驾:从命令到自动化
Kali Linux在渗透测试学习里的地位不太需要我多说,热搜上有"Kali Linux渗透测试系列""kali linux高级渗透测试"这些词,说明很多人都卡在这一关。但我要说的是:Kali里现成的工具很多,但如果你想真正提升效率,必须学会用Python把它们组合起来。工具是零件,脚本是装配线。
4.1 为什么系统和脚本要配合着学
Kali自带几百个工具,但每个工具输出的格式不一样、参数不一样、保存结果的方式也不一样。比如你要批量探测一批IP的开放端口,单独用nmap可以,但如果要把几百个IP的扫描结果自动化汇总成表格,就需要写脚本去调用nmap、解析输出、写入文件。Python在这里的角色就是一个"协调员"。
一个最简单的配合方式,是用subprocess去调用系统命令:
import subprocess def run_nmap(target): result = subprocess.run( ["nmap", "-sV", "-Pn", target], capture_output=True, text=True, timeout=120 ) return result.stdout这样做的意义在于:你不需要用Python重新实现端口探测的底层逻辑,那是nmap已经做得很好的事情;你要做的,是让整个过程可以批量、可重复、结果可解析。组合工具的能力,比重新发明轮子重要得多。
4.2 把"手工命令"转化成"脚本流程"的思考方法
学习阶段,我经常做的一个练习是:先用Kali里的命令手工完成一个任务,再用Python脚本把同样的任务自动化。比如手工敲nmap -sV 192.168.1.1,然后自动把结果解析成结构化数据,后续拼出报告。这要求你养成一种"流程化"思维:先把任务拆成步骤,再把每个步骤变成代码。
有一个反面教材是:看到某个教程里有一个很酷的脚本,直接复制下来改了改就跑。结果工具换成别的环境就报错,也不知道怎么排查。正确的姿势是:先理解每个步骤在做什么,再动手写。手工命令的输出格式是怎样的、哪些字段是关键的、异常情况下输出什么样,这些都要先了然于心。
4.3 在Kali里管理Python环境:别把系统Python折腾坏了
Kali的Python环境比一般Linux发行版更敏感,因为很多系统工具依赖特定版本的库。我在Kali上吃过一次亏:直接在系统Python里pip install requests升级了urllib3,结果某个系统工具开始报SSL错误。从那以后,在Kali里我几乎只做两件事:
- 系统Python保持原样,不为自己的项目安装任何第三方库。
- 每个项目单独建虚拟环境(
python -m venv project_env),所有依赖都装在虚拟环境里。
Kali默认带了pyenv或者至少是python3-venv可用,实在没有就先装:
sudo apt install python3-venv python3 -m venv my_test_env source my_test_env/bin/activate pip install requests这样做还有一个好处:虚拟环境里的脚本想给系统命令传参,直接通过subprocess调用/usr/bin/nmap之类的绝对路径就行,完全不冲突。
4.4 学习阶段的靶场边界:什么叫"授权环境"
聊到渗透测试,必须花点篇幅说清楚"边界"这两个字。初学者最大的误区是"学了两招就想去真实站点上验证"。这个念头非常危险,而且违法。正规的学习路径,几乎都是在自己搭的靶场里练手,比如DVWA(Damn Vulnerable Web Application)、sqli-labs、vulhub这类教学环境。在自己虚拟机里的靶场上,你可以随便尝试任意测试方法,因为目标就是你自己的机器。
如果你想练习"一卡通系统"这类真实场景的测试,也有对应的合法路径:要么是在甲方明确授权的护网/渗透测试项目里,要么是用模拟这类系统的开源靶场项目。没有授权就不能测,这是行业铁律,没有例外。很多安全工程师在面试时都会被问到一个问题:给你一个目标,你能马上开测吗?标准答案是:先确认授权范围、签好保密协议、拿到书面授权,再动手。脚本技术只是能力的一部分,职业素养才是走得更远的基础。
5. 学习第三篇的踩坑记录:这些错误我反复犯过
每一篇笔记我都会专门留一块来记录自己踩过的坑,这一篇也不例外。这些坑大部分不是技术难点,但因为特别隐蔽,一旦踩进去往往要折腾半天才能爬出来。记录下来,至少能让你少走弯路。
5.1 布尔盲注练习脚本的三大教训
写布尔盲注练习脚本时,我几乎把能踩的坑都踩了一遍。最核心的三个问题:
- 超时控制缺失:一个请求卡住,整个脚本就停在原地。后来每个请求都加了
timeout参数,配合try/except处理超时。 - 线程频率过高:刚开始用线程池并发请求,频率太高,直接把本地靶场搞到假死。后来在每次请求之间加了短延时,既保证测试进度,又不影响靶场稳定性。
- 编码没有统一:响应文本的编码如果不对,中文页面解析出来就是一串乱码,匹配关键词永远失败。后来统一用
resp.apparent_encoding或者手动指定编码。
这些坑其实跟"是不是布尔盲注"没太大关系,而是所有Web探测类脚本的共性问题。我把它们归类为"测试脚本的通用工程问题",后来写任何自动化请求都先考虑这三件事。
5.2 matplotlib画图横坐标太密集
做数据分析时,我把一批测试结果用matplotlib画出来,结果横坐标标签密密麻麻叠在一起,根本没法看。这个坑听起来很小,但特别影响效率。
解决办法有三种路径:
import matplotlib.pyplot as plt # 方案1:旋转标签 plt.xticks(rotation=45) # 方案2:隔几个点显示一个标签 plt.figure(figsize=(12, 6)) plt.gca().set_xticks(range(0, len(data), 5)) # 方案3:使用MaxNLocator自动控制密度 from matplotlib.ticker import MaxNLocator plt.gca().xaxis.set_major_locator(MaxNLocator(nbins=10))做数据可视化时要时刻记得:图是给人看的,不是用来自嗨的。如果标签挤成一团,第一反应不是调字体大小,而是调显示密度。
5.3 字符串与字节串的"隐形坑"
这类问题在Web响应处理中反复出现。resp.text是字符串,resp.content是字节串,两者经常要混合使用。比如你要把resp.content保存成文件,没问题;你要判断某个关键词是否在响应里,用resp.text更顺手。但如果你拿到的是从外部读取的二进制文件内容,想在里面搜索一个字符串,就要先encode:
content_bytes = open("data.bin", "rb").read() if b"password" in content_bytes: print("found")这类错误最隐蔽的地方在于:有时Python会自动做隐式转换,有时又毫不留情地抛TypeError。保险起见,凡是涉及字符串匹配,先明确当前对象是str还是bytes,再决定要不要encode/decode。
5.4 协程与同步代码混用:性能反而更差
看到async/await很酷,就想着在扫描脚本里用协程。结果发现requests是同步阻塞的,在协程里直接调用requests.get()会把整个事件循环卡住。后来改用httpx的异步客户端,代码才真正跑起来。
import asyncio import httpx async def fetch_url(client, url): resp = await client.get(url) return resp.status_code async def main(): async with httpx.AsyncClient() as client: tasks = [fetch_url(client, f"http://example.com/page/{i}") for i in range(10)] results = await asyncio.gather(*tasks) print(results) asyncio.run(main())这个经历让我明白了一个道理:工具选型要匹配任务模型。同步场景用线程池,IO密集型场景用异步,CPU密集型场景用多进程。不要因为某一种技术很流行就无脑往上套。很多脚本写得慢,不是Python本身慢,而是模型用错了。
这篇笔记写到这里,其实已经超出"教程"的范畴了,更像是我自己学习过程的一次复盘。回头看第三篇,对我帮助最大的反而不是某个具体语法,而是那种"用工程思维去做安全测试"的意识——环境要干净、代码要结构化、请求要控制频率、数据要清洗、边界要拎清。这套意识建立起来之后,后面再学什么工具都会快很多。接下来我打算把这篇笔记里提到的脚本骨架整理成一个自己的工具库模板,方便后面做实验时直接抄作业。如果你也在学这个方向,我的建议是不要贪多,先把手里的脚本反复打磨到"拿出去能跑、坏了能修"的程度,再往前赶进度。