改輪子之付費代理池實現(xiàn)

通過上篇文章,可以了解到代理池實現(xiàn)的具體思路,但公司業(yè)務(wù)需要,項目中要使用到性能更穩(wěn)定的代理
推薦使用自己搭的服務(wù)或購買收費代理
根據(jù)需求,修改上篇文章的代理池實現(xiàn)即可滿足需求,這里以芝麻代理為例

業(yè)務(wù)分析

最核心的部分當然修改獲取免費代理的方法啦,在get_proxy的類ProxyGetter中把所有的以proxy_開頭的免費代理獲取方法注釋掉,添加芝麻接口的proxy_方法即可

當然芝麻的代理機制和免費代理機制也是不同的,具體表現(xiàn)為:
免費代理:量大,可用代理少
付費代理:量少,幾乎獲取到的代理都可用
業(yè)務(wù)上需要,個人使用的版本是芝麻長效代理,每個代理有效時間為25分鐘到3小時,每天200個,在獲取代理的方法中調(diào)用芝麻接口獲取代理,每次只需要少量的幾個(因為芝麻的代理質(zhì)量比較好,幾乎獲取到的都是可用代理)
通過接口拿到代理之后做可用性檢測,然后入庫,因為每天可獲取的代理量只有兩百個,要保證24小時mongo中都有可用的代理,設(shè)計代理池的閾值范圍為3~5個,保證每時每刻代理池都有3到5個代理

代理量分配計算:

理想情況下平均每個代理持續(xù)時間大概為(25+180)/ 2 = 102.5 分鐘
200個代理分布到24小時: 200*102.5/24*60 =14.236...
理論上講可以保證池中最大閾值為14個,保險起見把閾值調(diào)的更小來提高代理服務(wù)的穩(wěn)定性,代理池容量要設(shè)置更小一些,因為付費的代理服務(wù)或多或少都有可能會有異常狀況出現(xiàn)

這個是芝麻代理返回json的數(shù)據(jù)調(diào)用接口:
http://webapi.http.zhimacangku.com/getip?num=2&type=2&pro=&city=0&yys=0&port=1&pack=***&ts=1&ys=0&cs=0&lb=1&sb=0&pb=45&mr=1&regions=
返回的json格式:
{"code":0,"success":true,"msg":"","data":[{"ip":"127.0.0.1", "port":123456}]}

那么獲取代理的方法就很簡單了:

    def proxy_zhima(self):
        url = 'http://webapi.http.zhimacangku.com/getip?num=2&type=2&pro=&city=0&yys=0' \
              '&port=1&pack=***&ts=1&ys=0&cs=0&lb=1&sb=0&pb=45&mr=1&regions='
        resp = parse_url(url)

        html = json.loads(resp)
        code = html.get('code')
        success = html.get('success')
        if code != 0 or success == 'false':
            print(html)
            return
        datas = html.get('data')
        for data in datas:
            yield data.get('ip') + ':' + str(data.get('port'))

https檢測

原方案實現(xiàn)了http的代理檢測,而我們有時會用到https的代理
這里就要在檢測模塊增加https的檢測方法、并在數(shù)據(jù)入庫的時候標識此次入庫的代理為http/https
aiohttp檢測https代理的方式和檢測http代理方式相同,只需要切換檢測url為https的即可
邏輯如下:
首先獲取代理并默認檢測代理是否是https,檢測失敗則再次檢測http
這樣取代理的時候默認取https代理(因為https代理所有的http協(xié)議都可以用),至于非https默認只入庫,特殊情況才使用

# tester.py -> class ProxyTester
    async def test_single_proxy(self, proxy):
        """
        測試一個代理,如果有效,即入庫
        """
        scheme = 'http://'
        test_url = HTTPS_TEST_URL
        if isinstance(proxy, bytes):
            proxy = proxy.decode('utf-8')
        real_proxy = scheme + proxy
        async def test_proxy(https=True):
            name = 'https' if https else 'http'
            async with session.get(test_url, proxy=real_proxy, timeout=10) as response:
                if response.status == 200 or response.status == 429:
                    self._conn.put(proxy, https)
                    print('Valid {} proxy'.format(name), proxy)
                else:
                    print('Invalid {} status'.format(name), response.status, proxy)
                    self._conn.delete(proxy)

        try:
            async with aiohttp.ClientSession() as session:
                try:
                    await test_proxy()
                except:
                    try:
                        test_url = HTTP_TEST_URL
                        await test_proxy(False)
                    except Exception as e:
                        self._conn.delete(proxy)
                        print('Invalid proxy', proxy)
                        print('session error', e)
        except Exception as e:
            print(e)

注意方法test_single_proxy內(nèi)部還嵌套了test_proxy方法,用于檢測業(yè)務(wù)
內(nèi)層嵌套函數(shù)默認可以獲取到外層的上下文(環(huán)境變量)
檢測成功即入庫,注意調(diào)用方法self._conn.put(proxy, https)
調(diào)用數(shù)據(jù)庫實例MongodbClient.put的方法,此時我已經(jīng)修改put方法的實現(xiàn),需要傳入兩個參數(shù),https參數(shù)用來標識此次入的的代理類型

此時我們關(guān)注一下mongo的api具體實現(xiàn):

class MongodbClient(object):

    def __init__(self, table=TABLE):
        self.table = table
        self.client = MongoClient(HOST, PORT)
        self.db = self.client[NAME]

    def change_table(self, table):
        self.table = table

    def proxy_num(self):
        """
        得到數(shù)據(jù)庫中代理num最高的數(shù)
        """
        if self.get_nums != 0:
            self.sort()
            datas = [i for i in self.db[self.table].find()]
            nums = []
            for data in datas:
                nums.append(data['num'])
            return max(nums)
        else:
            return 0

    def get(self, count):
        """
        從數(shù)據(jù)庫左側(cè)拿到相應(yīng)數(shù)量的代理
        """
        if self.get_nums != 0:
            self.sort()
            datas = [i for i in self.db[self.table].find()][0:count]
            proxies = []
            for data in datas:
                proxies.append(data['proxy'])
                # self.delete(data['proxy'])
            return proxies
        return None

    def put(self, proxy, https=False):
        """
        放置代理到數(shù)據(jù)庫
        """
        num = self.proxy_num() + 1
        if self.db[self.table].find_one({'proxy': proxy}):
            pass
        else:
            self.db[self.table].insert({'proxy': proxy, 'num': num, 'http/s': https})
            # self.db[self.table].insert({'proxy': proxy, 'num': num})

    def pop(self, https=False):
        """
        從數(shù)據(jù)庫右側(cè)拿到一個代理
        """
        if self.get_nums != 0:
            self.sort()
            data = random.choice([i for i in self.db[self.table].find({'http/s': https})])
            # data = [i for i in self.db[self.table].find({'http/s': https})][-1]
            proxy = data['proxy'] if data != None else None
            # 取出來使用后就從池中移除
            # self.delete(proxy)
            # 改變策略保留ip
            return proxy
        return None

    def delete(self, value):
        """
        如果代理沒有通過檢查,就刪除
        """
        self.db[self.table].remove({'proxy': value})

    def sort(self):
        """
        按num鍵的大小升序
        """
        self.db[self.table].find().sort('num', ASCENDING)

    def clean(self):
        """
        清空數(shù)據(jù)庫
        """
        self.client.drop_database('proxy')

    @property
    def get_nums(self):
        """
        得到數(shù)據(jù)庫代理總數(shù)
        """
        return self.db[self.table].count()

    @property
    def get_count(self):
        # 分別統(tǒng)計http/s的代理總數(shù)
        http = self.db[self.table].find({'http/s': False}).count()
        https = self.db[self.table].find({'http/s': True}).count()
        return http, https

其中put方法入庫的實現(xiàn):
self.db[self.table].insert({'proxy': proxy, 'num': num, 'http/s': https})
可以看到插入的mongo文檔添加了一個'http/s'字段用來標識代理的類型
get_count方法會分別返回兩種代理類型的數(shù)量

元類

博主之前的文章有介紹過元類,熟悉了就不難發(fā)現(xiàn)這個代理池實現(xiàn)的元類使用稍微有一點冗余部分
此前的元類中實現(xiàn)在類中添加兩個屬性,代理方法數(shù)量、代理方法名
其中代理方法名是一個列表類型,有了列表我們就可以遍歷列表了,所以此時元類中只需要一個屬性即可,代理方法的數(shù)量是多余的:

class ProxyMetaclass(type):
    """
    元類,在ProxyGetter類中加入
    __CrawlFunc__參數(shù)
    表示爬蟲函數(shù)
    """
    def __new__(cls, name, bases, attrs): 
        attrs['__CrawlFunc__'] = []
        for k in attrs.keys():
            if k.startswith('proxy_'):
                attrs['__CrawlFunc__'].append(k)                
        
        return super(ProxyMetaclass, cls).__new__(cls, name, bases, attrs)

此時注意,原先是返回type.__new__(cls, name, bases, attrs)
我們修改為更推薦的創(chuàng)建元類方式:super(ProxyMetaclass, cls).__new__(cls, name, bases, attrs)
添加代理的方法也要修改:

# adder.py -> class PoolAdder
 def add_to_pool(self):
        """
        補充代理
        """
        print('PoolAdder is working...')
        proxy_count = 0
        while not self.is_over_threshold():
            # 迭代所有的爬蟲
            # __CrawlFunc__是爬蟲方法
            for callback in self._crawler.__CrawlFunc__:
                raw_proxies = self._crawler.get_raw_proxies(callback)
                # 測試爬取到的代理
                self._tester.set_raw_proxies(raw_proxies)
                self._tester.test()
                proxy_count += len(raw_proxies)
                if self.is_over_threshold():
                    print('Proxy is enough, waiting to be used...')
                    break

這樣修改下來,代碼量、業(yè)務(wù)邏輯相對之前會簡潔一些,要善用元類

配置文件部分

配置部分做一些微調(diào),添加https的檢測url
修改調(diào)度周期

# 供測試的url
HTTP_TEST_URL = 'http://mini.eastday.com/assets/v1/js/search_word.js'
HTTPS_TEST_URL = 'https://mp.weixin.qq.com/mp/getappmsgext'

# Pool 的低閾值和高閾值
POOL_LOWER_THRESHOLD = 3
POOL_UPPER_THRESHOLD = 5

# 兩個調(diào)度進程的周期
VALID_CHECK_CYCLE = 3
POOL_LEN_CHECK_CYCLE = 5

其中每隔3s檢測池中代理的有效性,每隔5s檢測代理池容量大小是否在閾值范圍(3~5)
大體就實現(xiàn)定制自己的代理池服務(wù)了>_<

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容