【Python3爬虫】教你怎么利用免费代理搭建代理池

1、写在前面

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

 

2、目标分析

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

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

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

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

 

3、具体实现

一、存储模块

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

对于新添加到数据库中的代理,设置的初始分数为10,添加以后会进行一次测试,若是可用就把分数改成100代表可用,若是测试的结果是不可用就把分数减1,当分数减到0后就从代理池中移除。这么作的意义在于一次测试不可用并不能表明这个代理彻底不可用,有可能在以后的某次测试中是可用的,这样咱们就减少了将一个本来可用的代理从代理池中移除出去的几率。并发

当咱们想要从代理池中获取一个代理的时候,优先从分数为100的代理中随机获取,若是一个100分的代理都没有,则对全部代理进行排序,而后随机获取一个代理。因为咱们使用的是随机获取,这样就能保证代理池中的全部代理都有被获取的可能性。app

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

 1 """
 2 Version: Python3.5  3 Author: OniOn  4 Site: http://www.cnblogs.com/TM0831/  5 Time: 2019/2/12 14:54  6 """
 7 import redis  8 import random  9 
 10 MAX_SCORE = 100  # 最高分
 11 MIN_SCORE = 0  # 最低分
 12 INITIAL_SCORE = 10  # 初始分数
 13 REDIS_HOST = "localhost"
 14 REDIS_PORT = 6379
 15 
 16 
 17 class RedisClient:  18     def __init__(self):  19         self.db = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0)  20         self.key = "proxies"
 21 
 22     def add(self, proxy, score=INITIAL_SCORE):  23         """
 24  将代理添加到代理池中  25  :param proxy: 代理  26  :param score: 分数  27  :return:  28         """
 29         if not self.is_exist(proxy):  30  self.db.zadd(self.key, proxy, score)  31 
 32     def is_exist(self, proxy):  33         """
 34  判断代理池中是否存在该代理  35  :param proxy: 代理  36  :return: True or False  37         """
 38         if self.db.zscore(self.key, proxy):  39             return True  40         else:  41             return False  42 
 43     def random(self):  44         """
 45  获取有效代理,先获取最高分代理,若是不存在,则按分数排名而后随机获取  46  :return: 代理  47         """
 48         result = self.db.zrangebyscore(self.key, MAX_SCORE, MAX_SCORE)  49         if len(result):  50             return random.choice(result)  51         else:  52             result = self.db.zrangebyscore(self.key, MIN_SCORE, MAX_SCORE)  53             if len(result):  54                 return random.choice(result)  55             else:  56                 print("代理池已空!")  57 
 58     def decrease(self, proxy):  59         """
 60  代理分数减1分,若小于最低分,则从代理池中移除  61  :param proxy:  62  :return:  63         """
 64         if self.is_exist(proxy):  65             score = self.db.zscore(self.key, proxy)  66             if score > MIN_SCORE:  67                 score -= 1
 68  self.db.zadd(self.key, proxy, score)  69             else:  70  self.delete(proxy)  71 
 72     def max(self, proxy):  73         """
 74  将代理分数设置为最高分  75  :param proxy: 代理  76  :return:  77         """
 78         if self.is_exist(proxy):  79  self.db.zadd(self.key, proxy, MAX_SCORE)  80 
 81     def delete(self, proxy):  82         """
 83  从代理池中移除该代理  84  :param proxy: 代理  85  :return:  86         """
 87         if self.is_exist(proxy):  88  self.db.zrem(self.key, proxy)  89 
 90     def all(self):  91         """
 92  获取代理池中的全部代理  93  :return:  94         """
 95         if self.count():  96             return self.db.zrange(self.key, MIN_SCORE, MAX_SCORE)  97 
 98     def count(self):  99         """
100  获取代理池中代理数量 101  :return: 102         """
103         return self.db.zcard(self.key)

二、爬取模块

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

 1 """
 2 Version: Python3.5  3 Author: OniOn  4 Site: http://www.cnblogs.com/TM0831/  5 Time: 2019/2/12 15:07  6 """
 7 import requests  8 from lxml import etree  9 from fake_useragent import UserAgent 10 
11 
12 # 设置元类
13 class CrawlMetaClass(type): 14     def __new__(cls, name, bases, attrs): 15         attrs['__CrawlFuncCount__'] = 0 16         attrs['__CrawlFunc__'] = [] 17         for k, v in attrs.items(): 18             if 'crawl_' in k: 19                 attrs['__CrawlFunc__'].append(k) 20                 attrs['__CrawlFuncCount__'] += 1
21         # attrs['__CrawlFuncCount__'] = count
22         return type.__new__(cls, name, bases, attrs) 23 
24 
25 class Crawler(object, metaclass=CrawlMetaClass): 26     def __init__(self): 27         self.proxies = []  # 代理列表
28         ua = UserAgent()  # 使用随机UA
29         self.headers = { 30             "UserAgent": ua.random 31  } 32 
33     def get_proxies(self, callback): 34         """
35  运行各个代理爬虫 36  :param callback: crawl函数名称 37  :return: 38         """
39         for proxy in eval("self.{}()".format(callback)): 40             print("成功获取代理:", proxy) 41  self.proxies.append(proxy) 42         return self.proxies 43 
44     def crawl_kdd(self): 45         """
46  快代理爬虫 47  :return: 48         """
49         urls = ["https://www.kuaidaili.com/free/inha/{}/".format(i) for i in range(1, 4)] 50         for url in urls: 51             res = requests.get(url, headers=self.headers) 52             try: 53                 et = etree.HTML(res.text) 54                 ip_list = et.xpath('//*[@data-title="IP"]/text()') 55                 port_list = et.xpath('//*[@data-title="PORT"]/text()') 56                 for ip, port in zip(ip_list, port_list): 57                     yield ip + ":" + port 58             except Exception as e: 59                 print(e) 60 
61     def crawl_89ip(self): 62         """
63  89IP爬虫 64  :return: 65         """
66         urls = ["http://www.89ip.cn/index_{}.html".format(i) for i in range(1, 4)] 67         for url in urls: 68             res = requests.get(url, headers=self.headers) 69             try: 70                 et = etree.HTML(res.text) 71                 ip_list = et.xpath('//*[@class="layui-table"]/tbody/tr/td[1]/text()') 72                 port_list = et.xpath('//*[@class="layui-table"]/tbody/tr/td[2]/text()') 73                 ip_list = [i.strip() for i in ip_list] 74                 port_list = [i.strip() for i in port_list] 75                 for ip, port in zip(ip_list, port_list): 76                     yield ip + ":" + port 77             except Exception as e: 78                 print(e) 79 
80     def crawl_xc(self): 81         """
82  西刺代理爬虫 83  :return: 84         """
85         url = "https://www.xicidaili.com/?t=253"
86         res = requests.get(url, headers=self.headers) 87         try: 88             et = etree.HTML(res.text) 89             ip_list = et.xpath('//*[@id="ip_list"]/tr[3]/td[2]/text()') 90             port_list = et.xpath('//*[@id="ip_list"]/tr[3]/td[3]/text()') 91             for ip, port in zip(ip_list, port_list): 92                 yield ip + ":" + port 93         except Exception as e: 94             print(e)

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

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

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

 1 """
 2 Version: Python3.5  3 Author: OniOn  4 Site: http://www.cnblogs.com/TM0831/  5 Time: 2019/2/14 12:19  6 """
 7 from ProxyPool.crawl import Crawler  8 from ProxyPool.pool import RedisClient  9 
10 
11 class GetProxy: 12     def __init__(self): 13         self.crawler = Crawler() 14         self.redis = RedisClient() 15 
16     def get_proxy(self): 17         """
18  运行爬虫爬取代理 19  :return: 20         """
21         print("[INFO]Crawl Start...") 22         count = 0 23         for callback_label in range(self.crawler.__CrawlFuncCount__): 24             callback = self.crawler.__CrawlFunc__[callback_label] 25             # 获取代理
26             proxies = self.crawler.get_proxies(callback) 27             for proxy in proxies: 28  self.redis.add(proxy) 29             count += len(proxies) 30         print("这次爬取的代理数量为:{}".format(count)) 31         print("[INFO]Crawl End...\n\n")

三、测试模块

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

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

 1 """
 2 Version: Python3.5  3 Author: OniOn  4 Site: http://www.cnblogs.com/TM0831/  5 Time: 2019/2/14 14:24  6 """
 7 import time  8 import random  9 import requests 10 from fake_useragent import UserAgent 11 from ProxyPool.crawl import Crawler 12 from ProxyPool.pool import RedisClient 13 
14 
15 class TestProxy: 16     def __init__(self): 17         self.crawler = Crawler() 18         self.redis = RedisClient() 19         ua = UserAgent()  # 使用随机UA
20         self.headers = { 21             "UserAgent": ua.random 22  } 23 
24     def test(self): 25         """
26  测试函数,测试代理池中的代理 27  :return: 28         """
29         proxy_list = self.redis.all() 30         proxy_list = [i.decode('utf-8') for i in proxy_list]  # 字节型转字符串型
31 
32         print("[INFO]Test Start...") 33         for proxy in proxy_list: 34  self.request(proxy) 35         print("[INFO]Test End...\n\n") 36 
37     def request(self, proxy): 38         """
39  测试请求函数 40  :param proxy: 41  :return: 42         """
43         print("当前测试代理:{} 该代理分数为:{}".format(proxy, self.redis.db.zscore(self.redis.key, proxy))) 44         time.sleep(random.randint(1, 4)) 45         try: 46             url = "https://www.baidu.com/"
47             proxies = { 48                 "https": "https://" + proxy 49  } 50             res = requests.get(url, headers=self.headers, proxies=proxies, timeout=5) 51 
52             if res.status_code == 200: 53                 print("代理可用,分数设置为100") 54  self.redis.max(proxy) 55             else: 56                 print("错误的请求状态码,分数减1") 57  self.redis.decrease(proxy) 58         except: 59             print("代理请求失败,分数减1") 60             self.redis.decrease(proxy)

 

4、运行程序

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

 1 """
 2 Version: Python3.5  3 Author: OniOn  4 Site: http://www.cnblogs.com/TM0831/  5 Time: 2019/2/14 14:26  6 """
 7 from ProxyPool.pool import RedisClient  8 from ProxyPool.get import GetProxy  9 from ProxyPool.test import TestProxy 10 
11 
12 class Main: 13     def __init__(self): 14         self.gp = GetProxy() 15         self.tp = TestProxy() 16         self.db = RedisClient() 17 
18     def run(self): 19         """
20  运行的主函数,先爬取代理,而后测试,最后获取一个有效代理 21  :return: 22         """
23  self.gp.get_proxy() 24  self.tp.test() 25         proxy = self.db.random() 26         proxy = proxy.decode('utf-8') 27         print("从代理池中取出的代理为:{}".format(proxy)) 28 
29 
30 if __name__ == '__main__': 31     m = Main() 32     m.run()

运行结果截图以下:

爬取完毕,开始测试

测试完毕,获取代理

 

完整代码已上传到GitHub

相关文章
相关标签/搜索