一个免费ss网站的数据爬取过程
引言
爬虫整体概况
主要功能方法
绕过DDOS保护(Cloudflare)
post中参数a,b,c的解析
网页源码js中的参数a,b,c
二维码(base64文本)解码
pos参数a,b,c值的确定
post参数c的值的加密
AES加密数据解码
确定AES加密模式(弃用)
免判断加密模式并解密(推荐)
解码数据并测延时
最后
本文相关库
第三方开源库
在线测试工具
引言
偶然发现一个免费ss分享网站,本以为简单的url请求即可获取数据。但是没想到在网站的反爬机制很严格,这反而激起了我的好奇心。 不过对于爬虫经验技术来说,是一个很好的学习检验的机会。爬虫整体概况
数据获取的核心是围绕2个http请求:请求方法 | 请求地址 | 请求参数 | 备注 |
get | url | 无 | 网页源码中有重要信息 |
post | url1 | a, b, c | 返回加密数据 |
主要功能方法
绕过DDOS保护(Cloudflare)
简单来讲cloudflare就是通过js验证访问是否来至真正的web浏览器。1 $ git clone https://github.com/VeNoMouS/cloudflare-scrape-js2py.git 2 $ sudo python3 setup.py install
由于Cloudflare不断改变和强化其保护页面,最初使用的 cloudflare-scrape很快就不能用了,当然可以去提交问题或者查看相关问题的解决方案。
我也是各种尝试各种查,不过最后换了 cloudflare-scrape-js2py就解决了问题。 主要代码如下:1 session = requests.session() 2 scraper = cfscrape.create_scraper(sess=session) 3 scraper = cfscrape.create_scraper(delay=11) 4 5 req = scraper.get(url) 6 html = req.text
post中参数a,b,c的解析
网页源码js中的参数a,b,c
解析a,b,c参数主要是依赖正常显示的url的源码中的js代码,如下图重点地方以划出。 上图第一个被划线的一句js如下:1 if(detect){ 2 var a ='317d2cefb40586a9'; 3 var b ='e1a5f5499211cbd4'; 4 var c ='e59ab31720d6cf48'; 5 }else{ 6 var b ='e59ab31720d6cf48'; 7 var c ='317d2cefb40586a9'; 8 var a ='e1a5f5499211cbd4'; 9 }
根据多次观察,可知每次需要取else中的a,b,c的值,代码如下:
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
1 # 获取页面源码中else{后面的abc的值 2 # 参数值只能为:'a','b','c' 3 def get_var_abc(key='a'): 4 # 反向查找 并截取 5 ivs = key +"='" 6 ivl = html.rindex(ivs) 7 key = html[ivl +len(ivs): ivl +len(ivs)+16] 8 return key
二维码(base64文本)解码
上图中 docodeImage 方法中第一个参数值如下: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFcAAABXAQMAAABLBksvAAAABlBMVEX///8AAABVwtN+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAq0lEQVQ4jc3Ruw0DIQwGYEcu3B0LRGINOmbKAgm3AKxExxpIXiDXUURxjkgnXXExKe+vPjfgB8DZYkQisEhVPAEGwgCqPUePqQ2cMo8tOPAf/fT+3W6WI6+5bPhlIxjd1bSqePIv0+TtNAPhUjCJ5vW1R5ZIukUaLw0U97LYuWhe9xDIBtLc9+nq3VXNnufMEQaODm7ffzUTL1k1YCr83G596H5fstJA8bnyAVNpDVwJjnB4AAAAAElFTkSuQmCC 这个参数值相当于一个图片的base64编码,其实就是一个二维码。 这个二维码是一个长度为16的字符,其实也是post中的一个参数值。 代码如下:1 # 直接通过base64字符串解析二维码;获取二维码的值 2 def decode_qrcode(html): 3 qrdatas = html.split('data:image/png;base64,')[1].split("',")[0] 4 qrbytes =bytes(qrdatas, encoding="ascii") 5 decode_result =decode(Image.open(BytesIO(codecs.decode(qrbytes,'base64')))) 6 qrvalue =str(decode_result[0].data, encoding='utf-8') 7 return qrvalue
pos参数a,b,c值的确定
上图中的一段省略版的js代码如下:1 decodeImage('data:image/png;base64,iVBORw0KGxx...xxwJjnB4AAAAAElFTkSuQmCC', 2 function(c){ 3 $.post("data.php",{a:a,b:b,c:encc(c)},
多次观察发现function(key)方法中的参数随机a,b,c任一,而这个随机参数(a/b/c)需要用二维码的值来替代。
说明:post中的a,b,c的值是依赖js中的a,b,c的值和二维码的值,所以a,b,c的值要注意以免混淆。 代码如下:1 # 根据规则分别求出3种情况下的a,b,c 2 def get_param_abc(html): 3 a = get_var_abc(key='a') 4 b = get_var_abc(key='b') 5 c = get_var_abc(key='c') 6 symbolKey = html.split('data:image/png;base64,')[1].split("function(")[1][:1] 7 print('symbolKey',symbolKey) 8 print(symbolKey=='a',symbolKey=='b',symbolKey=='c') 9 if symbolKey == 'a': 10 a = decode_qrcode(html) 11 elif symbolKey == 'b': 12 b = decode_qrcode(html) 13 elif symbolKey == 'c': 14 c = decode_qrcode(html) 15 16 # js2py.translate_file('encc.min.js', 'encc.py') 17 from encc import PyJsHoisted_encc_ 18 c = PyJsHoisted_encc_(c).value 19 print('\n%s\n%s\n%s'% (a,b,c)) 20 return a,b,c
post参数c的值的加密
1 $.post("data.php",{ 2 a: a, 3 b: b, 4 c:encc(c) 5 },
这一行是js中post的请求,是固定的对参数c进行加密,网站中的js加密算法比较清晰。
这时的思路有3种:- 直接通过python执行js代码;
- 第三方(库)自动代码转换( js->python );
- 根据js中的加密思想,手动进行python编码;
1 a,b,c =get_param_abc(html) 2 3 #post提交a,b,c,得到密文 4 payload ={'a':a,'b':b,'c':c} 5 session.cookies = scraper.cookies # 必须带上cookies 6 req = scraper.post(url2,data=payload) 7 msg = req.text
AES加密数据解码
确定AES加密模式(弃用)
最初是想用判断语句指定加密模式参数,但有幸后来看到大牛的好方法。 不过有一个小点需要说明一下,就是可以从js源码中分析出来加密参数; 如上节图上被括起来的js文本如下:1 //js eval加密文本 2 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;}('1 2=\'4\'+\'h\'+\'8\'+\'9\'+\'a\';1 5=\'3\'+\'6\';1 7=0.b.g(d,i,{c:e,2:0.2.4,f:0.5.3});',19,19,'CryptoJS|var|mode|Pkcs7|CBC|pad|ZeroPadding|dec|CTR|ECB|OFB|AES|iv||y|padding|decrypt|CFB|x'.split('|'),0,{})) 3 4 //js 美化过的文本 5 eval(function(p, a, c, k, e, d) { 6 e = function(c) { 7 return (c < a ? "" : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)) 8 }; 9 if (!''.replace(/^/, String)) { 10 while (c--) d[e(c)] = k[c] || e(c); 11 k = [function(e) { 12 return d[e] 13 }]; 14 e = function() { 15 return '\\w+' 16 }; 17 c = 1; 18 }; 19 while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); 20 return p; 21 }('1 2=\'4\'+\'h\'+\'8\'+\'9\'+\'a\';1 5=\'3\'+\'6\';1 7=0.b.g(d,i,{c:e,2:0.2.4,f:0.5.3});', 19, 19, 'CryptoJS|var|mode|Pkcs7|CBC|pad|ZeroPadding|dec|CTR|ECB|OFB|AES|iv||y|padding|decrypt|CFB|x'.split('|'), 0, {})) 22 23 24 //js 解密(解压缩)后的文本 25 var mode = 'CBC' + 'CFB' + 'CTR' + 'ECB' + 'OFB'; 26 var pad = 'Pkcs7' + 'ZeroPadding'; 27 var dec = CryptoJS.AES.decrypt(d, x, { 28 iv: y, 29 mode: CryptoJS.mode.CBC, 30 padding: CryptoJS.pad.Pkcs7 31 });
免判断加密模式并解密(推荐)
中间和朋友电话闲聊中问过一句模式的选择,朋友只是说模式不重要;当时就觉得应该有一种遍历类型的解法。 后来就看到这种方法如下:1 """解密数据得到ss信息,返回list对象""" 2 def decrypt_data(key, iv, endata): 3 ctr = Counter.new(128, initial_value=int(binascii.hexlify(iv), 16)) 4 modes = [ 5 AES.new(key, AES.MODE_ECB), 6 AES.new(key, AES.MODE_CBC, iv), 7 AES.new(key, AES.MODE_OFB, iv), 8 AES.new(key, AES.MODE_CTR, counter=ctr), 9 AES.new(key, AES.MODE_CFB, iv, segment_size=128) 10 ] 11 rtdatas = None 12 for mode in modes: 13 try: 14 dec = mode.decrypt(endata).decode('utf-8') 15 print(dec[-200:]) # 解密后文本结尾可能存在'\0' 16 dec = dec.rstrip('\0') # 去除结尾空白符'\0' 17 rtdatas = json.loads(dec)['data'] 18 break 19 except: 20 pass 21 return rtdatas
根据js代码可知:上面方法的参数key,iv分别对应post参数的a,b;即:
1 endata = base64.b64decode(msg) 2 key =bytes(a,encoding="utf-8") 3 iv =bytes(b,encoding="utf-8") 4 5 ssdata =decrypt_data(key, iv, endata)
网站的加密模式是5种的随机的一种,其中可能遇到CFB模式中会报错,后来发现pycryto的版本问题,安装pycrytodome即可。
解码数据并测延时
post密文解密后是json的文本,下图是格式化片段:最后
对于这个网站学习挺好,其他就不要多想了。 我隐隐觉的这个环境不太适合我,去哪里,再说吧。
本文相关库
python=3.6.5 brotli=1.0.7 cfscrape=2.0.3 requests=2.21.0 Js2Py=0.60 Pillow=5.1.0 # Pillow(PIL Fork) cryptodemo=0.0.1第三方开源库
https://github.com/Anorov/cloudflare-scrape https://github.com/VeNoMouS/cloudflare-scrape-js2py https://github.com/PiotrDabkowski/Js2Py在线测试工具
在线二维码-Convert Your Base64 to Image https://codebeautify.org/base64-to-image-converter js在线解密/美化 https://tool.lu/js/ 在线json解析 https://www.sojson.com/ 参考: binascii.Error: Incorrect padding, even when string length is multiple of 4 https://stackoverflow.com/questions/45879045/binascii-error-incorrect-padding-even-when-string-length-is-multiple-of-4 python字符串str和字节数组相互转化 https://www.cnblogs.com/hhh5460/p/5243305.html Python3操作二维码图片 http://blog.niuhemoon.xyz/pages/2018/04/27/Python3-QRcode/ 关于xxx网站js加密算法的研究 https://github.com/gcdd1993/analyzeSS Python爬取某站科xue上wang帐号(5.15更新) https://mtaoist.xyz/2018/04/07/python-getss/ xiaoTaoist:Auto-Shadowsocks项目 https://github.com/xiaoTaoist/Auto-Shadowsocks
更多精彩