200 行 Python 实现代理 IP 质量评分系统(附完整代码)

简介: 本文介绍了一套用200行Python实现的代理IP四维量化评分系统(连通性30%、延迟25%、匿名度25%、稳定性20%),将IP质量转化为0–100分总分,自动排序剔除低质IP,告别主观判断,提升采集稳定性与效率。

很多人判断代理 IP 好不好用只看一件事——能不能连上。但能连上的 IP 里,相当一部分在请求头里回传了真实来源 IP,或者延迟高到拖垮批量采集。

IP 质量应该是连通性、响应延迟、来源可辨识度、稳定性四个维度加权的结果。

本文用 200 行 Python 把它量化成一个 0–100 的分数,一批 IP 跑一遍自动排序,低质量的直接沉底,比人工挨个试靠谱得多。

先看运行效果

把代理列表喂进脚本,跑完直接输出一份按总分降序的排名,死 IP 自动标“已剔除”:

http://12.34.56.78:8080      总分 92.3  延迟0.187s  匿名high        稳定1.0
http://23.45.67.89:8080      总分 78.5  延迟0.642s  匿名anonymous   稳定0.8
http://34.56.78.90:3128      总分 41.0  延迟2.310s  匿名transparent 稳定0.6
http://45.67.89.01:8888      不可用,已剔除

一眼就能看出该留谁、该扔谁。下面拆开讲每一维怎么测、分怎么算。

整体思路:四个维度,一条评分管线

flowchart TD
    A[代理 IP 列表] --> B[连通性 & 延迟测试]
    B -->|连不通| X[判 0 分 · 直接剔除]
    B -->|连通| C[匿名度检测]
    C --> D[稳定性测试]
    D --> E[四维归一化 · 加权合成总分]
    E --> F[按总分降序排名输出]

连通性是第一道闸——连不上的 IP 直接判 0,后面三项都不用测了,既省时间也避免对死 IP 发无意义的请求。

这套脚本解决什么问题,跑之前要准备什么

它解决的是“买了一堆代理 IP,肉眼分不清哪些能用”的问题——把主观感受换成一个可排序的分数。

前置条件很轻,Python 3.8+、装个 requests 就够,不依赖任何重型框架。核心要准备两样东西:一个待测的代理 IP 列表,一个能回显请求头和来源 IP 的接口。回显接口是整套评分的地基,因为判断来源是否泄露、延迟多少,都要靠一个“诚实告诉你它收到了什么”的服务端。

清单:

  • Python 3.8+ 与 requestspip install requests
  • 一批待测代理,格式统一为 http://ip:porthttp://user:pass@ip:port
  • 一个回显接口,返回 JSON 里带 origin(来源 IP)和 headers(请求头)。公开的 httpbin /get 就是这种结构;生产环境建议自建一个同结构接口,避免被限流
  • 一个稳定的连通性测试目标 URL,选一个大站首页即可

手头暂时没有可测的代理?很多服务商提供限时免费测试,可以先领一批喂进脚本做首轮筛选——先看分数再决定要不要采购,比盲买套餐再后悔省事。(例如极安代理官网标注新注册可领 8 小时免费测试,仅作为“哪里搞测试样本”的思路举例,评分逻辑对任何厂商的代理都通用。)

该量化哪四个维度

单看任何一个维度都会误判:延迟极低但请求头把真实来源 IP 抖出去了,采集照样触发风控;来源藏得干净但时通时断,批量任务也跑不完。所以拆成四项,权重按业务可调:

维度 权重 好的信号 差的信号
连通性 30% 能稳定返回 200 超时、连接被拒
响应延迟 25% 平均 <0.5 秒 >3 秒或抖动大
来源可辨识度 25% 头信息不回传真实来源 IP X-Forwarded-For 带真实来源 IP
稳定性 20% 连续请求成功率高 断断续续

延迟这一维没有绝对及格线,参照厂商公开口径来校准满分阈值更实际。比如有厂商官方口径给到平均响应<0.1 秒、默认 5M 带宽,那么把 0.5 秒设为满分、5 秒记零分的线性坡度,就是照着“优质代理该有的响应水平”定的,而不是拍脑袋。

一、连通性 + 响应延迟

这两项一次测完最省事:连着发几次请求,能返回 200 就算连通,每次耗时取平均就是延迟。采样多轮而不是只发一次,是为了避开单次网络抖动带来的误差。

import time
import requests

TEST_URL = "https://www.baidu.com"   # 连通性/延迟测试目标
TIMEOUT = 8
LATENCY_ROUNDS = 3                   # 延迟采样次数

def test_latency(proxy):
    """返回 (是否连通, 平均延迟秒);连不通返回 (False, None)。"""
    proxies = {
   "http": proxy, "https": proxy}
    costs = []
    for _ in range(LATENCY_ROUNDS):
        start = time.time()
        try:
            r = requests.get(TEST_URL, proxies=proxies, timeout=TIMEOUT)
            if r.status_code == 200:
                costs.append(time.time() - start)
        except Exception:
            continue
    if not costs:
        return False, None
    return True, sum(costs) / len(costs)

自测方法:拿一个已知能用的代理和一个瞎编的假地址各跑一次。真代理应返回 (True, 某个小数),假地址应返回 (False, None)。两种都对,说明连通判定和异常捕获都没漏。

二、匿名度:怎么判断一个代理是不是真高匿

关键是拿代理去请求回显接口,看它回传的头信息里有没有把真实来源 IP 抖出去。代理按来源可辨识度分三档,判定完全靠两个头字段——X-Forwarded-ForVia

检测项 高匿 (high) 普通匿名 (anonymous) 透明 (transparent)
返回的 origin 代理 IP 代理 IP 含真实来源 IP
X-Forwarded-For 代理 IP 真实来源 IP
Via

检测逻辑:先在不走代理的情况下拿到本机真实来源 IP,再让代理去请求回显接口,比对返回里有没有这个真实 IP,以及 ViaX-Forwarded-For 是否存在。

ECHO_URL = "https://httpbin.org/get"   # 回显请求头与来源 IP

def get_real_ip():
    try:
        r = requests.get(ECHO_URL, timeout=TIMEOUT)
        return r.json().get("origin", "").split(",")[0].strip()
    except Exception:
        return ""

def test_anonymity(proxy, real_ip):
    """返回 high / anonymous / transparent / unknown。"""
    proxies = {
   "http": proxy, "https": proxy}
    try:
        r = requests.get(ECHO_URL, proxies=proxies, timeout=TIMEOUT)
        data = r.json()
        headers = {
   k.lower(): v for k, v in data.get("headers", {
   }).items()}
        origin = data.get("origin", "")
    except Exception:
        return "unknown"
    xff = headers.get("x-forwarded-for", "")
    if real_ip and (real_ip in origin or real_ip in xff):
        return "transparent"
    if headers.get("via") or xff:
        return "anonymous"
    return "high"

自测方法:先单独跑 get_real_ip() 确认能拿到本机公网 IP。如果拿到的是空值或内网地址,说明本机自己挂了系统代理,要先关掉再取——否则后面所有匿名度判定都会偏。这是最容易踩的坑,务必先确认。

三、稳定性:连续打才暴露得出来

稳定性就是连续发 N 次请求,数成功几次,成功率就是这一维的原始分。一次成功不代表稳定,代理池里最坑的是那种“测的时候好好的、批量跑到一半就掉”的 IP,只有连续打才现形。

STABILITY_ROUNDS = 5   # 稳定性连续请求次数

def test_stability(proxy):
    """连续请求,返回成功率 0-1。"""
    proxies = {
   "http": proxy, "https": proxy}
    ok = 0
    for _ in range(STABILITY_ROUNDS):
        try:
            r = requests.get(TEST_URL, proxies=proxies, timeout=TIMEOUT)
            if r.status_code == 200:
                ok += 1
        except Exception:
            pass
    return ok / STABILITY_ROUNDS

稳定性本质是可用率的短周期采样。优质代理的连续成功率应该贴近满分,脚本里跑出 0.6、0.8 这种成绩的,基本可以判定是二手回收池而不是纯净资源。轮数越多越准,代价是耗时——日常筛选 5 次够用,进重点池前调到 10 次以上复筛

自测方法:对同一个代理连跑两遍 test_stability,成绩应在小范围内浮动;如果两次差距很大(比如 1.0 和 0.2),说明这个 IP 本身就不稳,正好是要被扣分剔除的对象。

四、四维合成总分

把四个原始分各自归一到 0–100,再按权重加权求和,就是最终总分。归一化是为了让延迟的“秒”和稳定性的“成功率”能放在同一把尺子上比:延迟按 0.5 秒满分、5 秒零分线性衰减;匿名度按档位查表给分;连通和稳定直接乘 100。

WEIGHTS = {
   "conn": 0.30, "latency": 0.25, "anon": 0.25, "stability": 0.20}
ANON_SCORE = {
   "high": 100, "anonymous": 60, "transparent": 10, "unknown": 0}

def score_proxy(proxy, real_ip):
    alive, avg_latency = test_latency(proxy)
    if not alive:
        return {
   "proxy": proxy, "alive": False, "total": 0}
    latency_score = max(0, min(100, (5 - avg_latency) / 4.5 * 100))
    anon = test_anonymity(proxy, real_ip)
    stability = test_stability(proxy)
    total = (100 * WEIGHTS["conn"]
             + latency_score * WEIGHTS["latency"]
             + ANON_SCORE[anon] * WEIGHTS["anon"]
             + stability * 100 * WEIGHTS["stability"])
    return {
   "proxy": proxy, "alive": True, "latency": round(avg_latency, 3),
            "anon": anon, "stability": round(stability, 2), "total": round(total, 1)}

权重不是死的,跟着业务改才对:反爬严的目标站把 anon 调高,高频持续采集把 stability 调高,四个权重之和保持为 1 即可。

自测方法:手动构造一个全满分的假数据代入公式,总分应等于 100;把 anonhigh 换成 transparent,总分应正好掉 (100-10)×0.25 = 22.5 分,对得上说明加权没写错。

完整代码

上面的函数拼起来,加上多线程批量调度就能直接跑。用线程池并发是因为检测是纯 IO 等待,串行测几百个 IP 会慢到没法用。

import time
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed

# ---------- 配置 ----------
ECHO_URL = "https://httpbin.org/get"     # 回显请求头与来源 IP
TEST_URL = "https://www.baidu.com"       # 连通性/延迟测试目标
TIMEOUT = 8
LATENCY_ROUNDS = 3
STABILITY_ROUNDS = 5
WEIGHTS = {
   "conn": 0.30, "latency": 0.25, "anon": 0.25, "stability": 0.20}
ANON_SCORE = {
   "high": 100, "anonymous": 60, "transparent": 10, "unknown": 0}


def get_real_ip():
    try:
        r = requests.get(ECHO_URL, timeout=TIMEOUT)
        return r.json().get("origin", "").split(",")[0].strip()
    except Exception:
        return ""


def test_latency(proxy):
    proxies = {
   "http": proxy, "https": proxy}
    costs = []
    for _ in range(LATENCY_ROUNDS):
        start = time.time()
        try:
            r = requests.get(TEST_URL, proxies=proxies, timeout=TIMEOUT)
            if r.status_code == 200:
                costs.append(time.time() - start)
        except Exception:
            continue
    if not costs:
        return False, None
    return True, sum(costs) / len(costs)


def test_anonymity(proxy, real_ip):
    proxies = {
   "http": proxy, "https": proxy}
    try:
        r = requests.get(ECHO_URL, proxies=proxies, timeout=TIMEOUT)
        data = r.json()
        headers = {
   k.lower(): v for k, v in data.get("headers", {
   }).items()}
        origin = data.get("origin", "")
    except Exception:
        return "unknown"
    xff = headers.get("x-forwarded-for", "")
    if real_ip and (real_ip in origin or real_ip in xff):
        return "transparent"
    if headers.get("via") or xff:
        return "anonymous"
    return "high"


def test_stability(proxy):
    proxies = {
   "http": proxy, "https": proxy}
    ok = 0
    for _ in range(STABILITY_ROUNDS):
        try:
            r = requests.get(TEST_URL, proxies=proxies, timeout=TIMEOUT)
            if r.status_code == 200:
                ok += 1
        except Exception:
            pass
    return ok / STABILITY_ROUNDS


def score_proxy(proxy, real_ip):
    alive, avg_latency = test_latency(proxy)
    if not alive:
        return {
   "proxy": proxy, "alive": False, "total": 0}
    latency_score = max(0, min(100, (5 - avg_latency) / 4.5 * 100))
    anon = test_anonymity(proxy, real_ip)
    stability = test_stability(proxy)
    total = (100 * WEIGHTS["conn"]
             + latency_score * WEIGHTS["latency"]
             + ANON_SCORE[anon] * WEIGHTS["anon"]
             + stability * 100 * WEIGHTS["stability"])
    return {
   "proxy": proxy, "alive": True, "latency": round(avg_latency, 3),
            "anon": anon, "stability": round(stability, 2), "total": round(total, 1)}


def batch_score(proxy_list, max_workers=20):
    real_ip = get_real_ip()
    results = []
    with ThreadPoolExecutor(max_workers=max_workers) as pool:
        futures = {
   pool.submit(score_proxy, p, real_ip): p for p in proxy_list}
        for f in as_completed(futures):
            results.append(f.result())
    results.sort(key=lambda x: x["total"], reverse=True)
    return results


if __name__ == "__main__":
    proxies = [
        "http://127.0.0.1:8080",
        "http://127.0.0.1:8081",
    ]
    for item in batch_score(proxies):
        if item["alive"]:
            print(f"{item['proxy']:<28} 总分{item['total']:>5}  "
                  f"延迟{item['latency']}s  匿名{item['anon']}  稳定{item['stability']}")
        else:
            print(f"{item['proxy']:<28} 不可用,已剔除")

proxies 列表换成自己的真实代理,运行后就能看到一份按总分降序的排名,死 IP 直接标“已剔除”。

跑的时候常踩哪些坑

绝大多数报错不是脚本 bug,而是代理本身或环境的问题。对着下表排查,比重读代码快:

报错信号 原因 修复
ProxyError / Max retries exceeded 代理已失效或格式写错 检查 http:// 前缀和端口,剔除死 IP
SSLError 代理不支持 HTTPS 隧道 换成 http 测试目标,或只测 http 协议
请求全部超时 TIMEOUT 太短或并发太高 调大 TIMEOUT、降低 max_workers
匿名度全返回 unknown 回显接口被限流或不可达 换回显服务,优先自建同结构接口
real_ip 取到内网地址 本机自身走了系统代理 关闭系统代理再取真实来源 IP

其中最容易被忽略的是最后一条:如果 get_real_ip() 拿到的是本机内网 IP,后面所有代理的匿名度判定都会失真,务必先确认真实来源 IP 取得干净再批量跑

还能怎么优化

基础版够日常筛选用,往生产方向走还有几个可选升级项,按需取用:

  • 权重动态化:把 WEIGHTS 做成按目标站配置,反爬严的站调高来源可辨识度权重,长任务调高稳定性权重,一套脚本适配多种采集场景。
  • 加协议维度:把 SOCKS5 代理纳入检测,proxies 里写 socks5://ip:port,requests 需额外装 requests[socks]
  • 落地成代理池:把分数写进 Redis 并定时复评,低于阈值的自动下线,配合业务侧按分数取用,替代一次性跑分。
  • 并发调优max_workers 不是越大越好,本机出口带宽和回显接口的限流才是天花板,从 20 起步按实测调。

结语

这套评分的价值不在代码多复杂,而在于把“这个代理好不好”从一句感觉,变成四个可复现、可排序、可回溯的数字,买之前先跑一遍分,比事后返工划算得多。代码可以直接复制运行,也欢迎在评论区贴出你的权重调法和踩坑记录。

FAQ

Q1:连通性都过不了的代理,还有必要测后面三项吗?

没必要。脚本里 test_latency 返回 False 就直接判 0 分、跳过后续检测,既省时间也避免对死 IP 做无意义的请求。这也是把连通性放第一道闸的原因。

Q2:延迟采样次数和稳定性轮数能调小加速吗?

能,但会牺牲准确度。采样越少越容易被单次抖动带偏。日常筛选把 LATENCY_ROUNDS 设 3、STABILITY_ROUNDS 设 5 是速度和精度的平衡点;进重点池前建议把稳定性轮数调到 10 以上复筛。

Q3:httpbin 换成别的回显接口,代码要改吗?

只要新接口返回的 JSON 里同样有 originheaders 两个字段,改一下 ECHO_URL 常量即可,逻辑不用动。生产环境强烈建议自建回显接口,公共服务容易被限流,一旦返回异常匿名度就会全判 unknown

Q4:脚本判出来的“透明代理”是不是完全不能用?

看场景。透明代理会在请求头回传真实来源 IP,做需要来源不外泄的公开数据采集时基本要剔除;但如果只是内网加速、对来源没要求的场景,透明代理照样能用。分数低不等于绝对不可用,权重和阈值要贴着自己的业务定。

Q5:并发数 max_workers 设多大合适?

没有统一答案,取决于本机出口带宽和回显接口的限流阈值,不是越大越快。建议从 20 起步,观察超时率,超时明显上升就往下调。盲目开到几百反而会因为大量超时把好 IP 误判成不可用。

Q6:这套脚本测公网代理,测私密独享代理准吗?

一样准,四个维度对任何 HTTP/HTTPS 代理都通用。区别只在于独享代理通常稳定性和延迟分更高、来源更干净,跑出来的分数普遍偏高,这时可以把及格线相应上调,用同一把尺子做更细的分层。

相关文章
|
1天前
|
人工智能 安全 测试技术
|
3天前
|
云安全 人工智能 安全
阿里云 Agentic SOC 位居 IDC MarketScape安全运营智能体2026领导者类别
以 Agentic AI 重构安全运营闭环,阿里云云安全在产品能力与市场份额
1153 3
|
3天前
|
数据采集 机器学习/深度学习 人工智能
田间杂草定位与检测4200张YOLO智慧农业数据集分享
本数据集含4200张真实农田图像,YOLO格式,单类别(杂草)高质量标注,覆盖多作物、多光照、多生长阶段等复杂场景,专为智慧农业杂草检测与智能除草设备研发设计,支持YOLOv5/v8/v10等主流模型训练。
346 93
|
4天前
|
缓存 UED 开发者
Codex109天重置23次,明天还要再送一次
Codex近109天完成23次额度重置,7月14日将迎来第24次。Tibo高频响应用户反馈:优化GPT-5.6高消耗问题、补发失效福利、调整重置时间——形成“反馈→回应→修复→补偿”正向闭环,彰显以用户为中心的产品哲学。(239字)
560 11
|
7天前
|
存储 人工智能 JSON
Qwen 本地部署搭配 ComfyUI 生成 AI 漫剧完整实操指南(小白零基础可落地,零成本无限生成+角色一致性天花板)
2026全网最优本地漫剧流水线:零成本、离线运行、角色统一、低配(8G显卡)可跑。融合Qwen本地大模型+ComfyUI双引擎,实现剧本生成→分镜绘图→动态成片全自动,隐私安全、无审核限流,新手30分钟上手,日更无忧。(239字)
|
7天前
|
人工智能 缓存 JSON
刚刚 GPT-5.6 发布,吊打 Claude 5 和 Grok 4.5?一手实测来啦!
GPT-5.6 刚发布,跑分号称超越 Fable 5,真的假的?附一手实测,让 3 个最强 AI 编程模型 GPT-5.6 Sol、Claude Fable 5、Grok 4.5 在 Cursor 里同时一把梭开发网页游戏,看看最新模型到底谁更能打。
361 0

热门文章

最新文章