自动爬取网上免费代理实战:检测模块篇

1.检测模块说明

  • 检测模块顾名思义就是验证某个东西然后看结果怎么样,这里文中说的是检测代理是否可用。

  • 当我们从网上爬取代理下来时,比如:proxy = '185.78.228.24:8000',如何检测它是否有效呢?

  • 测试一个代理是否可用的标准,在存储模块篇就提到过了【跳转】,这里再简单过一遍。如果一个代理可用,就给它设置一个分数,比如设置一个最高分,最高分标识该代理最可用的情况,反之最低分则标识该代理最不可用。在起初给代理设置一个默认分数,当测试一个代理可用时,把该代理设置成最高分,当循环测试完一遍代理后,发现不可用的代理,就把不可用的代理的分数-1,直至减至0分,0分可以表示为最低分最不可用,则把它移除掉。

  • 检测原理也很简单,拿到代理后。第一步,拿代理去访问http://www.httpbin.org/get网站,如果响应正常,我们就把其中一个字段名字叫origin的值保存下来,为了进一步确保测试结果。第二步,不加代理,然后再次访问上述的网站,如果响应正常再次拿到其中的origin字段值,比较前后者的值,比较结果必须不一致。第三步,拿第二步的origin字段值,与代理ip比较做比较,如果比对结果一致,则认为代理可用。

    比如:

    有一个代理: 113.93.224.2:3256

    第一步,加代理访问http://www.httpbin.org/get,响应返回:{..., "origin": "113.93.224.2",....}

    第二步,不加代理访问http://www.httpbin.org/get,响应返回:{..., "origin": "66.42.45.249",....}

    比较第一步origin=113.93.224.2和第二步的origin=66.42.45.249,比较结果必须不一致,如果结果一致,说明代理失败

    第三步,拿第一步得到的origin=113.93.224.2与代理113.93.224.2:3256(只取ip)比较,结果一致。

2.代码实现

代码环境:Python 3.9.1, Redis:3.5.3

第三方依赖包:aiohttp、pyquery、lxml

主模块

import asyncio
import sys
import aiohttp
import re
from pyquery import PyQuery as pq
from proxypool.storage.redisclient import redisClient
from aiohttp.client_exceptions import ClientConnectorError, ClientHttpProxyError, \
    ServerDisconnectedError, ClientOSError, ClientResponseError
from asyncio import TimeoutError
from proxypool.untils.parse import bytes_convert_string
from lxml.etree import ParserError, XMLSyntaxError
from proxypool.untils.loggings import Logging
from proxypool.setting import COUNT, REDIS_KEY, TEST_URL, TEST_URL_SWITCH, ip111
from requests.exceptions import ConnectionError
from urllib3.exceptions import MaxRetryError, NewConnectionError
from socket import gaierror

access_proxy = False        # 默认标识代理不可用
again_access_proxy = False  # 默认标识代理不可用

Exceptions = (
    ClientConnectorError,
    ClientHttpProxyError,
    ClientOSError,
    ServerDisconnectedError,
    TimeoutError,
    ClientResponseError,
    AssertionError,
    ParserError,
    XMLSyntaxError,
    ConnectionError,
    MaxRetryError,
    NewConnectionError,
    gaierror,
)


class Tester(object):
    """
    测试代理池
    """

    def __init__(self):
        self.redis = redisClient()
        self.logger = Logging()
        
        # https://github.com/aio-libs/aiohttp/issues/4536#issuecomment
        if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
            asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
        self.loop = asyncio.get_event_loop()

    async def test(self, proxy):
        """
        测试每个代理状况
        :param: proxy,代理服务器,比如:`66.42.45.249:6666`
        
		ip111:默认不开启,从谷歌测试proxy是否用,True if available, Interface from `http://ip111.cn/`
		TEST_URL_SWITCH:默认开启,需配合TEST_URL测试,TEST_URL可自行配置,比如TEST_URL: `https://www.baidu.com`
        """
        global access_proxy, again_access_proxy
        try:
            async with aiohttp.ClientSession() as session:
                if ip111:
                    
                    async with session.get(url='http://sspanel.net/ip.php', proxy=f'http://{proxy}') as response:
                        res_text = await response.text()
                        doc = pq(res_text).text()
                        match = re.search(r'(\d+\.\d+\.\d+\.\d+)', doc)
                        if match:
                            # access_proxy = match.group()
                            access_proxy = True 

                # 获取当前主机公网ip
                async with session.get(url='https://www.httpbin.org/ip') as response:
                    res_json = await response.json()
                    origin_ip = res_json.get('origin', None)

                # 测试proxy是否可用,可用则返回proxy
                async with session.get(url='https://www.httpbin.org/ip', proxy=f'http://{proxy}') as response:
                    res_json = await response.json()
                    _proxy_ip = res_json.get('origin', None)

                # 断言判断
                assert origin_ip != _proxy_ip
                proxy_ip = proxy.split(":")[0]
                assert proxy_ip == _proxy_ip

                if TEST_URL_SWITCH:
                    
                    async with session.get(TEST_URL, proxy=f'http://{proxy}') as response:
                        if response.status == 200:
                            again_access_proxy = True

                if access_proxy or again_access_proxy:
                    # 设置最高分
                    self.redis.max(REDIS_KEY, proxy)  
                else:
                    # 代理不可用则减分
                    self.redis.decrease(REDIS_KEY, proxy)  
        except Exceptions:
            # 代理不可用则减分
            self.redis.decrease(REDIS_KEY, proxy)  
            self.logger.debug(f'proxy {proxy} is invalid')

    @Logging.catch
    def run(self):
        self.logger.info('starting tester......')
        
        count = self.redis.get_count(REDIS_KEY)
        self.logger.debug(f'{count} proxies to test')
        cursor = 0
        while True:
            self.logger.debug(f'Testing proxies use cursor {cursor}, count {COUNT}')
            # 从redis中迭代获取集合元素,其中COUNT参数是:每次批量测试代理的个数
            cursor, proxies = self.redis.batch(REDIS_KEY, cursor, COUNT) 
            if proxies:
                # 从redis取出来的value默认是python的bytes类型
                tasks = [self.test(bytes_convert_string(proxy[0])) for proxy in
                         proxies]  
                self.loop.run_until_complete(asyncio.wait(tasks))
            if not cursor:
                break


def runtest():
    # 跳过redis测试单个代理是否可用
    proxy = '185.78.228.24:8000'
    tests = [tester.test(proxy)]
    tester.loop.run_until_complete(asyncio.wait(tests))


if __name__ == '__main__':
    tester = Tester()
    # tester.run()
    runtest()

image

完整代码:https://github.com/rosaany/proxypool

3. 总结

检测模块原理理解起来比较简单,当我们拿到一个代理后,想判断其是否可用,可使用访问网站方式去测试,比如直接访问百度也是可以的,为了确保代理的可用性,建议测试不少于一遍,当测试多遍验证代理可用,就认为代理可用。

参考:https://github.com/Python3WebSpider/ProxyPool

如有错误,望纠正,与君共勉~

posted @ 2021-08-02 18:11  Rosaany  阅读(106)  评论(0编辑  收藏  举报