js逆向之全网代理IP的爬取
之前搭建IP代理池的时候爬取过全网代理IP,全网代理IP免费的代理虽然只有首页的20个代理,但是可用程度非常高,可用率高达90%而且它的20个免费代理会每隔20s更新一次,这绝对是搭建IP代理池的绝佳选择,可别高兴太早,这20个代理IP可用率高但要把它们全部爬取下来却要花一点功夫,因为它有几个反爬的坑等着你,且容我慢慢道来~
1.css混淆反爬
找到需要爬取的代理IP,我们右键检查一下,然后我就懵逼了这都是什么鬼~
存放代理IP的那个td标签里既有p、span标签还有div标签,一开始我以为每个标签里都保存了代理IP的一部分只要把这些标签里所有的文本都爬取下来然后拼接到一起就可以了,按照这个思路我得到了结果16168.11.14.250:8510,对比一下网页显示的结果发现怎么第一个IP段多了一个"16"而且IP端口也不对,先别管端口我们先处理IP,再审查一下td标签发现原来td标签里的p标签和span标签里的文本都有"16",那它是怎么渲染的呢?我又分析了一下第二个代理IP,结果发现第一个第二个代理IP的td标签里这些标签有的行内样式是display: none;学过css就知道有display: none样式的元素不会被显示。想到这里我对其他IP进行了相同的分析,结果都得到了正确的结果。
那知道了反爬原理爬取起来岂不是简单,我们只要把带有display: none;的标签去掉就行了,然后把剩余的标签里所有的文本都爬取下来然后拼接到一起就可以了。
2.js混淆反爬
处理完IP地址再来处理端口,其实端口在Elements选项卡显示是正确的,但是源代码中却是有问题,这就说明它是js渲染的结果。
源代码中显示
既然如此那我们就找js,打开F12调试找到Network选项卡重新刷新网页,然后看它调用的js文件:
对js一个一个分析,前两个是jQuery和 bootstrap引入可以跳过,看了main.js和back-to-top.js并没有跟端口相关的函数,接着看pde.js?v=1.0.js,打开你会看到一大堆看不懂的js代码,有反反爬经验的人一看到eval就知道这可能是js混淆压缩,应对js混淆压缩加密,我们可以网上找js在线解压缩工具,这里我用javascript工具对这段js进行在线解压缩、解混淆结果发现它压缩了两次(够坑的):
最终结果:
var _$ = ['.port', "each", "html", "indexOf", '*', "attr", 'class', "split", " ", "", "length", "push", 'ABCDEFGHIZ', "parseInt", "join", ''];
$(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) {}
})
})
再格式化一下:
$(function() {
$('.port').each(function() {
var a = $(this).html();
if (a.indexOf('*') != -1) {
return
};
var b = $(this).attr('class');
try {
b = (b.split(" "))[1];
var c = b.split("");
var d = c.length;
var f = [];
for (var g = 0; g < d; g++) {
f.push('ABCDEFGHIZ'.indexOf(c[g]))
};
$(this).html(window.parseInt(f.join('')) >> 3)
} catch (e) {}
})
})
这样一看解密的原理呼之欲出了,其实就是把端口的class属性值的port后面的那些大写字母拿出来,然后把每个大写字母在"ABCDEFGHIZ"中遍历得到位置上的数,最后遍历完所有的字母,把这些字母位置所在的数按顺序拼接再转化为数字再除于8就可以得到正确的端口号。
js混淆F12处理
其实js混淆不用js在线工具也能处理,只要我们打开F12找到控制台( console)选项,把加密混淆的js代码复制到控制台中,然后把eval四个字母去掉回车就行,如果有多次压缩加密,就把上一次解密的js代码再复制到控制台,再把evall四个字母去掉回车,反复如此得到最终结果。
完整代码
import re
import requests
from lxml import etree
url = "http://www.goubanjia.com/"
headers = {
'user-agent': "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.29 Safari/537.36",
}
request = requests.get(url=url, headers=headers)
# 用正则去除带有display:none;的标签
html = re.sub(r"<p style='display:none;'>.*?</p>","",request.text)
html = re.sub(r"<p style='display: none;'>.*?</p>","",html)
html = re.sub(r"<span style='display:none;'>.*?</span>","",html)
html = re.sub(r"<span style='display: none;'>.*?</span>","",html)
html = re.sub(r"<div style='display:none;'>.*?</div>","",html)
html = re.sub(r"<div style='display: none;'>.*?</div>","",html)
data = etree.HTML(html)
ip_info = data.xpath('//td[@class="ip"]')
for i in ip_info:
# 把td标签里所有的文本取出来,再通过join函数连接成字符串,最后用正则替换掉假的端口
ip_addr = re.sub(r":\d+","","".join(i.xpath('.//text()')))
# 提取出要处理的span标签的class属性里的大写字母
port = "".join(i.xpath('./span[last()]/@class')).replace(r"port","").strip()
# 定义一个空列表用于保存找到字母的位置
num = []
# 遍历提取出来的字母,并对每一个遍历出来的字母在字符串"ABCDEFGHIZ"里找位置
for j in port:
num.append(str("ABCDEFGHIZ".find(j)))
# 先把num连接成字符串,然后再转换为整型,最后处于8得到真正的端口
ip_port = str(int(int("".join(num))/8))
# 把处理好的ip地址和端口拼接到一起得到完整的ip地址和端口
full_ip = ip_addr + ":" + ip_port
print(full_ip)
结果展示:
浏览器真实端口和ip
代码运行后得到的ip和端口
对比发现两个结果是一样的,其实无忧代理有一样的反爬机制有兴趣的去试试无忧代理网!