Appearance
网站电信用户快、联通用户慢?用 IP 归属地按运营商给访客分流(Python 实现)
一台服务器放在电信机房,电信用户打开秒开,联通用户转圈半分钟,移动用户直接看加载失败。这不是玄学,是跨运营商互联的老毛病:国内几大运营商之间的互联带宽就那么大,高峰期一挤,跨网流量就走得又慢又绕。BGP 多线机房一个月多花几千块,CDN 按流量计费也不便宜——小站长的网站、下载站、资源站,其实有更省钱的玩法。
思路很简单:你有两条线路(比如电信一台、联通一台,或者同一台服务器的电信+联通双 IP),在入口按访客的运营商 302 到对应线路的域名。访客是电信的就去电信线路,联通的去联通线路,谁都不绕路。关键就一步:怎么知道访客是哪个运营商的——用 IP 归属地接口查 isp 字段。
一、为什么跨运营商访问会慢
国内网络分电信、联通、移动三大张网,网间靠少量互联节点互通。电信用户访问电信机房的服务器,全程在电信网内跑,快;联通用户访问同一台服务器,数据要先从联通网绕到互联节点再进电信网,赶上互联带宽拥堵,延迟和丢包直接起飞。游戏里叫"跨网",下载场景里就是"龟速"。
解决跨网慢的常规路子有三条:BGP 多线(贵)、CDN 加速(按量付费,小流量站不划算)、多线路服务器手动分流(累)。这里说的第四种——按运营商自动分流,适合自己有两台或双线服务器的站长:入口统一,后端按访客运营商把请求引到对应线路,成本就是免费 IP 接口的几次调用。
二、方案设计
整体长这样:
- 两条线路各一个域名,分别解析到对应线路的服务器,比如
tel.example.com走电信线、uni.example.com走联通线。 - 主域名
www.example.com指向分流入口(一台轻量服务器或直接复用某条线路)。 - 入口收到请求,拿访客 IP 调
https://ip9.com.cn/get?ip=<ip>查归属地,读返回的isp字段判断运营商(免费版就返回这个字段)。 - 按运营商 302 跳到对应线路域名,路径和参数原样带过去。
- 查询结果在内存里缓存 6 小时——同一 IP 一天顶多查几次,免费版 60 次/分钟/IP 的额度根本用不完。
两个容易翻车的点提前说:第一,入口前面如果还有 Nginx 或 CDN,代码里直接取 TCP 连接 IP 会拿到代理的地址,得先让 Nginx 把真实 IP 放进 X-Real-IP 头;第二,接口超时或查不到时必须放行到默认线路,不能让访客因为查归属地失败而打不开网站。
三、Python 实现
只用了标准库,python3 直接跑,不装任何依赖。起一个 8080 端口的 HTTP 服务当分流入口:
python
import json
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.request import Request, urlopen
# ===== 配置区 =====
# 各线路入口域名:分别解析到电信/联通/移动线路的服务器
LINES = {
"telecom": "tel.example.com", # 电信线路
"unicom": "uni.example.com", # 联通线路
"mobile": "mob.example.com", # 移动线路
}
DEFAULT_LINE = "telecom" # 查不到运营商时兜底走哪条
CACHE_TTL = 6 * 3600 # 归属地缓存 6 小时
# =================
_cache = {} # ip -> (isp, 过期时间戳),单进程内存缓存;多进程部署换 Redis
def get_isp(ip):
"""查 IP 归属地,返回运营商字符串;失败返回空串"""
now = time.time()
hit = _cache.get(ip)
if hit and hit[1] > now:
return hit[0]
isp = ""
try:
# 不带 User-Agent 会被接口拒(403),务必加上
req = Request("https://ip9.com.cn/get?ip=" + ip,
headers={"User-Agent": "Mozilla/5.0 (compatible; IP9Demo/1.0)"})
resp = urlopen(req, timeout=5)
data = json.loads(resp.read().decode("utf-8"))
if data.get("ret") == 200:
isp = (data.get("data") or {}).get("isp", "") or ""
except Exception:
pass # 超时/断网都走默认线路,不阻塞访问
_cache[ip] = (isp, now + CACHE_TTL)
return isp
def pick_line(isp):
"""isp 字符串里带的关键字决定线路;实测返回形如 '中国电信/DNS'"""
if "电信" in isp:
return "telecom"
if "联通" in isp or "网通" in isp:
return "unicom"
if "移动" in isp or "铁通" in isp:
return "mobile"
return DEFAULT_LINE
class LineHandler(BaseHTTPRequestHandler):
def do_GET(self):
# 前面有 Nginx/CDN 时用 X-Real-IP,直连部署就用 TCP 连接的 IP
client_ip = self.headers.get("X-Real-IP") or self.client_address[0]
isp = get_isp(client_ip)
line = pick_line(isp)
target = "https://%s%s" % (LINES[line], self.path) # path 自带查询参数
self.send_response(302)
self.send_header("Location", target)
self.send_header("X-Line", line) # 调试用,方便 curl 看结果
self.send_header("Cache-Control", "no-store") # 不让浏览器/代理缓存跳转
self.end_headers()
def log_message(self, fmt, *args):
pass # 安静点,想看日志自己放开
if __name__ == "__main__":
HTTPServer(("0.0.0.0", 8080), LineHandler).serve_forever()跑起来之后,拿真实 IP 验证一下。电信 DNS 202.96.128.86 查到 isp=中国电信/DNS,跳 tel.example.com;联通 DNS 119.6.6.6 查到 isp=中国联通/DNS,跳 uni.example.com:
bash
curl -sI -H "X-Real-IP: 202.96.128.86" http://127.0.0.1:8080/soft/x.zip | grep -i -E "location|x-line"
# Location: https://tel.example.com/soft/x.zip
curl -sI -H "X-Real-IP: 119.6.6.6" http://127.0.0.1:8080/soft/x.zip | grep -i -E "location|x-line"
# Location: https://uni.example.com/soft/x.zip部署时入口服务器前面挂 Nginx,加一行 proxy_set_header X-Real-IP $remote_addr; 再反代到 8080 就行。Nginx 本身会处理并发和 TLS,这个 Python 进程只干分流一件事,单进程撑小站的量没问题。
四、落地注意事项
- 只有能跳转的入口适合 302。下载页面、软件详情页随便跳;API 接口、支付回调、登录接口不能跳——这些场景应该直接在业务里按归属地选线路,或者干脆用 DNS 分线解析。
- 线路域名要备案、要证书。
tel.example.com、uni.example.com都得有 ICP 备案和 HTTPS 证书,不然跳过去浏览器直接拦。域名多的话上泛域名证书。 - 别把误判做成死循环。某条线路的服务器挂了,访客跳过去打不开,他手动切回主域名,又被分流到同一条死线路——所以入口要保留一个"手动选线路"的入口(比如 URL 参数
?line=unicom优先于自动判断),也方便你自己测试。 - 缓存时间别太短。家庭宽带动态 IP 的运营商不会频繁变,6 小时合理;缓存太短会拿免费接口的 60 次/分钟额度去硬扛突发流量。多进程部署时内存缓存各存各的,把
_cache换成 Redis 即可。 - 接口失败必须放行。代码里已经做了:查不到就返回空串走默认线路。查归属地是优化手段,不是访问前置条件——这一条比分流逻辑本身更重要。
- 什么时候该换方案:访客量上来之后,正确做法是上 DNS 分线解析或直接套 CDN,让专业的分发网络去处理;这个 Python 网关适合自建双线的小型下载站、资源站、个人站,零成本解决"某一网用户特别慢"的投诉。
总结
跨运营商访问慢的根子是网间互联,中小站长不一定要买 BGP 多线。两条线路 + 一个按 isp 字段分流的入口,就能让电信、联通、移动的用户都走自己网内的线路。核心代码不到 50 行,免费接口就能撑起来。
归属地接口用 https://ip9.com.cn/get?ip=<ip>:免费版返回国家/省市/运营商/经纬度,IPv4、IPv6 都支持,60 次/分钟对分流场景足够;字段和额度说明见官网 https://www.ip9.com.cn 。先把两条线路的域名备好案,再把这 50 行挂上去,联通用户的投诉基本就消停了。