如今,‘人生苦短、我用Python’ 再非一句戏言........
Python.png
我们在爬取网上数据的时候经常会遇到IP被封的情况,解决这种问题我们无非就是替换IP,一般替换IP有两种方法:网上获取免费代理、购买代理。但是不管哪种方式获得代理,都不能保证100%的有效,因为一个IP可能被很多人使用,如果别人也使用了同样的代理访问网站,然后被封,那这个代理的使用价值就大打折扣了!或者代理服务器突然发生故障或者网络不给力,这些情况都会降低我们的开发效率。
所以,我们有必要提前对代理做个筛选,保留可用的代理,剔除被封掉的代理。在我们使用的时候直接从众多可用代理中选择一个即可。
本文将详细讲解Python的实战、本文中的demo使用到了Redis、Flask、asyncio、aiohttp、metaclass、requests、pyquery、redis-py等,想一步到位got源码的同学点击 demo 即可。
先简要介绍一下代理池的架构:
代理池架构.jpg代理池一共分为四个模块:获取模块、存储模块、检测模块、接口模块
- 获取模块
该模块定时从代理网站获取代理,然后保存到数据库中备用 - 存储模块
该模块可以说是个中心模块,把其他模块串联起来,使用的是Redis集合 - 检测模块
定时从存储模块中取出代理检测,根据结果对代理进行重新标识 - 接口模块
接口模块通过Web API提供服务接口, 接口通过连接数据库并通过Web形式返回可用的代理
我们分别介绍一下几个模块的实现
-
获取模块
该模块的实现相对简单,先上码(上马)
from pyquery import PyQuery as pq
import re
class ProxyMetaclass(type):
def __new__(cls, name, bases, attrs):
count = 0
attrs['__CrawlFunc__'] = []
for k, v in attrs.items():
if 'crawl_' in k:
attrs['__CrawlFunc__'].append(k)
count += 1
attrs['__CrawlFuncCount__'] = count
return type.__new__(cls, name, bases, attrs)
# eval()函数的使用
# 字符串拼接
class Crawler(object, metaclass=ProxyMetaclass):
def get_proxies(self, callback):
"""
获取抓取的代理
:param callback: 抓取代理的方法
:return:
"""
proxies = []
for proxy in eval("self.{}()".format(callback)):
print("成功获取代理: " + proxy)
proxies.append(proxy)
return proxies
# 66代理
# join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串
def crawl_proxy66(self, page_count=6):
"""
获取66代理
:param page_count: 指定页码
:return:
"""
start_url = 'http://www.66ip.cn/{}.html'
urls = [start_url.format(page) for page in range(1, page_count + 1)]
for url in urls:
html = get_page(url)
if html:
doc = pq(html)
trs = doc('.containerbox table tr:gt(0)').items()
for tr in trs:
ip = tr.find('td:nth-child(1)').text()
port = tr.find('td:nth-child(2)').text()
yield ':'.join([ip, port])
该模块中用到了元类metaclas、pyquery。对python中的metaclass不熟悉的同学可以点进去看看。这里为了缩短篇幅,代码中只包含一个网站的抓取,其它网站的代理抓取可以通过demo查看。
该类中定义了一个ProxyMetaclass元类,用来聚集抓取方法。重写了new方法,里面的四个参数是固定的,其中attrs这个属性包含了类的所有属性。通过便利该属性,获取到当前类中的所有抓取方法,然后将方法加入到CrawlFunc这个属性中,这样就可以在其他地方获取到方法并调用。如果想扩展的话可以自定义方法,并以crawl开头即可。
此类中还定义了一个调用方法的方法,即:get_proxies,该方法的参数是方法名字,使用eval()函数,将字符串转变成方法调用,获取代理之后,代理列表作为参数返回即可。
既然定义了Crawler类用来抓取代理,接下来就再定义一个Getter类,用来动态调用所有以crawl开头的方法,然后获取抓取到的代理,保存到数据库中。
from proxypool.tester import Tester
from proxypool.db import RedisClient
from proxypool.crawler import Crawler
from proxypool.setting import *
import sys
class Getter():
def __init__(self):
self.redis = RedisClient()
self.crawler = Crawler()
def is_over_threshold(self):
"""
判断是否达到了代理池限制
"""
if self.redis.count() >= POOL_UPPER_THRESHOLD:
return True
else:
return False
def run(self):
print('获取器开始执行')
if not self.is_over_threshold():
for callback_label in range(self.crawler.__CrawlFuncCount__):
callback = self.crawler.__CrawlFunc__[callback_label]
# 获取代理
proxies = self.crawler.get_proxies(callback)
sys.stdout.flush()
for proxy in proxies:
self.redis.add(proxy)
Getter类就是一个获取类,它定义了代理池能容纳的最大数量POOL_UPPER_THRESHOLD,也定义了is_over_threshold()方法判断是否达到了最大容量。该方法里面调用了RedisClient的count方法获取数据库中的代理数量进行判断。
接着定义了run()方法,该方法首先判断了代理池是否达到了最大值,然后再调用获取方法,得到的代理加入数据库,这样获取模块的工作就完成了。
-
存储模块
为了缩减篇幅,该模块中的方法实现暂时pass,pass掉也不耽误大家对整个流程的理解,如果感觉不顺眼的同学,可以结合着demo进行查看。
class RedisClient(object):
def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD):
""" 初始化 键值存储数据库 """
pass
def exists(self, proxy):
""" 判断有序集合中是否存在代理 """
pass
def add(self, proxy, score=INITIAL_SCORE):
""" 添加代理,并初始化分数 """
pass
def batch(self, start, stop):
""" 批量获取代理 """
pass
def all(self):
""" 获取全部代理 """
pass
def count(self):
""" 获取数量 """
pass
def max(self, proxy):
""" 如果代理可用,将代理分数设置为最高 """
pass
def decrease(self, proxy):
""" 将代理分数减去一分,小于最小值,则删除 """
pass
def random(self):
""" 随机获取有效代理,首先尝试获取最高分代理,如果不存在,按照排名获取 """
pass
这个模块中主要是对Redis的使用,具体使用的是Redis中的有序集合,想进一步了解Redis的同学可以自行百度。demo中也有各个方法的详细说明,再次不再细说。
本篇文章暂时介绍以上两个模块,剩下的模块定时更新。
网友评论