下行消耗脚本
73
0
0
简单记录一下
初始环境
python3.11 -m venv venv
./venv/bin/pip install -i http://mirrors.aliyun.com/pypi/simple --trusted-host curl_cffi
./venv/bin/python ./speed.py
speed.py 代码如下
import os
import json
import time
import random
import string
import threading
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
from curl_cffi import requests
from rich.progress import (
Progress,
BarColumn,
DownloadColumn,
TransferSpeedColumn,
TimeRemainingColumn,
)
from rich.console import Console
from rich.live import Live
# ==================== 测试配置 ====================
URL = "https://www.lava3dp.com/wp-content/uploads/stl_files_uploads/ul.stl"
MAX_WORKERS = 10 # 同时保持的并发线程数(根据您的带宽调整)
FEISHU_WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxx"
SYNC_INTERVAL_SECONDS = 3600 # 飞书同步间隔:1小时
STATS_FILE = os.path.join(os.path.dirname(os.path.abspath('.')), "speedtest_stats.json")
# ==================================================
console = Console()
def load_stats():
"""从本地文件加载累计流量统计"""
if os.path.exists(STATS_FILE):
try:
with open(STATS_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
pass
return {
"total_bytes": 0,
"total_tasks": 0,
"start_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"last_sync_time": None,
}
def save_stats(stats):
"""将累计流量统计写入本地文件(调用者需持有锁)"""
return
try:
with open(STATS_FILE, "w", encoding="utf-8") as f:
json.dump(stats, f, ensure_ascii=False, indent=2)
except IOError as e:
console.print(f"[red]写入统计文件失败: {e}[/red]")
def accumulate_bytes(bytes_count, stats, stats_lock):
"""累加下载字节数并持久化到本地文件"""
with stats_lock:
stats["total_bytes"] += bytes_count
stats["total_tasks"] += 1
save_stats(stats)
def send_feishu_notification(stats):
"""发送飞书机器人通知,汇报累计流量"""
total_mb = stats["total_bytes"] / (1024 * 1024)
total_gb = total_mb / 1024
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if total_gb >= 1:
traffic_display = f"**{total_gb:.2f} GB**"
else:
traffic_display = f"**{total_mb:.2f} MB**"
card = {
"msg_type": "interactive",
"card": {
"header": {
"title": {
"tag": "plain_text",
"content": "📊 带宽测试流量同步报告",
},
"template": "blue",
},
"elements": [
{
"tag": "div",
"fields": [
{
"is_short": True,
"text": {
"tag": "lark_md",
"content": f"**累计流量:**\n{traffic_display}",
},
},
{
"is_short": True,
"text": {
"tag": "lark_md",
"content": f"**累计任务数:**\n{stats['total_tasks']}",
},
},
{
"is_short": True,
"text": {
"tag": "lark_md",
"content": f"**测试开始时间:**\n{stats['start_time']}",
},
},
{
"is_short": True,
"text": {
"tag": "lark_md",
"content": f"**本次同步时间:**\n{now_str}",
},
},
],
},
{"tag": "hr"},
{
"tag": "note",
"elements": [
{
"tag": "plain_text",
"content": f"数据文件: {STATS_FILE}",
}
],
},
],
},
}
try:
resp = requests.post(
FEISHU_WEBHOOK_URL,
json=card,
impersonate="chrome",
timeout=10,
)
if resp.status_code == 200:
result = resp.json()
if result.get("code") == 0:
console.print(f"[green][飞书同步] 成功 | 累计流量: {traffic_display} | 时间: {now_str}[/green]")
else:
console.print(f"[red][飞书同步] 接口返回错误: {result}[/red]")
else:
console.print(f"[red][飞书同步] HTTP {resp.status_code}[/red]")
except Exception as e:
console.print(f"[red][飞书同步] 请求失败: {e}[/red]")
def feishu_sync_daemon(stats, stats_lock, stop_event):
"""后台守护线程:每小时同步一次累计流量到飞书机器人"""
while not stop_event.is_set():
if stop_event.wait(timeout=SYNC_INTERVAL_SECONDS):
break
with stats_lock:
stats_copy = dict(stats)
stats["last_sync_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
save_stats(stats)
send_feishu_notification(stats_copy)
def generate_random_params():
"""生成 1 到 5 个随机防缓存参数"""
params = {}
for _ in range(random.randint(1, 5)):
key = ''.join(random.choices(string.ascii_lowercase, k=5))
val = ''.join(random.choices(string.digits, k=5))
params[key] = val
return params
def generate_random_ip():
"""生成一个随机的公网 IPv4 地址(排除私有/保留段)"""
while True:
ip = f"{random.randint(1, 223)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(1, 254)}"
first = int(ip.split('.')[0])
second = int(ip.split('.')[1])
# 排除私有地址段:10.x.x.x / 172.16-31.x.x / 192.168.x.x / 127.x.x.x
if first == 10 or first == 127:
continue
if first == 172 and 16 <= second <= 31:
continue
if first == 192 and second == 168:
continue
return ip
def generate_fake_ip_headers():
"""
生成伪装真实 IP 的请求头(与 wp-config.php 中解析逻辑对应)
优先级顺序:CF-Connecting-IP > X-Forwarded-For > X-Real-IP > X-Forwarded > Client-IP
"""
real_ip = generate_random_ip()
headers = {
"CF-Connecting-IP": real_ip,
"X-Forwarded-For": f"{real_ip}, {generate_random_ip()}",
"X-Real-IP": real_ip,
"X-Forwarded": real_ip,
"Client-IP": real_ip,
}
return headers
def download_worker(task_id, progress, task_lock, stats, stats_lock):
"""单个下载任务:请求文件、流式读取、丢弃数据,并实时更新进度条"""
params = generate_random_params()
headers = generate_fake_ip_headers()
downloaded_bytes = 0
progress_task_id = progress.add_task(
f"[cyan]任务 {task_id}",
total=None,
start=False,
)
try:
response = requests.post(
URL,
params=params,
# headers=headers,
impersonate="chrome",
stream=True,
timeout=15,
)
if response.status_code == 200:
total_size = int(response.headers.get('content-length', 0))
progress.start_task(progress_task_id)
progress.update(progress_task_id, total=total_size if total_size > 0 else None)
for chunk in response.iter_content(chunk_size=1024 * 64):
if chunk:
downloaded_bytes += len(chunk)
progress.update(progress_task_id, completed=downloaded_bytes)
if downloaded_bytes > 0:
progress.update(
progress_task_id,
description=f"[green]任务 {task_id} ✓",
completed=downloaded_bytes,
total=downloaded_bytes,
)
# 累加到本地持久化统计
accumulate_bytes(downloaded_bytes, stats, stats_lock)
return downloaded_bytes
else:
progress.update(
progress_task_id,
description=f"[red]任务 {task_id} ✗ (HTTP {response.status_code})",
)
except Exception as e:
progress.update(
progress_task_id,
description=f"[red]任务 {task_id} ✗ 错误",
)
finally:
time.sleep(0.5)
with task_lock:
try:
progress.remove_task(progress_task_id)
except Exception:
pass
return 0
def start_infinite_test():
stats = load_stats()
stats_lock = threading.Lock()
# 启动飞书定时同步守护线程
stop_event = threading.Event()
sync_thread = threading.Thread(
target=feishu_sync_daemon,
args=(stats, stats_lock, stop_event),
daemon=True,
)
sync_thread.start()
prev_total_mb = stats["total_bytes"] / (1024 * 1024)
console.print("=" * 60, style="bold blue")
console.print("开始【无限循环版】多线程带宽稳定性测试...", style="bold green")
console.print(f"当前并发线程数: [yellow]{MAX_WORKERS}[/yellow] | 底层伪装: [yellow]Chrome 浏览器[/yellow]")
console.print(f"历史累计流量: [cyan]{prev_total_mb:.2f} MB[/cyan] | 飞书同步间隔: [cyan]1小时[/cyan]")
console.print(f"统计文件: [dim]{STATS_FILE}[/dim]")
console.print("数据直接从内存释放(不写磁盘)。按下 [red]Ctrl+C[/red] 可随时停止测试。")
console.print("=" * 60)
console.print()
global_start_time = time.time()
session_downloaded_bytes = 0
task_counter = 0
task_lock = threading.Lock()
progress = Progress(
"[progress.description]{task.description}",
BarColumn(bar_width=30),
"[progress.percentage]{task.percentage:>3.0f}%",
DownloadColumn(),
TransferSpeedColumn(),
TimeRemainingColumn(),
console=console,
transient=False,
refresh_per_second=10,
)
with Live(progress, console=console, refresh_per_second=10):
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = {}
for i in range(1, MAX_WORKERS + 1):
future = executor.submit(download_worker, i, progress, task_lock, stats, stats_lock)
futures[future] = i
task_counter = MAX_WORKERS
try:
while futures:
for future in as_completed(futures):
bytes_downloaded = future.result()
session_downloaded_bytes += bytes_downloaded
futures.pop(future)
task_counter += 1
new_future = executor.submit(download_worker, task_counter, progress, task_lock, stats, stats_lock)
futures[new_future] = task_counter
break
except KeyboardInterrupt:
console.print("\n\n[yellow]正在接收终止信号,请稍候(等待当前进行中的线程安全退出)...[/yellow]")
for f in futures:
f.cancel()
# 停止飞书同步线程
stop_event.set()
sync_thread.join(timeout=3)
# 退出前触发一次飞书同步,确保最新数据及时上报
with stats_lock:
stats_copy = dict(stats)
send_feishu_notification(stats_copy)
total_duration = time.time() - global_start_time
session_mb = session_downloaded_bytes / (1024 * 1024)
total_mb_all = stats["total_bytes"] / (1024 * 1024)
avg_speed = session_mb / total_duration if total_duration > 0 else 0
console.print()
console.print("=" * 60, style="bold blue")
console.print("[bold]最终测试报告[/bold]", style="bold green")
console.print("=" * 60, style="bold blue")
console.print(f"本次测试时长: [cyan]{total_duration:.2f}[/cyan] 秒")
console.print(f"本次下载流量: [cyan]{session_mb:.2f}[/cyan] MB")
console.print(f"本次平均吞吐: [cyan]{avg_speed:.2f}[/cyan] MB/s (约 [cyan]{avg_speed*8:.2f}[/cyan] Mbps)")
console.print(f"本次完成任务: [cyan]{stats['total_tasks']}[/cyan] 次")
console.print(f"历史累计流量: [cyan]{total_mb_all:.2f}[/cyan] MB")
console.print("=" * 60, style="bold blue")
if __name__ == "__main__":
start_infinite_test()