ProxyBroker改造记录

近几年的工作多数围绕着Web Services进行,无非就是授权的攻击测试、扫描等等,这类操作也是各种WAF和风控机制的首选拦截对象。由于前期没有做代理,导致公司出口IP被某网站封禁,并通过各种威胁情报共享的机制被应用到更多网站上,甚至办公室没法访问公司自己的某些服务。去年开始尝试使用一些开源的工程搭建代理池,如scyllaproxy_poolProxyBroker等,因为只是简单的搭建,只能算是“能用”的阶段,一些问题并没有及时解决。最近对上述代理工具进行了一些严格的测试,并且阅读了部分代码,最终确定使用ProxyBroker作为代理工具,并加以改造,实现了稳定的代理池。

根据现有情况,我们对代理池的需求如下:

  • 支持二级代理,即ProxyRotator,内网搭建一个代理服务器,自动切换出口代理
  • 尽可能多的代理IP,扩展性要好,方便增加代理网站
  • 出口IP检测,检测地理位置、连接速度、类型、匿名度等
  • 代理有效性检测,自动剔除失效IP
  • 稳定、长时间运行,各种策略完善

综合评估后,ProxyBroker比较符合需求,在使用的过程中,遇到两个问题:

  • ProxyBroker中大多数默认代理网站需要国际出口
  • ProxyBroker的serve模式不会对HTTP匿名代理进行判断

改起来都比较简单,由于不想PR,所以使用了Patch的方式解决。

增加国际出口代理

ProxyBroker使用aiohttp从代理网站获取ip,代码位于providers.py。aiohttp默认不会使用环境变量中的HTTP_PROXY和HTTPS_PROXY,即trust_env默认是False,同时ProxyBroker也不会处理。所以,如果在国内使用,会发现ProxyBroker获取到的IP数量和质量相当的差,这也就是最开始没使用的原因。Patch方法也很简单,代码如下:

class ProviderWithProxy(Provider):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    async def _get(self, url, data=None, headers=None, method="GET"):
        page = ""
        try:
            timeout = aiohttp.ClientTimeout(total=self._timeout)
            async with self._sem_provider, self._session.request(
                    method, url, data=data, headers=headers, timeout=timeout,
                    proxy=UPSTREAM_HTTP_PROXY,

            ) as resp:
                page = await resp.text()
                if resp.status != 200:
                    logger.debug(
                        "url: %s\nheaders: %s\ncookies: %s\npage:\n%s"
                        % (url, resp.headers, resp.cookies, page)
                    )
                    raise BadStatusError("Status: %s" % resp.status)
        except (
                UnicodeDecodeError,
                BadStatusError,
                asyncio.TimeoutError,
                aiohttp.ClientOSError,
                aiohttp.ClientResponseError,
                aiohttp.ServerDisconnectedError,
        ) as e:
            page = ""
            logger.debug("%s is failed. Error: %r;" % (url, e))
        return page
# Patch
Provider._get = ProviderWithProxy._get

ProxyPool增加HTTP高匿代理筛选

简单来说,ProxyBroker内部使用ProxyPool作为Server从Providers提取IP的中间层,虽然在获取代理后,可以通过参数指定获取的HTTP代理IP的类型是透明、匿名还是高匿,但目前有很多代理IP,同时支持HTTP和HTTPS,而ProxyPool在获取的时候,没有做检测。所以,获取的时候需要增加一段检测代码:

class ProxyPoolV2(ProxyPool):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    async def get(self, scheme):
        scheme = scheme.upper()
        # 单独处理HTTP
        if scheme == "HTTP":
            chosenProxy = None
            # 首先检测现有的池中是否有满足需求的代理。
            for priority, proxy in self._pool:
                if scheme in proxy.schemes and \
                        proxy.types[scheme] == "High":
                    chosenProxy = proxy
                    self._pool.remove((proxy.priority, proxy))
                    break
            else:
                # 尝试获取20个代理,没有就算了
                for i in range(0, 20):
                    chosenProxy = await self._import(scheme)
                    if chosenProxy.types[scheme] == "High":
                        logger.info(f"{chosenProxy} is good.")
                        break
                    else:
                        logger.warning(f"{chosenProxy} is bad.")
            return chosenProxy
        # 非HTTP,直接使用。
        for priority, proxy in self._pool:
            if scheme in proxy.schemes:
                chosen = proxy
                self._pool.remove((proxy.priority, proxy))
                break
        else:
            chosen = await self._import(scheme)
        return chosen
ProxyPool.get = ProxyPoolV2.get

这样就可以在遇到HTTP请求的时候,筛选出高匿的代理IP并且使用。

增加Providers

按照官方的Provider示例,填写一个URL即可,ProxyBroker会通过正则筛选出ip和端口。

发表评论