通過上篇文章,可以了解到代理池實現(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®ions=
返回的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®ions='
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ù)了>_<