代理池概述与开发环境准备

在进行爬虫开发期间,目标网站常常会针对高频访问的IP施行封锁策略,所以构建一个稳定且可靠的代理池成了爬虫工程师所必须具备的技能。代理池的关键作用是动态性地维护一批能够使用的代理IP,以供爬虫程序随时去调用,进而突破访问限制。本阶段的入门教程会引领读者从零开始去设计并实现一个完整的代理池系统。在开发环境这方面而言,我们挑选Python 3.8+当作编程语言,原因在于它具备丰富的网络库以及简洁的语法。要安装的核心库有,用于发送HTTP请求的requests ,作为与MongoDB数据库交互驱动的pymongo ,还有用于构建API服务的Flask。这些工具将支撑起整个代理池的采集、校验与调度功能。

代理池的系统架构设计

由四大模块合成是通常一个功能完备的那些代理池,存储模块,采集模块,校验模块和API接口模块。负责持久化代理IP信息的是存储模块,这里我们把MongoDB采用成数据库,利用其灵活的文档结构去存储IP的IP地址,端口,协议类型,响应速度和历史失败次数等字段。通过爬取免费代理网站或者付费代理API来获取原始IP列表的是采集模块,定期检测这些IP的可用性跟匿名度,并且依据检测结果动态调整其优先级分数的是校验模块,对外提供RESTful风格调用接口以备爬虫程序随时能够获取优质代理的是API接口模块。

代理IP数据模型的定义

在代码实现的层面之上,首先要做到明确代理IP的数据结构,我们构建出一个用于规范代理IP属性以及方法的Python类,这对后续各个模块实施统一的操作有着帮助。此类含有一类表现为ip的基础字段,还有一类呈现为port的基础字段,另外存在一类形如protocol(HTTP/HTTPS)这样的基础字段,与此同时对其中引入了一类称作score的字段,该字段被用以衡量代理的那种可靠性,其初始的值能够被设置为一定的分数,每当校验成功之时就进行加分,而要是失败的话那么就进行减分,一旦低于阈值便予以删除。此外,还需要记录,speed(也就是响应时间),以及,failed_count(即连续失败次数),而这些属性,将会为后续的择优查询,提供相应的依据。

工具模块的封装实现

为了促使代码复用性得以提高,我们对一系列工具函数进行封装,譬如,有一个定义得以存在,该定义是关于获取随机User - Agent的函数,借助此函数模拟不同浏览器发送请求,以此降低被识别的风险,还有另一个关键工具,是一个可检测代理IP可用性,也就是专门针对检测代理IP可用性的函数,但它是通过目标测试网站(像httpbin.org这样的网站)去测量代理的响应时间以及协议支持情况的。被采集模块频繁调用的这些工具函数,会被校验模块也频繁调用,所以采用模块化设计,将其存放在名为utils.py的文件中,可以保持项目结构清晰。

数据库操作类的构建

编程入门爬虫存储_python爬虫代理池开发环境_代理池设计思路

整个代理池的基石是数据库操作,我们于__init__方法里建立与MongoDB的连接并且获取指定的代理集合对象,在__del__方法当中编写关闭数据库连接的逻辑以至于确保资源正确释放,基础功能涵盖插入新代理、依据IP删除代理、修改代理属性(像更新分数和速度)以及查询所有代理。面向API模块的特定需求,达成条件查询方法,此方法可依据协议类型以及数量来筛选记录。并且要按照分数进行降序排列,并同步按照速度进行升序排列,优先返回高质量的代理IP。

代理IP的智能获取与更新机制

我们有着这样一个目的,那就是要让 API 模块把工作开展得高效起来,所以呢就去实现了一个具备智能获取代理这么一种功能的东西。这种方法,它有接收协议类型跟目标网站域名当作参数这样的行为,先是从数据库里去查询那种适用于这个协议并且分数比较高的代理列表。紧接着,借助工具模块来检测这些代理对于指定域名的实际访问效果,还会进一步把无效 IP 给过滤掉。最后,从剩下的那些可用代理当中随机返回一个,以此来达成负载均衡的效果。与此同时,会把测试通过的那个域名添加到这个代理 IP 的专属记录当中去,积累它所擅长访问的网站的相关特征,以给后续任务提供参考依据。

import pymongo
import random

from settings import MONGO_URL, DEFAULT_SCORE
from domain import Proxy
from utils.log import logger

class MongoPool(object):
def __init__(self):
"""初始化"""
self.client = pymongo.MongoClient(MONGO_URL)
# 获取要操作的集合
self.proxies = self.client['proxy_pool']['proxies']

def __del__(self):
# 关闭数据库连接
self.client.close()

def insert(self, proxy=None):
"""保存代理IP到数据库中"""
if proxy:
# 查询代理ip数量,
count = self.proxies.count_documents({'_id': proxy.ip})
# 如果值不为0, 说明该代理IP, 已经存在了, 直接返回
if count != 0:
# 表示代理IP已经存在, 直接返回
return

# 把Proxy对象转换为转换为字典
dic = proxy.__dict__
# 设置_id字段, 为代理IP的IP地址
dic['_id'] = proxy.ip
# 向集合中插入代理IP
self.proxies.insert_one(dic)
logger.info("插入新代理IP:{}".format(dic))
else:
logger.error("没有传入要插入的proxy")

def update(self, proxy=None):
"""更新代理"""
if proxy:
self.proxies.update_one({'_id':proxy.ip}, {"$set": proxy.__dict__})
logger.info("更新代理: {}".format(proxy))
else:
logger.error("请求传入要更新的代理")

def find_all(self):
# 获取查询所有代理IP的cursor对象
cursor = self.proxies.find()
# 变量游标, 获取代理IP
for item in cursor:
# 删除_id键
item.pop('_id')
# 创建Proxy对象
proxy = Proxy(**item)
# 返回代理IP
yield proxy

def delete(self, proxy=None):
"""根据条件删除代理IP"""
if proxy:
self.proxies.delete_one({'_id': proxy.ip})
logger.info('删除代理: {}'.format(proxy))
else:
logger.error("请传入要删除的代理")

def find(self, conditions=None, count=0):
"""
根据条件查询代理IP
:param conditions: # 字典形式的查询条件
:param count: 查询多少条数据
:return: 返回先按分数降序, 后按响应速度升序排列前count条数据, 如果count==0, 就查询所有的,
"""
# 如果没有conditions, 将conditions设置为{}
if conditions is None:
conditions = {}

# 获取查询的游标地下
cursor = self.proxies.find(conditions, limit=count).sort(
[("score", pymongo.DESCENDING), ("speed", pymongo.ASCENDING)])
# 创建一个list, 用于存储Proxy
results = []
# 变量游标, 获取代理IP
for item in cursor:
# 创建Proxy对象
# 删除_id键
item.pop('_id')
# 创建Proxy对象
proxy = Proxy(**item)
# 把Proxy对象添加到结果集
results.append(proxy)
# 返回查询的结果
return results

def get_proxies(self, protocol=None, domain=None, count=0, nick_type=0):
"""
根据协议类型, 获取代理IP, 默认查询都是高匿的
:param protocol: 协议: http 或 https
:param domain: 要访问网站的域名
:param count: 代理IP的数量, 默认全部
:param nick_type: 匿名程度, 默认为高匿

:return:
"""

conditions = {'nick_type': nick_type}
if domain:
# 如果有域名, 就获取不可用域名中, 没有该域名的代理
conditions['disable_domains'] = {'$nin':[domain]}

if protocol is None:
# 如果没有协议, 就获取及支持http 又支持https的协议
conditions['protocol'] = 2
elif protocol.lower() == 'http':
# 如果是HTTP的请求, 使用支持http 和 支持http/https均可以
conditions['protocol'] = {'$in': [2, 0]}
elif protocol.lower() == 'https':
# 如果是HTTP的请求, 使用支持https 和 支持http/https均可以
conditions["protocol"] = {'$in': [2, 1]}

return self.find(conditions, count=count)

def random(self, protocol=None, domain=None, count=0):
"""
从指定数量代理IP中, 随机获取一个
:param protocol: 协议: http 或 https
:param domain: 要访问网站的域名
:param count: 代理IP的数量
:return: 一个随机获取代理IP
"""
proxy_list = self.get_proxies(protocol, domain=domain, count=count)
return random.choice(proxy_list)

def disable_domain(self, ip, domain):
# 如果该IP下没有这个域名才更新
if self.proxies.count_documents({'_id':ip,'disable_domains':domain}) == 0:
# 向指定IP的不可用域名中添加域名
self.proxies.update_one({'_id': ip}, {'$push': {'disable_domains': domain}})

if __name__ == '__main__':
# 1. 保存代理IP
# proxy = Proxy('124.89.97.43', '80', protocol=1, nick_type=0, speed=0.36, area='陕西省商洛市商州区')
# proxy = Proxy('124.89.97.43', '80', protocol=2, nick_type=0, speed=0.36, area='陕西省商洛市商州区')
# proxy = Proxy('124.89.97.44', '80', protocol=1, nick_type=0, speed=0.36, area='陕西省商洛市商州区')
# proxy = Proxy('124.89.97.45', '80', protocol=0, nick_type=0, speed=0.36, area='陕西省商洛市商州区')
# proxy = Proxy('124.89.97.46', '80', protocol=0, nick_type=2, speed=0.36, area='陕西省商洛市商州区')
# mongo.insert(proxy)

# 3. 测试减少代理分值
# mongo.decrease_score(proxy)
# 4. 恢复
# proxy = Proxy('124.89.97.43', '80', protocol=2, nick_type=0, speed=0.36, area='陕西省商洛市商州区', score=1)
# mongo.resume_score(proxy)
# 5. 获取大理IP列表
# proxies = mongo.get_proxies('http')
# proxies = mongo.get_proxies('https')
# 随机获取一个代理IP
# proxy = mongo.random('http')
# proxy = mongo.random('https')
# print(proxy)
# 删除代理IP
proxy = Proxy('124.89.97.46', '80', protocol=0, nick_type=2, speed=0.36, area='陕西省商洛市商州区')
mongo.delete(proxy)

# 2. 查询高匿代理IP
proxies = mongo.find({'nick_type': 0})
for proxy in proxies:
print(proxy)

完整代码整合与流程测试

将上边所说的全部模块拼装到一块儿,构成一个能够运作的代理池系统。最先运行采集模块去取得原始代理,把它存进数据库。接着启动校验模块里边的定时任务,针对库内的所有IP开展周期性的检测,进而更新分数以及状态。最终启动Flask所提供的API服务,爬虫程序凭借HTTP请求就能够随时获取到一个针对特定网站的可用代理。譬如,去访问/get?protocol=http&domain=example.com这种情况,就能够返回符合要求的代理IP了,整个系统运用松耦合设计方式,每个模块职责是单一存在的,方便后期进行扩展以及维护,为爬虫的稳定运行给予坚实保障。