用 Python + 免费代理池优雅地爬网页:从拿代理到跑请求的完整流程
写爬虫写久了,大家大概都遇到过这一幕:
开开心心用 requests 写了几十行代码,第一次跑还挺顺利,过一会儿直接给你来个 403、429,甚至干脆连不上。原因很简单——IP 暴露太直接。
解决思路之一,就是把请求从一堆 HTTP/HTTPS 代理 走一遍。本文用一个实际可用的公开代理池 NexaHub Proxy 做例子,演示:
- 用它提供的 HTTP API 拉一批代理(只要
http/http_tunnel/https标签) - 在 Python 里把这些代理接到
requests上 - 做一个简单的轮询 + 重试逻辑,让爬虫“好歹能跑一阵”
整篇文章都是 Markdown 格式,直接贴到 优快云 就能用。
1. 准备环境
我下面用的是 Python 3.10+,依赖只有一个:
pip install requests
如果你平时就写 Python 爬虫,基本上环境已经有了。
2. 先认识一下代理池的 API
这个代理池有两个域名:
- 数据写入 & 底层查询 API:
https://apiproxy.nexahub.one - 门户和可视化:
https://proxy.nexahub.one
我们要用的是前者的查询接口 /proxies/search,它是一个 POST JSON 的接口:
- 地址:
https://apiproxy.nexahub.one/proxies/search - 请求体:一个 JSON,对应
SearchBody - 返回:
{"total": N, "items": [ ... ]},每个item都是一个代理节点的完整信息
这里面最关键的字段:
ip:代理 IPport:代理端口tags:标签,比如http/http_tunnel/https/socks5等validation_status:active或stale,结合recent_minutes判断是否算“活跃”- 一堆延迟和时间字段:
success_count、latency_ms_avg、last_success_at等
我们只关心三类标签:
httphttp_tunnelhttps
这三种都可以当作 HTTP(S) 代理来用。
3. 用 API 拉一批 HTTP 代理
先写一段小脚本,从 API 里拿一批“质量还行”的代理,筛选条件定得保守一点:
tags_any = ["http", "http_tunnel", "https"]validation_status = "active":只要最近验证过、状态为活跃的min_success = 3:至少成功过几次max_latency_avg = 2000:平均延迟不超过 2 秒recent_minutes = 60:最近一小时内有成功记录limit = 50:最多拉 50 条
代码如下:
import requests
API_URL = "https://apiproxy.nexahub.one/proxies/search"
def fetch_http_proxies():
"""
从 NexaHub Proxy 拉一批 http/http_tunnel/https 代理。
返回值是原始的代理列表(每一项都是一个 dict)。
"""
payload = {
"tags_any": ["http", "http_tunnel", "https"],
"validation_status": "active",
"min_success": 3,
"max_latency_avg": 2000,
"recent_minutes": 60,
"limit": 50,
"fields": "ip,port,tags,last_success_at,latency_ms_avg,success_count",
}
resp = requests.post(API_URL, json=payload, timeout=10)
resp.raise_for_status()
data = resp.json()
items = data.get("items", [])
print(f"总共拿到 {len(items)} 个代理节点")
return items
if __name__ == "__main__":
proxies = fetch_http_proxies()
for item in proxies[:5]:
print(
item["ip"],
item["port"],
item.get("tags"),
"avg_latency=" + str(item.get("latency_ms_avg")),
)
这段跑起来,大致会打印出几行这样的东西(示意):
203.0.113.10 8080 ['http'] avg_latency=420.0
198.51.100.23 3128 ['https'] avg_latency=350.0
...
如果这里就空了,说明当前池子里符合条件的代理比较少,可以适当放宽一点条件,比如:
- 把
min_success降低 - 把
max_latency_avg提高 - 把
recent_minutes调大一点(比如 120 或 180)
4. 把代理接到 requests 上
API 返回的每条记录都是一个 dict,我们只需要拼成 requests 能认识的 proxies 字典就行。
一个简单的选择函数:
import random
def choose_proxy_for_requests(items):
"""
从代理列表里随机挑一个,转成 requests 的 proxies 结构。
"""
if not items:
raise RuntimeError("代理列表为空,无法选择")
item = random.choice(items)
ip = item["ip"]
port = item["port"]
# 这里按 http 代理来用,https 请求会通过 CONNECT 隧道转发
proxy_url = f"http://{ip}:{port}"
proxies = {
"http": proxy_url,
"https": proxy_url,
}
return proxies, item
接下来写一个“用代理访问网页”的小函数:
def fetch_url_with_proxy(url, proxies, timeout=10):
"""
使用给定代理访问目标 URL,返回 (状态码, 文本长度)。
出错时抛异常。
"""
resp = requests.get(
url,
proxies=proxies,
timeout=timeout,
headers={
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0 Safari/537.36"
)
},
)
resp.raise_for_status()
return resp.status_code, len(resp.text)
简单测一下代理是否能用,可以访问一个公开的测试接口,例如 https://httpbin.org/ip 或者随便一个普通网页:
if __name__ == "__main__":
test_url = "https://httpbin.org/ip"
items = fetch_http_proxies()
proxies, meta = choose_proxy_for_requests(items)
print("当前选中的代理:", proxies, "元信息:", meta)
status, length = fetch_url_with_proxy(test_url, proxies)
print(f"通过代理访问 {test_url},状态码={status},响应长度={length}")
跑通之后,说明:
- API 是正常的
- 选中的这个代理确实能访问外网
5. 做一个简单的“代理池轮询爬虫”
真正爬数据的时候,只靠一个代理肯定不保险。最起码要有这么几个逻辑:
- 拉一批代理到内存里
- 每次请求随机选一个代理
- 请求失败时,把这个代理暂时踢出列表,换下一个
- 代理快用完时再从 API 拉一批新的
下面是一个简化版的轮询器,只是示意,没有做持久化和多线程:
import time
class ProxyPool:
def __init__(self):
self.items = []
def refresh(self):
"""
从 API 重新拉一批代理,覆盖当前列表。
"""
print("从 API 刷新代理列表...")
self.items = fetch_http_proxies()
if not self.items:
print("警告:没有从 API 拿到任何代理")
else:
print(f"当前池中有 {len(self.items)} 个代理")
def get_one(self):
"""
拿一个代理。如果当前列表为空,会先尝试刷新一次。
"""
if not self.items:
self.refresh()
if not self.items:
raise RuntimeError("代理池为空,无法获取代理")
return choose_proxy_for_requests(self.items)
def ban(self, item):
"""
请求失败时,把这个代理从列表中移除。
"""
try:
self.items.remove(item)
except ValueError:
pass
有了这个类,就可以写一个简单的爬取循环:
def crawl_urls(urls):
pool = ProxyPool()
results = {}
for url in urls:
for attempt in range(5): # 每个 URL 最多尝试 5 次
try:
proxies, meta = pool.get_one()
print(f"[尝试] {url} 使用代理 {meta['ip']}:{meta['port']} 标签={meta.get('tags')}")
status, length = fetch_url_with_proxy(url, proxies, timeout=15)
print(f"[成功] {url} 状态码={status} 长度={length}")
results[url] = {
"status": status,
"length": length,
"proxy": meta,
}
break
except Exception as e:
print(f"[失败] {url} 代理 {meta['ip']}:{meta['port']},错误:{e}")
pool.ban(meta)
time.sleep(1)
else:
print(f"[放弃] {url} 连续失败,记录为空")
results[url] = None
return results
if __name__ == "__main__":
target_urls = [
"https://httpbin.org/get",
"https://httpbin.org/headers",
"https://www.example.com/",
]
crawl_results = crawl_urls(target_urls)
print("最终结果:", crawl_results)
这个版本比较朴素,但运行思路很清晰:
ProxyPool负责和代理 API 打交道crawl_urls只管拿代理、发请求、失败就换下一个- 每个 URL 最多尝试 5 次,避免死循环
如果你要对某个站点做批量爬取,只需要把 target_urls 换成自己的列表,然后在 fetch_url_with_proxy 里加入自己对 HTML 的解析逻辑就行。
6. 一点实战里的小经验
最后随手记几个在实际使用 HTTP 代理爬数据时经常会踩到的小坑:
-
超时时间不要设太小
公共代理本身就不稳定,网络链路也比直连多一层,像timeout=3这种数,基本上等于不让代理工作。一般我会把连接 + 读超时合在一个 10–15 秒的总超时。 -
适当过滤高延迟代理
上面我们用的是max_latency_avg=2000,就是平均延迟不超过 2 秒。这个值可以根据你目标站点的响应速度适当调高或调低。太高了爬起来会很慢,太低了又容易拿不到代理。 -
轮询比死盯一个代理稳得多
有些代理活跃一阵子就挂掉,轮询 + 自动剔除无效代理,是最简单好用的一种策略。上面ban()的写法虽然粗糙,但够用。 -
尊重目标站点
代理不代表“为所欲为”。正常控制并发、遵守网站的 robots 规则,别给对方服务搞崩了,否则迟早会被封得更狠。
7. 小结
整篇下来,其实就做了两件事:
- 利用 NexaHub Proxy 的 API,从
https://apiproxy.nexahub.one/proxies/search把带有http/http_tunnel/https标签的活跃代理拉出来; - 在 Python 里把这些代理接到
requests上,做一个简易的轮询和重试机制,让爬虫跑得更稳一些。
你可以在这个基础上继续往上堆东西,比如:
- 把代理池做成本地缓存文件
- 加上多线程 / 协程
- 把成功率、响应时间写进日志做个统计
核心思路其实已经够用了:用一个稳定的代理池 API 做后盾,前端爬虫只管按需取代理、发请求、记录结果。这样比到处找生 IP、临时填一两个代理地址,要省心很多。

被折叠的 条评论
为什么被折叠?



