0%

Python爬取某站科学上网帐号(5.15更新)

前言

无意间发现了一个SS帐号分享站,但每次都需要手动获取帐号信息太麻烦了,这里用Python写一个爬虫来自动获取帐号信息。

分析

先来看一下网站源码。站长做了防爬处理,数据并不保存在源代码中,并且针对爬虫常见的模拟浏览器手段进行检测。

free-ss_view
看一下网络连接,发现了一个ss.json的get请求,内容居然帐号信息。
free-ss_json
本以为是站长特意留下的API接口,测试之后才发现里面的帐号信息都不能正常使用。现在只能老老实实的去分析源码了。
网站关键部分是用javascript写的。

1
2
3
4
5
6
7
8
9
10
11
12
//用a,b 分别初始化x(key),y(iv)
var a = '1a52316b1ed72c8f';var b = 'cd3e6a7914085b2f';var c = '327bd961633a77c4';
var x = CryptoJS.enc.Latin1.parse(a);
var y = CryptoJS.enc.Latin1.parse(b);

//post方法提交a,b,c,得到密文data
$.post("data.php",{a:a,b:b,c:c},function(data){
//解密
var dec = CryptoJS.AES.decrypt(data,x,{iv:y,mode:CryptoJS.mode.ECB,padding:CryptoJS.pad.Pkcs7});
//格式化成json格式方便后面使用
var tbdt = $.parseJSON(dec.toString(CryptoJS.enc.Utf8))
}

可以看到流程如下

  1. 判断是否是正常的浏览器,如果正常,就用POST方法提交正确的a,b,c 参数,服务端会返回一串AES密文
  2. 用a,b分别初始化key,iv,用Crypto-js库去解密返回的数据
  3. 解密后,重新排版,优化显示

这里我们用Python去模拟整个过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import re
import base64
import requests
from Crypto.Cipher import AES


headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:59.0) Gecko/20100101 Firefox/59.0'}
url= "https://free-ss.site/"
url2="https://free-ss.site/data.php"

#获取正确的a,b,c参数
s = requests.session()
html = s.get(url,headers=headers,timeout=3)
a,b,c = re.findall(r"\'(.*?)\'",html.text)[6:9]

#post提交a,b,c,得到密文
p_data = {'a':a,'b':b,'c':c}
msg = s.post(url2,headers=headers,data=p_data,timeout=3)
endata = base64.b64decode(msg.text) #返回的是base64编码数据,先解码


#用正确的参数初始化AES,ECB模式不需要iv向量
key = bytes(a,encoding="utf-8")
#iv = bytes(b,encoding="utf-8")
aes = AES.new(key,AES.MODE_ECB)
#解密得到数据
data = aes.decrypt(endata).decode('utf-8')

我们可以看到数据末尾多了一些无用字符,因为AES加密需要原始数据块是16位的倍数,不足则用按照某些标准(诸如源码中的Pkcs7)进行填充补位。
ss_data
我们继续处理解密后的数据,生成ss链接以方便直接导入

1
2
3
4
5
6
7
8
9
10
import json
ss_data = re.findall(r'{.*}',data)[0] #去掉多余的填充字符
ss_json = json.loads(ss_data)['data'] #json化数据

for i in ss_json:
#ss://method:password@server:port
ss = "{}:{}@{}:{}".format(i[3],i[4],i[1],i[2])
ss_url = "ss://"+ bytes.decode( base64.b64encode(bytes(ss,encoding="utf8")) )
#print(ss)
print(ss_url)

后来发现网站每次使用的加密方法都是随机的,Crypto-js一共实现了AES的5种加密模式:CBC、CFB、CTR、ECB、OFB,每种都用Python实现太麻烦了,这里我们取个巧:判断加密模式,如果是ECB,则进行解密,否则重新请求网页信息。整个Demo如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import re
import base64
import json
import requests
from Crypto.Cipher import AES

if __name__ == '__main__':
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:59.0) Gecko/20100101 Firefox/59.0'}
url= "https://free-ss.site/"
url2="https://free-ss.site/data.php"
while(1):
s = requests.session()
html = s.get(url,headers=headers,timeout=3)
mode = re.findall(r"CryptoJS.mode.(\w{3})",html.text)
print(mode)
if 'ECB' in mode:
a,b,c = re.findall(r"\'(.*?)\'",html.text)[6:9]
p_data = {'a':a,'b':b,'c':c}
msg = s.post(url2,headers=headers,data=p_data,timeout=3)
endata = base64.b64decode(msg.text)
key = bytes(a,encoding="utf-8")
aes = AES.new(key,AES.MODE_ECB)
data = aes.decrypt(endata).decode('utf-8')
ss_data = re.findall(r'{.*}',data)[0]
ss_json = json.loads(ss_data)['data']
for i in ss_json:
#ss://method:password@server:port
ss = "{}:{}@{}:{}".format(i[3],i[4],i[1],i[2])
ss_url = "ss://"+ bytes.decode( base64.b64encode(bytes(ss,encoding="utf8")) )
#print(ss)
print(ss_url)
break

后续

完整脚本已经放在Github上,一键版下载地址

本文仅供学习交流,如有冒犯之处,联系可删。

参考

欢迎关注我的其它发布渠道