赞
踩
本文章案例仅供参考
提示:这里可以添加本文要记录的大概内容:
代理池就是有代理IP组成的池子, 它可以提供多个稳定可用的代理IP
ip反爬
; 也就是当同一个IP访问这个网站次数过多, 频率过高,就会限制这个IP访问. 怎么解决这个问题呢? 就是需要经常换IP; 使用代理IP是其中一个比较常用的方案。平台: Mac,可以运行Window和Linux上
开发语言: Python3
开发工具: PyCharm
使用到的主要技术:
requests: 发送请求, 获取页面数据
lxml: 使用XPATH从页面提取我们想要的数据
pymongo: 把提取到代理IP存储到MongoDB数据库中和从MongoDB数据库中读取代理IP,给爬虫使用.
Flask: 用于提供WEB服务
实现对代理ip的增删改查操作,这里使用MongoDB来存储代理IP
程序启动入口: `main.py` 代理池提供一个统一的启动入口
数据模型: `domain.py`: 代理IP的数据模型
工具模块:
1.日志模块: 用于记录日志信息
2.http模块: 用于获取随机User-Agent的请求头
配置文件: `settings.py` 用于配置日志, 启动的爬虫, 启用的代理IP检验类等
-- IPProxyPool -- db -- __init__.py -- mongo_pool.py -- proxy_validate -- __init__.py -- httpbin_validator.py -- proxy_spider -- __init__.py -- base_spider.py -- proxy_spiders.py -- run_spiders.py -- utils -- __init__.py -- http.py -- log.py -- proxy_test.py -- proxy_api.py -- main.py -- domain.py -- settings.py
用于封装代理信息
步骤:
- 定义一个类, 继承object
- 实现__init__方法, 负责初始化, 包含如下字段:
- ip: 代理的IP地址
- port: 代理IP的端口号
- protocol: 代理IP支持的协议类型,http是0, https是1, https和http都支持是2
- nick_type: 代理IP的匿名程度, 高匿:0, 匿名: 1, 透明:0
- speed: 代理IP的响应速度, 单位s
- area: 代理IP所在地区
- score: 代理IP的评分, 默认分值可以通过配置文件进行配置. 在进行代理可用性检查的时候, 每遇到一次请求失败就减1份, 减到0的时候从池中删除. 如果检查代理可用, 就恢复默认分值
- disable_domains: 不可用域名列表, 有些代理IP在某些域名下不可用, 但是在其他域名下可用
- 创建配置文件: settings.py; 定义MAX_SCORE = 50,
代码实现:
# 代理模型类, 用于封装代理相关信息
class Proxy(object):
def __init__(self, ip, port, protocol=-1, nick_type=-1,speed=-1, area=None, score=50, disable_domains=[]):
self.ip=ip #ip
self.port=port #端口号
self.protocol=protocol #代理IP支持协议类型,HTTP是0,HTTP是1,https和http是支持2
self.nick_type=nick_type #匿名程度:高匿0 匿名1 透明0
self.area=area #所在地区
self.speed=speed #速度 单位s
self.score=score #代理ip评分 在进行代理可用性检查的时候, 每遇到一次请求失败就减1份, 减到0的时候从池中删除. 如果检查代理可用, 就恢复默认分值
self.disable_domains=disable_domains
def __str__(self):
#返回数字字符串
return str(self.__dict__)
步骤:
- 拷贝笔记日志代码到项目中
- 把日志相关配置信息 放到配置文件中
- 修改日志代码, 使用配置文件中的配置信息
代码:
import sys import logging # 默认的配置 LOG_LEVEL = logging.INFO # 默认等级 LOG_FMT = '%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s: %(message)s' # 默认日志格式 LOG_DATEFMT = '%Y-%m-%d %H:%M:%S' # 默认时间格式 LOG_FILENAME = 'log.log' # 默认日志文件名称 class Logger(object): def __init__(self): #1.获取一个logger对象 self._logger=logging.getLogger() #2.设置formater对象 self.formatter=logging.Formatter(fmt=LOG_FMT,datefmt=LOG_DATEFMT) #3.设置日志输出 #3.1 设置文件日志模式 self._logger.addHandler(self._get_file_handler(LOG_FILENAME)) #3.2 设置终端日志模式 self._logger.addHandler(self._get_console_handler()) #4.设置日志等级 self._logger.setLevel(LOG_LEVEL) def _get_file_handler(self,filename): '''返回一个文件日志handler''' # 1. 获取一个文件日志handler filehandler=logging.FileHandler(filename=filename,encoding="utf-8") #2.设置日志格式 filehandler.setFormatter(self.formatter) #3.返回 return filehandler def _get_console_handler(self): '''返回一个输出到终端日志handler''' # 1. 获取一个输出到终端日志handler console_handler=logging.StreamHandler(sys.stdout) #2.设置日志格式 console_handler.setFormatter(self.formatter) #3.返回handler return console_handler @property def logger(self): return self._logger # 初始化并配一个logger对象,达到单例的 # 使用时,直接导入logger就可以使用 logger=Logger().logger if __name__ == '__main__': logger.debug("调试信息") logger.info("状态信息") logger.warning("警告信息") logger.error("错误信息") 2022-12-17 19:52:41 日志.py [line:53] INFO: 状态信息 2022-12-17 19:52:41 日志.py [line:54] WARNING: 警告信息 2022-12-17 19:52:41 日志.py [line:55] ERROR: 错误信息
我在从代理IP网站上抓取代理IP
和 检验代理IP
时候, 为了不容易不服务器识别为是一个爬虫, 我们最好提供随机的User-Agent请求头.
获取随机User-Agent的请求头
步骤:
- 1. 准备User-Agent的列表
2. 实现一个方法, 获取随机User-Agent的请求头
代码:
import random user_agent=[ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)", "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)", "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20", "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1", "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11", "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10" ] def get_request_header(): return { "User-Agent":random.choice(user_agent), "Connection":"keep-alive", "Cache-Control":"max-age=0", "Upgrade-Insecure-Requests":"1", "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding":"gzip,deflate", "Accept-Language":'zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7', } if __name__ == '__main__': print(get_request_header()) print(get_request_header())
检查代理IP的速度,支持协议类型,以及匿名程度
匿名程度检查:
- 对 `http://httpbin.org/get` 或 `https://httpbin.org/get` 发送请求
- 如果 `origin` 中有','分割的两个IP就是透明代理IP
- 如果 `headers` 中包含 `Proxy-Connection` 说明是匿名代理IP
- 否则就是高匿代理IP
检查代理IP协议类型
如果 `http://httpbin.org/get` 发送请求可以成功, 说明支持http协议
如果 `https://httpbin.org/get` 发送请求可以成功, 说明支持https协议
import requests import time import json from utils import http import settings from domain import Proxy from utils.log import logger def check_proxy(proxy): ''' 检测代理协议类型, 匿名程度 :param :return:(协议: http和https:2,https:1,http:0, 匿名程度:高匿:0,匿名: 1, 透明:0 , 速度, 单位s ) ''' # 根据proxy对象构造, 请求使用的代理 proxies = { 'http': "http://{}:{}".format(proxy.ip, proxy.port), 'https':"https://{}:{}".format(proxy.ip, proxy.port), } http, nick_type, http_speed = _check_http_proxy(proxies) https, nick_type, https_speed = _check_http_proxy(proxies, False) if http and https: # 如果http 和 https 都可以请求成功, 说明支持http也支持https, 协议类型为2 proxy.protocol = 2 proxy.nick_type = nick_type proxy.speed = http_speed elif http: # 如果只有http可以请求成功, 说明支持http协议, 协议类型为 0 proxy.protocol = 0 proxy.nick_type = nick_type proxy.speed = http_speed elif https: # # 如果只有https可以请求成功, 说明支持https协议, 协议类型为 1 proxy.protocol = 1 proxy.nick_type = nick_type proxy.speed = https_speed else: proxy.protocol = -1 proxy.nick_type = -1 proxy.speed = -1 logger.debug(proxy) return proxy def _check_http_proxy(proxies, isHttp=True): nick_type = -1 # 匿名程度 speed = -1 # 响应速度 if isHttp: test_url = 'http://httpbin.org/get' else: test_url = 'https://httpbin.org/get' try: start = time.time() r = requests.get(url=test_url, headers=http.get_request_header(), timeout=settings.TIMEOUT, proxies=proxies) if r.ok: # 计算响应速度, 保留两位小数 speed = round(time.time() - start, 2) # 把响应内容转换为字典 content = json.loads(r.text) # 获取请求头 headers = content['headers'] # 获取origin, 请求来源的IP地址 ip = content['origin'] # 获取请求头中 `Proxy-Connection` 如果有, 说明匿名代理 proxy_connection = headers.get('Proxy-Connection', None) if ',' in ip: # 如果 `origin` 中有','分割的两个IP就是透明代理IP nick_type = 2 # 透明 elif proxy_connection: # 如果 `headers` 中包含 `Proxy-Connection` 说明是匿名代理IP nick_type = 1 # 匿名 else: # 否则就是高匿代理IP nick_type = 0 # 高匿 return True, nick_type, speed else: return False, nick_type, speed except Exception as e: logger.exception(e) return False, nick_type, speed if __name__ == '__main__': proxy = Proxy('118.190.95.35', '9001') # proxy = Proxy('150.107.143.33', '9797') rs = check_proxy(proxy) print(proxy.protocol) print(rs)
用于对proxies
集合进行数据库的相关操作,实现对数据库增删改查相关操作
步骤:
- 1. 定义MongoPool类, 继承object
2. 实现初始化方法, 建立数据连接, 获取要操作的集合
3. 实现插入功能
4. 实现修改该功能
5. 实现保存功能, 如果不存在插入, 如果存在了就更新
6. 实现查询功能: 根据条件进行查询, 可以指定查询数量, 先分数降序, 速度升序排, 保证优质的代理IP在上面.
7. 实现删除代理: 根据代理的IP删除代理
8. 实现根据协议类型 和 要访问网站的域名, 获取代理IP列表
9. 实现根据协议类型 和 要访问完整的域名, 随机获取一个代理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 insert(self, proxy=None): """保存代理IP到数据库中""" if proxy: # 把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 save(self, proxy): """ 保存代理信息: 如果代理IP不存在就插入, 存在就更新""" # 1. 根据IP查询代理IP数量, 如果数量为0 , 说明该代理IP是新的, 否则该代理IP已经存在 count = self.proxies.count_documents({'_id': proxy.ip}) # 如果如果代理IP不存在就插入, 否则, 更新原来的代理IP信息 if count == 0: self.insert(proxy) else: self.update(proxy) 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对象 proxy = Proxy(item['ip'], item['port'], score=item['score'], protocol=item['protocol'], nick_type=item['nick_type'], speed=item['speed'], disable_domains=item['disable_domains']) # 把Proxy对象添加到结果集 results.append(proxy) # 返回查询的结果 return results def delete(self, proxy=None): """根据条件删除代理IP""" if proxy: self.proxies.delete_one({'_id': proxy.ip}) logger.info('删除代理: {}'.format(proxy)) else: logger.error("请传入要删除的代理") 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) # 创建MongoPool的单例对象 mongo = MongoPool() 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.save(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)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。