声明
本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除!由于本人水平有限,如有理解或者描述不准确的地方,还望各位大佬指教!!
搭建代理池思路
思路来源:崔庆才大佬的爬虫书
代理从何而来
用爬虫抓取各大代理网站
采集到的IP我们得将它存储起来
Mysql:它当然可以存储IP,可是它也有它的局限性,Mysql不能去重,因为有时我们采集到的IP可能一样,还有一个问题就是Mysql查询效率低
MongoDB:也可以存储IP,但它也不能去重
Redis:最合适,首先它的查询效率最高,还有良好的去重的集合(zset);zset有一个特性,他有一个分值(score),我们可以通过控制分值的高低就可以将稳定性高的IP取出来,从而提高免费IP的可用性
能验证IP的有效性
先将每个IP定一个初始分值(50),然后对每个IP都进行校验,如果这个IP可用那么就将这个IP的分值拉满(100),如果不可用就进行扣分(10),直到IP变成0分,就将这个IP删除。
思路理清了,接下来就是如何写程序了
采集:写爬虫抓取IP,将IP存储到Redis
校验:从Redis中取出IP,用IP简单发送一个请求,如果可以正常返回,证明该IP可用
提供:写api接口,将可用的IP提供给用户
如果我们按照单线程去完成上面的步骤,就有局限性,只有每次将IP提供给用户,才可以继续采集IP,而我们希望的是这三个步骤互不影响,不管采集、校验还是给用户提供IP,都应该是一直进行,在提供IP的时候也可以继续采集、校验。三个独立的程序,我们就可以用多进程。
Redis
- 连接Redis
- zset存储 判断IP存不存在,不存在就新增
- 查询所有IP(校验IP时要用到)
- 将分值拉满(IP可用)
- 将分值降低(IP不可用)
- 查询可用的IP 先给满分的,没有满分的给51-99分的
# redis的各种操作
from redis import Redis
from settings import *
class ProxyRedis:
# 连接redis
def __init__(self):
self.red = Redis(
host=REDIS_HOST,
port=REDIS_PORT,
db=REDIS_DB,
password=REDIS_PASSWORD,
decode_responses=True
)
# 存储ip
def add_proxy_ip(self, ip):
# 判断是否有ip
if not self.red.zscore(REDIS_KEY, ip):
self.red.zadd(REDIS_KEY, {ip: DEFAULT_SCORE})
print("采集到了IP地址了", ip)
else:
print("采集到了IP地址了", ip, "但是已经存在")
# 查询所有ip
def get_all_proxy(self):
return self.red.zrange(REDIS_KEY, 0, -1)
# 将分值拉满
def set_max_score(self, ip):
self.red.zadd(REDIS_KEY, {ip: MAX_SCORE})
# 降低分值
def reduce_score(self, ip):
# 查询分值
score = self.red.zscore(REDIS_KEY, ip)
# 如果有分值,扣分
if score > 0:
self.red.zincrby(REDIS_KEY, -10, ip)
else: # 分值没有则删除
self.red.zrem(REDIS_KEY, ip)
# 查询可用ip
def get_avail_proxy(self):
lis = []
ips = self.red.zrangebyscore(REDIS_KEY, MAX_SCORE, MAX_SCORE, 0, -1)
if ips:
lis.append(ips)
return lis
else:
ips = self.red.zrangebyscore(REDIS_KEY, DEFAULT_SCORE + 1, MAX_SCORE - 1, 0, -1)
if ips:
lis.append(ips)
return lis
else:
print("没有可用ip")
return None
采集ip
爬取这些网站很简单,基本都没有什么反爬,页面也都差不多,直接用xpath解析就可以得到想要的IP
from proxy_redis import ProxyRedis
import requests
import time
from lxml import etree
from setting import *
from urllib.parse import urljoin
import urllib3
urllib3.disable_warnings()
headers = {"user-agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'}
# 采集代理
def get_ip(red, url_info):
url = url_info['url']
resp = requests.get(url, headers=headers, verify=False, timeout=10)
resp.encoding = 'utf-8'
tree = etree.HTML(resp.text)
trs = tree.xpath(url_info['list_xpath'])
for tr in trs:
ip = tr.xpath(url_info['ip_xpath']) # ip地址
port = tr.xpath(url_info['port_xpath']) # 端口
if not ip:
continue
ip = ip[0].replace('\t', '').replace('\n', '').replace(' ', '')
port = port[0].replace('\t', '').replace('\n', '').replace(' ', '')
proxy_ip = ip + ":" + port
red.add_proxy_ip(proxy_ip, url_info['name']) # 增加ip地址
def run():
red = ProxyRedis() # 创建redis存储
while True:
try:
for url_info in url_infos:
get_ip(red, url_info) # 采集代理
get_xiaoshu(red)
get_66(red)
get_proxylist(red)
get_zdaye(red)
except Exception as e:
logger.error(f"抓取ip出错了--{e}")
time.sleep(300) # 每分钟跑一次
if __name__ == '__main__':
run()
校验ip可用性
- 查询所有的IP
- 每一个IP都发送一个请求,可用分值拉满,不用可扣分
这里如果我们采集的IP比较多的话,用单线程就比较慢了,所以为了提高效率,这里我采用协程
from proxy_redis import ProxyRedis
from settings import *
import asyncio
import aiohttp
import time
async def verify_one(ip, sem, red):
print(f"开始检测{ip}")
timeout = aiohttp.ClientTimeout(total=10) # 设置超时时间,超过10秒就报错
try:
async with sem:
async with aiohttp.ClientSession() as session:
async with session.get("http://www.baidu.com/", proxy="http://" + ip, timeout=timeout) as resp: # 简单发送一个请求
page_source = await resp.text()
if resp.status in [200, 302]: # 验证状态码
# 将分值拉满
red.set_max_score(ip)
print(f"检测到{ip}是可用的")
else:
red.reduce_score(ip)
print(f"检测到{ip}是不可用的, 扣10分")
except Exception as E:
print("ip检验时出错了", E)
red.reduce_score(ip)
print(f"检测到{ip}是不可用的, 扣10分")
async def main(red):
# 查询全部ip
all_proxy = red.get_all_proxy()
sem = asyncio.Semaphore(SEM_COUNT) # 控制并发量
tasks = []
for ip in all_proxy:
tasks.append(asyncio.create_task(verify_one(ip, sem, red)))
if tasks:
await asyncio.wait(tasks)
def run():
red = ProxyRedis()
time.sleep(10)
while True:
try:
asyncio.run(main(red))
time.sleep(100)
except Exception as e:
print("校验时报错了", e)
time.sleep(100)
if __name__ == '__main__':
run()
使用flask写接口
from proxy_pool import proxy_redis
from flask import Flask, request, jsonify
import random
app = Flask(__name__)
red = proxy_redis.ProxyRedis()
@app.route("/get_ip", methods=["GET"])
def dispose():
ip_list = red.get_avail_proxy()
if ip_list:
return random.choice(ip_list) # 返回给客户端
else:
return '没有可用ip'
if __name__ == '__main__':
app.run(host="0.0.0.0", port=3000)
**启动进程**
from ip_api import run as api_run
from ip_collection import run as col_run
from ip_verify import run as ver_run
from multiprocessing import Process
def run():
# 启动三个进程
p1 = Process(target=api_run)
p2 = Process(target=col_run)
p3 = Process(target=ver_run)
p1.start()
p2.start()
p3.start()
if __name__ == '__main__':
run()
配置文件
# 配置文件
# proxy_redis
# redis主机ip地址
REDIS_HOST = "127.0.0.1"
# redis端口号
REDIS_PORT = 6379
# redis数据库编号
REDIS_DB = 2
# redis的密码
REDIS_PASSWORD = "123456"
# redis的key
REDIS_KEY = "proxy_ip"
# 默认的ip分值
DEFAULT_SCORE = 50
# 满分
MAX_SCORE = 100
# ip_verify
# 一次检测ip的数量
SEM_COUNT = 30
代理池获取链接:https://www.aliyundrive.com/s/Yi17DfjWUAt