爬虫代理池实战:NexaHub Proxy应用指南

用 Python + 免费代理池优雅地爬网页:从拿代理到跑请求的完整流程

写爬虫写久了,大家大概都遇到过这一幕:
开开心心用 requests 写了几十行代码,第一次跑还挺顺利,过一会儿直接给你来个 403429,甚至干脆连不上。原因很简单——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:代理 IP
  • port:代理端口
  • tags:标签,比如 http / http_tunnel / https / socks5
  • validation_statusactivestale,结合 recent_minutes 判断是否算“活跃”
  • 一堆延迟和时间字段:success_countlatency_ms_avglast_success_at

我们只关心三类标签:

  • http
  • http_tunnel
  • https

这三种都可以当作 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. 做一个简单的“代理池轮询爬虫”

真正爬数据的时候,只靠一个代理肯定不保险。最起码要有这么几个逻辑:

  1. 拉一批代理到内存里
  2. 每次请求随机选一个代理
  3. 请求失败时,把这个代理暂时踢出列表,换下一个
  4. 代理快用完时再从 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 代理爬数据时经常会踩到的小坑:

  1. 超时时间不要设太小
    公共代理本身就不稳定,网络链路也比直连多一层,像 timeout=3 这种数,基本上等于不让代理工作。一般我会把连接 + 读超时合在一个 10–15 秒的总超时。

  2. 适当过滤高延迟代理
    上面我们用的是 max_latency_avg=2000,就是平均延迟不超过 2 秒。这个值可以根据你目标站点的响应速度适当调高或调低。太高了爬起来会很慢,太低了又容易拿不到代理。

  3. 轮询比死盯一个代理稳得多
    有些代理活跃一阵子就挂掉,轮询 + 自动剔除无效代理,是最简单好用的一种策略。上面 ban() 的写法虽然粗糙,但够用。

  4. 尊重目标站点
    代理不代表“为所欲为”。正常控制并发、遵守网站的 robots 规则,别给对方服务搞崩了,否则迟早会被封得更狠。


7. 小结

整篇下来,其实就做了两件事:

  1. 利用 NexaHub Proxy 的 API,从 https://apiproxy.nexahub.one/proxies/search 把带有 http / http_tunnel / https 标签的活跃代理拉出来;
  2. 在 Python 里把这些代理接到 requests 上,做一个简易的轮询和重试机制,让爬虫跑得更稳一些。

你可以在这个基础上继续往上堆东西,比如:

  • 把代理池做成本地缓存文件
  • 加上多线程 / 协程
  • 把成功率、响应时间写进日志做个统计

核心思路其实已经够用了:用一个稳定的代理池 API 做后盾,前端爬虫只管按需取代理、发请求、记录结果。这样比到处找生 IP、临时填一两个代理地址,要省心很多。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值