自动爬取网上免费代理实战:检测模块篇
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()
完整代码:https://github.com/rosaany/proxypool
3. 总结
检测模块原理理解起来比较简单,当我们拿到一个代理后,想判断其是否可用,可使用访问网站方式去测试,比如直接访问百度也是可以的,为了确保代理的可用性,建议测试不少于一遍,当测试多遍验证代理可用,就认为代理可用。
参考:https://github.com/Python3WebSpider/ProxyPool
如有错误,望纠正,与君共勉~