一、写在前面

有时候你的爬虫刚开始的时候可以正常运行,能够正常的爬取数据,但是过了一会,却出现了一个“403 Forbidden",或者是”您的IP访问频率太高“这样的提示,这就意味着你的IP被ban了,好一点的情况是过一段时间你就能继续爬取了,坏一点的情况就是你的IP已经进入别人的黑名单了,然后你的爬虫就GG了。怎么办呢?我们可以通过设置代理来解决,付费代理的效果自然不必多说,但是对于学习阶段的人来说,我觉得爬取网上的免费代理来用是一个更好的选择,而这一篇博客就将教你怎么利用免费代理搭建属于你自己的代理池。

二、目标分析

要搭建一个代理池,需要三个模块:存储模块、爬取模块和测试模块。

存储模块:负责存储我们爬取下来的代理,首先我们需要保证这些代理不能有重复的,然后我们还要对代理是否可用进行标记,这里可以使用Redis数据库的SortedSet(有序集合)进行存储。

爬取模块:负责对一些提供免费代理的网站进行爬取,代理的形式是IP+端口,爬取下来之后保存到数据库里。

测试模块:负责对代理池中的代理的可用性进行测试,由于测试出错不一定就表明代理不可用,可能是因为网络问题或者请求超时等等,所以我们可以设置一个分数标识,100分标识可用,分数越低标识可用性越低,当分数低于一个阈值之后,就从代理池中移除。

三、具体实现

1、存储模块

这里使用的是Redis的有序集合。Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员,不同的是每个元素都会关联一个double类型的分数,Redis正是通过分数来为集合中的成员进行从小到大的排序。这样我们保存到数据库中的元素就是一个代理和一个分数,比如112.17.65.133:8060和100,这就表示112.17.65.133:8060这个代理是可用的。

对于新添加到数据库中的代理,设置的初始分数为10,添加之后会进行一次测试,如果可用就把分数改为100表明可用,如果测试的结果是不可用就把分数减1,当分数减到0后就从代理池中移除。这么做的意义在于一次测试不可用并不能代表这个代理完全不可用,有可能在之后的某次测试中是可用的,这样我们就减小了将一个原本可用的代理从代理池中移除出去的概率。

当我们想要从代理池中获取一个代理的时候,优先从分数为100的代理中随机获取,如果一个100分的代理都没有,则对所有代理进行排序,然后随机获取一个代理。由于我们使用的是随机获取,这样就能保证代理池中的所有代理都有被获取的可能性。

现在我们需要定义一个类来实现这个有序集合,还需要设置一些方法来实现添加代理、修改分数、获取代理等功能。具体代码如下:

 """
Version: Python3.5
Author: OniOn
Site: http://www.cnblogs.com/TM0831/
Time: 2019/2/12 14:54
"""
import redis
import random MAX_SCORE = 100 # 最高分
MIN_SCORE = 0 # 最低分
INITIAL_SCORE = 10 # 初始分数
REDIS_HOST = "localhost"
REDIS_PORT = 6379 class RedisClient:
def __init__(self):
self.db = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0)
self.key = "proxies" def add(self, proxy, score=INITIAL_SCORE):
"""
将代理添加到代理池中
:param proxy: 代理
:param score: 分数
:return:
"""
if not self.is_exist(proxy):
self.db.zadd(self.key, proxy, score) def is_exist(self, proxy):
"""
判断代理池中是否存在该代理
:param proxy: 代理
:return: True or False
"""
if self.db.zscore(self.key, proxy):
return True
else:
return False def random(self):
"""
获取有效代理,先获取最高分代理,如果不存在,则按分数排名然后随机获取
:return: 代理
"""
result = self.db.zrangebyscore(self.key, MAX_SCORE, MAX_SCORE)
if len(result):
return random.choice(result)
else:
result = self.db.zrangebyscore(self.key, MIN_SCORE, MAX_SCORE)
if len(result):
return random.choice(result)
else:
print("代理池已空!") def decrease(self, proxy):
"""
代理分数减1分,若小于最低分,则从代理池中移除
:param proxy:
:return:
"""
if self.is_exist(proxy):
score = self.db.zscore(self.key, proxy)
if score > MIN_SCORE:
score -= 1
self.db.zadd(self.key, proxy, score)
else:
self.delete(proxy) def max(self, proxy):
"""
将代理分数设置为最高分
:param proxy: 代理
:return:
"""
if self.is_exist(proxy):
self.db.zadd(self.key, proxy, MAX_SCORE) def delete(self, proxy):
"""
从代理池中移除该代理
:param proxy: 代理
:return:
"""
if self.is_exist(proxy):
self.db.zrem(self.key, proxy) def all(self):
"""
获取代理池中的所有代理
:return:
"""
if self.count():
return self.db.zrange(self.key, MIN_SCORE, MAX_SCORE) def count(self):
"""
获取代理池中代理数量
:return:
"""
return self.db.zcard(self.key)

2、爬取模块

爬取模块比较简单,就是定义一个Crawler类来对一些提供免费代理的网站进行爬取。具体代码如下:

 """
Version: Python3.5
Author: OniOn
Site: http://www.cnblogs.com/TM0831/
Time: 2019/2/12 15:07
"""
import requests
from lxml import etree
from fake_useragent import UserAgent # 设置元类
class CrawlMetaClass(type):
def __new__(cls, name, bases, attrs):
attrs['__CrawlFuncCount__'] = 0
attrs['__CrawlFunc__'] = []
for k, v in attrs.items():
if 'crawl_' in k:
attrs['__CrawlFunc__'].append(k)
attrs['__CrawlFuncCount__'] += 1
# attrs['__CrawlFuncCount__'] = count
return type.__new__(cls, name, bases, attrs) class Crawler(object, metaclass=CrawlMetaClass):
def __init__(self):
self.proxies = [] # 代理列表
ua = UserAgent() # 使用随机UA
self.headers = {
"UserAgent": ua.random
} def get_proxies(self, callback):
"""
运行各个代理爬虫
:param callback: crawl函数名称
:return:
"""
for proxy in eval("self.{}()".format(callback)):
print("成功获取代理:", proxy)
self.proxies.append(proxy)
return self.proxies def crawl_kdd(self):
"""
快代理爬虫
:return:
"""
urls = ["https://www.kuaidaili.com/free/inha/{}/".format(i) for i in range(1, 4)]
for url in urls:
res = requests.get(url, headers=self.headers)
try:
et = etree.HTML(res.text)
ip_list = et.xpath('//*[@data-title="IP"]/text()')
port_list = et.xpath('//*[@data-title="PORT"]/text()')
for ip, port in zip(ip_list, port_list):
yield ip + ":" + port
except Exception as e:
print(e) def crawl_89ip(self):
"""
89IP爬虫
:return:
"""
urls = ["http://www.89ip.cn/index_{}.html".format(i) for i in range(1, 4)]
for url in urls:
res = requests.get(url, headers=self.headers)
try:
et = etree.HTML(res.text)
ip_list = et.xpath('//*[@class="layui-table"]/tbody/tr/td[1]/text()')
port_list = et.xpath('//*[@class="layui-table"]/tbody/tr/td[2]/text()')
ip_list = [i.strip() for i in ip_list]
port_list = [i.strip() for i in port_list]
for ip, port in zip(ip_list, port_list):
yield ip + ":" + port
except Exception as e:
print(e) def crawl_xc(self):
"""
西刺代理爬虫
:return:
"""
url = "https://www.xicidaili.com/?t=253"
res = requests.get(url, headers=self.headers)
try:
et = etree.HTML(res.text)
ip_list = et.xpath('//*[@id="ip_list"]/tr[3]/td[2]/text()')
port_list = et.xpath('//*[@id="ip_list"]/tr[3]/td[3]/text()')
for ip, port in zip(ip_list, port_list):
yield ip + ":" + port
except Exception as e:
print(e)

可以看到几个爬虫的方法名称都是以crawl开头的,主要是为了方便,如果要添加新的爬虫就只用添加crawl开头的方法即可。

这里我写了爬取快代理、89IP代理和西刺代理的爬虫,都是用xpath进行解析,也都定义了一个生成器,然后用yield返回爬取到的代理。然后定义了一个get_proxies()方法,将所有以crawl开头的方法都调用一遍,获取每个方法返回的结果并生成一个代理列表,最后返回这个代理列表。那么如何获取crawl开头的方法呢?这里借用了元类来实现。首先定义一个类CrawlMetaClass,然后实现了__new__()方法,这个方法的第四个参数attrs里包含了类的一些属性,所以我们可以遍历attrs中包含的信息,就像遍历一个字典一样,如果方法名以crawl开头,就将其添加到__CrawlFunc__中,这样我们就能获取crawl开头的方法了。

我们已经定义好了爬取的方法了,但是还需要定义一个类来执行这些方法,这里可以定义一个GetProxy类来实现爬取代理并保存到代理池中,具体代码如下:

 """
Version: Python3.5
Author: OniOn
Site: http://www.cnblogs.com/TM0831/
Time: 2019/2/14 12:19
"""
from ProxyPool.crawl import Crawler
from ProxyPool.pool import RedisClient class GetProxy:
def __init__(self):
self.crawler = Crawler()
self.redis = RedisClient() def get_proxy(self):
"""
运行爬虫爬取代理
:return:
"""
print("[INFO]Crawl Start...")
count = 0
for callback_label in range(self.crawler.__CrawlFuncCount__):
callback = self.crawler.__CrawlFunc__[callback_label]
# 获取代理
proxies = self.crawler.get_proxies(callback)
for proxy in proxies:
self.redis.add(proxy)
count += len(proxies)
print("此次爬取的代理数量为:{}".format(count))
print("[INFO]Crawl End...\n\n")

3、测试模块

我们已经将代理成功爬取下来并保存到代理池中了,但是我们还需要对代理的可用性进行测试。测试的方法就是使用requests库设置代理并发送请求,如果请求成功并且返回的状态码是200的话,就表明这个代理是可用的,然后我们就要将该代理的分数设置为100,反之如果出现请求失败、请求超时或者返回的状态码不是200的话, 就要将该代理的分数减1,如果分数等于0了,就要从代理池中移除。

这里可以定义一个TestProxy类来实现,具体代码如下:

 """
Version: Python3.5
Author: OniOn
Site: http://www.cnblogs.com/TM0831/
Time: 2019/2/14 14:24
"""
import time
import random
import requests
from fake_useragent import UserAgent
from ProxyPool.crawl import Crawler
from ProxyPool.pool import RedisClient class TestProxy:
def __init__(self):
self.crawler = Crawler()
self.redis = RedisClient()
ua = UserAgent() # 使用随机UA
self.headers = {
"UserAgent": ua.random
} def test(self):
"""
测试函数,测试代理池中的代理
:return:
"""
proxy_list = self.redis.all()
proxy_list = [i.decode('utf-8') for i in proxy_list] # 字节型转字符串型 print("[INFO]Test Start...")
for proxy in proxy_list:
self.request(proxy)
print("[INFO]Test End...\n\n") def request(self, proxy):
"""
测试请求函数
:param proxy:
:return:
"""
print("当前测试代理:{} 该代理分数为:{}".format(proxy, self.redis.db.zscore(self.redis.key, proxy)))
time.sleep(random.randint(1, 4))
try:
url = "https://www.baidu.com/"
proxies = {
"https": "https://" + proxy
}
res = requests.get(url, headers=self.headers, proxies=proxies, timeout=5) if res.status_code == 200:
print("代理可用,分数设置为100")
self.redis.max(proxy)
else:
print("错误的请求状态码,分数减1")
self.redis.decrease(proxy)
except:
print("代理请求失败,分数减1")
self.redis.decrease(proxy)

四、运行程序

这里我定义了一个Main类来实现,过程为先爬取代理,然后对代理池中的代理进行测试,最后从代理池中获取一个可用代理。具体代码如下:

 """
Version: Python3.5
Author: OniOn
Site: http://www.cnblogs.com/TM0831/
Time: 2019/2/14 14:26
"""
from ProxyPool.pool import RedisClient
from ProxyPool.get import GetProxy
from ProxyPool.test import TestProxy class Main:
def __init__(self):
self.gp = GetProxy()
self.tp = TestProxy()
self.db = RedisClient() def run(self):
"""
运行的主函数,先爬取代理,然后测试,最后获取一个有效代理
:return:
"""
self.gp.get_proxy()
self.tp.test()
proxy = self.db.random()
proxy = proxy.decode('utf-8')
print("从代理池中取出的代理为:{}".format(proxy)) if __name__ == '__main__':
m = Main()
m.run()

运行结果截图如下:

完整代码已上传到GitHub

【Python3爬虫】教你怎么利用免费代理搭建代理池的更多相关文章

  1. python3爬虫系列19之反爬随机 User-Agent 和 ip代理池的使用

    站长资讯平台:python3爬虫系列19之随机User-Agent 和ip代理池的使用我们前面几篇讲了爬虫增速多进程,进程池的用法之类的,爬虫速度加快呢,也会带来一些坏事. 1. 前言比如随着我们爬虫 ...

  2. Python3爬虫实例 代理的使用

    现在爬虫越来越难了,一些网站会有相应的反爬虫措施,例如很多网站会检测某一段时间某个IP的访问次数,如果访问频率太快以至于看起来不像正常访客,它可能就会会禁止这个IP的访问. 所以我们需要设置一些代理服 ...

  3. 记一次企业级爬虫系统升级改造(六):基于Redis实现免费的IP代理池

    前言: 首先表示抱歉,春节后一直较忙,未及时更新该系列文章. 近期,由于监控的站源越来越多,就偶有站源做了反爬机制,造成我们的SupportYun系统小爬虫服务时常被封IP,不能进行数据采集. 这时候 ...

  4. 手把手教你如何利用 HeroKu 免费获取一个 Scrapyd 集群

    手把手教你如何利用 HeroKu 免费获取一个 Scrapyd 集群 本文原始地址:https://sitoi.cn/posts/48724.html 准备环境 一个 GitHub 的账号 一个 He ...

  5. 【Python3爬虫】用Python中的队列来写爬虫

    一.写在前面 当你看着你的博客的阅读量慢慢增加的时候,内心不禁有了些小激动,但是不得不吐槽一下--博客园并不会显示你的博客的总阅读量是多少.而这一篇博客就将教你怎么利用队列这种结构来编写爬虫,最终获取 ...

  6. 第三百三十五节,web爬虫讲解2—Scrapy框架爬虫—豆瓣登录与利用打码接口实现自动识别验证码

    第三百三十五节,web爬虫讲解2—Scrapy框架爬虫—豆瓣登录与利用打码接口实现自动识别验证码 打码接口文件 # -*- coding: cp936 -*- import sys import os ...

  7. Python获取免费的可用代理

    Python获取免费的可用代理 在使用爬虫多次爬取同一站点时,常常会被站点的ip反爬虫机制给禁掉,这时就能够通过使用代理来解决.眼下网上有非常多提供最新免费代理列表的站点.这些列表里非常多的代理主机是 ...

  8. Python爬虫开发【第1篇】【代理】

    1.简单的自定义opener() import urllib2 # 构建一个HTTPHandler 处理器对象,支持处理HTTP请求 http_handler = urllib2.HTTPHandle ...

  9. 《精通Python网络爬虫》|百度网盘免费下载|Python爬虫实战

    <精通Python网络爬虫>|百度网盘免费下载|Python爬虫实战 提取码:7wr5 内容简介 为什么写这本书 网络爬虫其实很早就出现了,最开始网络爬虫主要应用在各种搜索引擎中.在搜索引 ...

随机推荐

  1. Java元编程及其应用

    首先,我们且不说元编程是什么,他能做什么.我们先来谈谈生产力. 同样是实现一个投票系统,一个是python程序员,基于django-framework,用了半小时就搭建了一个完整系统,另外一个是标准的 ...

  2. noip2010 引水入城 bfs+贪心

    如果能够实现,每个河边的城市对应的控制区域一定是一条线段. 所以直接bfs每个河边的城市,贪心线段的右端点 #include<cstdio> #include<cstring> ...

  3. BZOJ_3585_mex && BZOJ_3339_Rmq Problem_主席树

    BZOJ_3585_mex && BZOJ_3339_Rmq Problem_主席树 Description 有一个长度为n的数组{a1,a2,...,an}.m次询问,每次询问一个区 ...

  4. Python Django 1.Hello Django

    #安装Djangopip install Django #==版本号#选择路径:D:#任意文件夹名 cd Django #罗列Django所提供的命令,其中startproject命令来创建项目 dj ...

  5. 【SAP业务模式】之STO(一):业务背景和前台操作

    所谓STO即两个关联公司之间的库存转储交易,一家公司发出采购订单向另一家公司做采购,然后在做发货.如此之后,两家公司有相应应收应付的票据,以及开票和发票校验等动作. STO分为一步法与两步法,因为一步 ...

  6. webpack Code Splitting浅析

    Code Splitting是webpack的一个重要特性,他允许你将代码打包生成多个bundle.对多页应用来说,它是必须的,因为必须要配置多个入口生成多个bundle:对于单页应用来说,如果只打包 ...

  7. 对抗明文口令泄露 —— Web 前端慢 Hash

    (更新:https://www.cnblogs.com/index-html/p/frontend_kdf.html ) 0x00 前言 天下武功,唯快不破.但在密码学中则不同.算法越快,越容易破. ...

  8. 安卓开发笔记(二十八):仿写IOS switch选择器控件实现,checkbox

    我们先来看看效果: 这里我们主要使用了github上的一个开源项目,配置起来比较方便,下面解释一下该如何使用:首先是:Gradle文件当中进行配置: dependencies { implementa ...

  9. Python练习:九九乘法表

    打印 n * n 的乘法表 #打印 9*9 乘法表 def Multiplication(n): # n - 定义打印的行数 max_len = len(str((n)**2)) #计算最大值的占位( ...

  10. SSRS报表服务随笔(rdl报表服务)-创建一个简单的报表

    这段时间一直在敲rdl报表,在国内的不这么留在,在国外的话,还是挺流行的,国内的话,这方面的资料很少很少,也踏过不少坑 先从SSRS了解起,SSRS全称 SQL Server Reporting Se ...