Python爬虫实战——搭建自己的IP代理池[通俗易懂]

2022-08-26 12:58:54 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

如今爬虫越来越多,一些网站网站加强反爬措施,其中最为常见的就是限制IP,对于爬虫爱好者来说,能有一个属于自己的IP代理池,在爬虫的道路上会减少很多麻烦

环境参数

工具

详情

服务器

Ubuntu

编辑器

Pycharm

第三方库

requests、bs4、redis

搭建背景

之前用Scrapy写了个抓取新闻网站的项目,今天突然发现有一个网站的内容爬不下来了,通过查看日志发现是IP被封,于是就有了这篇文章。

思路

一般出售IP代理的都会提供一些免费代理,既然是免费的就不要浪费,我们只要把免费的代理爬下了,及时维护和更新就可以把免费的变成我们自己的代理池

编写爬虫

搜索免费代理会有很多结果,一般情况大部分都可以使用,这里以其中一家代理为例,打开代理网站以后,首先通过浏览器查看代码,然后分析代码开始编写爬虫

网站源代码

代码语言:javascript复制
<!--其中一条数据-->
...
<tr class="success">
	<td class="ip"><div style="display:inline-block;"></div>
	<span style="display:inline-block;">59</span><span style="display:inline-block;">.1</span><div style="display:inline-block;"></div><p style="display:none;">0</p><span>0</span><span style="display:inline-block;"></span><span style="display:inline-block;">8.</span><div style="display:inline-block;">12</div><span style="display:inline-block;">5</span><p style="display:none;"></p><span></span><p style="display:none;"></p><span></span><div style="display:inline-block;">.2</div><div style="display:inline-block;">41</div>:<span class="port GEGEA">8080</span></td>
<td><a title="高匿代理IP" style="color:red;" class="href">高匿</a></td>
<td><a title="http代理IP" class="href">http</a></td>
<td><a title="中国代理IP" class="href">中国</a>&nbsp;&nbsp;
<a title="北京代理IP" class="href">北京</a>&nbsp;&nbsp;
<a title="北京代理IP" class="href">北京</a> </td><td><a title="方正宽带代理IP" class="href">方正宽带</a></td>
<td>2.786 秒</td><td>7分钟前</td><td style="color: green; font-weight: bold;">11天</td></tr>
....

通过上面一条数据可以看出,提供者为防止网站被爬取还是做了一些防范措施,但是我们可以使用正则表达式取出IP地址和端口号。 使用正则表达式的时候我们一般会有两种思路

  • 1.提取数字和点.
  • 2.过滤html标签,保留我们想要的数字和点

这里我们以第二种方法为例

代码语言:javascript复制
soup = BeautifulSoup(html, 'html.parser')
data = soup.find('td', class_='ip')
res=re.compile('<p.*?/p>|<.*?>',re.S)
proxy=re.sub(res, '', str(data))
print(proxy)

# 59.108.125.241:8080

这个时候IP地址和端口号就提取出来了,当你把整个网页的代理地址都提取出来以后,你会发现没有一个可以使用的。

这是为什么呢?难道是代理商提供的免费代理都是垃圾,其实不然,细心的你可能会发现你匹配的端口和他们官网显示的端口号不一样,很显然他们的端口号是通过js动态加载的,遇到这种情况,我们一般也会想到2种解决方案

  • 使用selenium
  • 破解js 如果加密方式复杂、js文件很多,无从下手时可以使用selenium,好在我们今天爬取的这个网站js文件不是很多,通过打断点,很容易定位到我们要解密的js文件,下面我就分享一下的我解决方法

破解js

  • 找到对应的js文件

可以给网站中的每个js文件打断点,一步步调试找出影响数据的js文件,通过调试我找到这样一个文件

代码语言:javascript复制
eval(function(p,a,c,k,e,d){ 
   e=function(c){ 
   return(c<a?"":e(parseInt(c/a))) ((c=c%a)>35?String.fromCharCode(c 29):c.toString(36))};if(!''.replace(/^/,String)){ 
   while(c--)d[e(c)]=k[c]||e(c);k=[function(e){ 
   return d[e]}];e=function(){ 
   return'\w '};c=1;};while(c--)if(k[c])p=p.replace(new RegExp('\b' e(c) '\b','g'),k[c]);return p;}('1M(17(p,a,c,k,e,r){e=17(c){18(c<a?'':e(1w(c/a))) ((c=c%a)>1s?1b.1r(c 1q):c.1v(1u))};19(!''.1a(/^/,1b)){1c(c--)r[e(c)]=k[c]||e(c);k=[17(e){18 r[e]}];e=17(){18'\\w '};c=1};1c(c--)19(k[c])p=p.1a(1t 1y('\\b' e(c) '\\b','g'),k[c]);18 p}('i h$=[\'\\E\\n\\x\\s\\j\',"\\l\\m\\v\\o","\\o\\j\\G\\p","\\r\\q\\H\\l\\I\\J\\K",\'\\M\',"\\m\\j\\j\\s",\'\\v\\p\\m\\k\\k\',"\\k\\n\\p\\r\\j","\\O","","\\p\\l\\q\\Q\\j\\o","\\n\\R\\k\\o",\'\\S\\T\\V\\z\\A\\B\\C\\D\\u\\F\',"\\n\\m\\s\\k\\l\\u\\q\\j","\\16\\x\\r\\q",\'\'];$(y(){$(h$[0])[h$[1]](y(){i a=$(t)[h$[2]]();L(a[h$[3]](h$[4])!=-w){N};i b=$(t)[h$[5]](h$[6]);P{b=(b[h$[7]](h$[8]))[w];i c=b[h$[7]](h$[9]);i d=c[h$[10]];i f=[];U(i g=W;g<d;g  ){f[h$[11]](h$[12][h$[3]](c[g]))};$(t)[h$[2]](X[h$[13]](f[h$[14]](h$[15]))>>Y)}Z(e){}})})',1A,1B,'|||||||||||||||||1C|1x|1z|1p|1h|1i|1d|1e|1f|1m|1n|1o|1g|1k|1l|1j|1W|17|1X|1Y|1V|1S|1T|1U|1Z|23|25|24|20|21|19|22|18|1R|1H|1I|1J|1G|1D|1E|1F|1O|1P|1Q|1N|||||||1K'.1L('|'),0,{}))',62,130,'|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||function|return|if|replace|String|while|x70|x68|x6c|this|x65|x61|0x1|x49|x63|x6e|x69|x72|x73|29|fromCharCode|35|new|36|toString|parseInt|var|RegExp|x74|62|69|_|x42|for|x43|x41|try|x67|x75|x6a|split|eval|catch|0x0|window|0x3|x20|x47|x48|x2e|x46|x6f|x44|x45|x5a|x4f|x66|x2a|x6d|x78|x64'.split('|'),0,{ 
   }))
  • 解密js文件

很显然上面这个文件是加密压缩过的,通过在线解密工具,两次解密以后我们得到这样一个方法,有点js基础的同学应该能看的懂,但是还是不够直观,因为这个方法首先定义了一个数组,每个变量都是用数组切片的方式代替,所以一眼很难看出加密方式

代码语言:javascript复制
var _$ = ['x2ex70x6fx72x74', "x65x61x63x68", "x68x74x6dx6c", "x69x6ex64x65x78x4fx66", 'x2a', "x61x74x74x72", 'x63x6cx61x73x73', "x73x70x6cx69x74", "x20", "", "x6cx65x6ex67x74x68", "x70x75x73x68", 'x41x42x43x44x45x46x47x48x49x5a', "x70x61x72x73x65x49x6ex74", "x6ax6fx69x6e", ''];
$(function() { 
   
	$(_$[0])[_$[1]](function() { 
   
		var a = $(this)[_$[2]]();
		if (a[_$[3]](_$[4]) != -0x1) { 
   
			return
		};
		var b = $(this)[_$[5]](_$[6]);
		try { 
   
			b = (b[_$[7]](_$[8]))[0x1];
			var c = b[_$[7]](_$[9]);
			var d = c[_$[10]];
			var f = [];
			for (var g = 0x0; g < d; g  ) { 
   
				f[_$[11]](_$[12][_$[3]](c[g]))
			};
			$(this)[_$[2]](window[_$[13]](f[_$[14]](_$[15])) >> 0x3)
		} catch (e) { 
   }
	})
})

通过对数组的拆分,你会发现上面方法的核心内容可以简化成这样

代码语言:javascript复制
    var f = []; 
    var c="GEGEA".split(""); 
	for (var g = 0; g < c.length; g  ) { 
   
		f.push('ABCDEFGHIZ'.indexOf(c[g]))
	};

我来解释一下这个代码片段,首先”GEGEA”这个值是怎么来的? 这个值不是固定的,而是网页源码中class=‘port GEGEA’ port的同级class,获取到这个class以后,先把它转为数组,判断数组中的每个元素在’ABCDEFGHIZ’中的位置,会得到一个类似这样的数组[6, 4, 6, 4, 0],再把这个新数组转为字符串,然后位移,就可以得到真实的端口号,所以可以把解密函数简化成这样

代码语言:javascript复制
    // 定义一个数组,用于记录class在'ABCDEFGHIZ'出现的位置
    var f = []; 
    // 把class转为一个数组
    var c="GEGEA".split(""); 
    // 根据数组的长度记录数组中每个元素在'ABCDEFGHIZ'出现的位置
	for (var g = 0; g < c.length; g  ) { 
   
		f.push('ABCDEFGHIZ'.indexOf(c[g]))
	};
	// 把数组转为字符串,再进行运算
	var port=f.join('')>>0x3
	// 得到真实的端口号
	console.log(port)

如果上面的js解密步骤你已经理解,接下来用python重写一下这个解密步骤很会简单很多,具体代码如下所示

代码语言:javascript复制
# port_class 是源代码port的同级class
def parse_port(self,port_class):
        string = 'ABCDEFGHIZ'
        arr = list(port_class)
        lists = []
        for x in range(0, len(arr)):
            lists.append(string.find(arr[x]))

        ports = ''.join(str(x) for x in lists)
        return int(ports) >> 3

这段python代码和上面的js代码逻辑一致,效果也一样,只不过是用python翻译了一遍。

到这里爬虫的难点我们都解决了,现在要做的是把爬取下来的代理存储到redis里面。 至于为什么用redis存储,有以下几点原因:

  1. redis相比mysql、文件写入速度更快
  2. 使用redis里的集合特性,不用担心有重复数据
  3. 项目中使用了分布式爬虫,存到redis中方便多台服务器调用

IP代理池添加和维护

下面分为4个步骤来分享一下IP代理池的维护

  • 安装redis 不同系统redis的安装方法不同,本文以Ubuntu为空
代码语言:javascript复制
apt-get install redis-server

redis 安装好以后会自动安装一个客户端redis-cli,我们可以通过redis-cli对数据的增删改查,比如:

代码语言:javascript复制
# 进入客户端
redis-cli
# 添加一条数据
set name 'hello world'
# 获取name的值
get name

但是我们总不能把IP代理地址一个个手动添加到redis里,所以我们还要安装一个python操作redis的模块

这个模块名刚好也叫redis

代码语言:javascript复制
pip install redis

模块安装好我们就可以通过python管理redis里的数据了 redis有5种数据类型分别为:string(字符串),hash(哈希),list(列表),set(集合)及zset(有序集合),我们这里主要使用set(集合)

  • 添加IP代理地址到redis
代码语言:javascript复制
# 导入模块
import redis
# 连接到Redis服务器
conn = redis.Redis(host='127.0.0.1', port=6379)
# 添加数据 key 可以更加自己的需求设置
conn.sadd('proxy','119.179.0.1:8083')
# 随机取出一条代理地址
conn.redis.srandmember('proxy')
  • 验证IP代理是否有效 可以在存入的时候可以验证,也可以在取的时候验证代理是否有效,但是如果存入的时候就验证,取得时候有可能已经不能使用
代码语言:javascript复制
# 导入模块
import redis
import requests
# 连接到Redis服务器
conn = redis.Redis(host='127.0.0.1', port=6379)
# 随机取出一条代理数据
ip=conn.redis.srandmember('proxy')
print(ip)

url='https://www.baidu.com'
proxies = { 
   
            "http": "http://"   ip.decode("utf-8")
        }
        
# 使用IP代理访问百度,测试代理地址是否有效
try:
    data = requests.get(url=url, proxies=proxies, timeout=5)
except:
    # 代理地址无效
  • 删除无效的IP代理

验证IP代理是否无效,如果代理地址无效,可以使用以下命令删除代理,这样可以保证我们代理池中的地址都是有效的

代码语言:javascript复制
conn.redis.srem('proxy', '无效的IP代理地址')

最后把获取代理的步骤封装成一个方法,在需要代理的地方调用即可

到这里我们的代理池就搭建好了,如果感觉只有一个网站的数据不能我们使用,只需要多爬取几个免费代理及时维护就可以啦。

对于大多数爬虫初学者来说,其实爬取一个没有反爬的网站不是什么难事,无非就是把网站的源代码获取下来,然后使用bs4或者正则表达式来提取数据,这里我专门找来一个有反爬的网站,就是想让大家感受一下反爬的流程,当然这也是很简单的一个。

对于代理池的搭建记住三点即可:

  1. 添加IP代理
  2. 验证IP代理是否有效
  3. 及时删除无效代理

总结:本文用一半的篇幅再和大家分享JS破解的步骤,对于没有JS基础的同学看起来会有点吃力,但是通过python的解密步骤,可能会让你对JS加密流程有个大致了解。reids的操作可以参考具体文档

JS在线解密工具

https://tool.lu/js/

参考文档

https://pypi.org/project/redis/

https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#

爬取网站

http://www.goubanjia.com

源代码

https://github.com/iyuyoung/proxy_pool

案例 https://www.mphot.cn

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/143738.html原文链接:https://javaforall.cn

0 人点赞