相爱相杀的爬虫与反爬工程师啊……愿你们和谐相处。
前些日子写爬虫时遇到一个比较奇怪的编码,是构造目标网址的一个组成部分,我更倾向于说是编码而不是加密,虽然的确有点加密的意思。当时算是一个绕不开的需求,所以费了很大功夫研究了出来。有些时候网站的前端、后端工程师会用尽办法反爬,而写爬虫的人又要想尽办法反反爬,真可谓是相爱相杀了。尤其是在编码(加密)这件事上,我随口胡诌一个编码过程,就很难逆向推导出来,这次有点运气成分,也有点规律可寻,所以记录下来给大家。
为了让大家专注于编码本身,我就不把真实网址放出来了。大家只需要知道网址构成大概是这个样子
https://www.wtf.com/N3V5bw==.html
这是最终的一个目标页,后面的N3V5bw==在这里的含义是36672。由于数据量比较大,从列表页没办法抓取到所有的href,所以需要自己构造网址,而网址又不可能是没有规律的,想办法观察到了36672(可以理解成一个资源的ID)和N3V5bw==的对应关系。
N3V5bw==这样的形式,很容易判断出是base64编码,于是base64解码,得到7uyo,没有什么明显思路,显然还做了其他操作(后来知道是自己经验不足,离答案挺近的)。
然后手动获取到了一些一一对应关系。
id | base64 | base64解码 |
---|---|---|
1 | YQ | a |
2 | aw | k |
3 | dQ | u |
4 | MTQ= | 14 |
5 | MWU= | 1e |
6 | MW8= | 1o |
7 | MXk= | 1y |
8 | Mjg= | 28 |
9 | Mmk= | 2i |
10 | MnM= | 2s |
11 | MzI= | 32 |
12 | M2M= | 3c |
13 | M20= | 3m |
到这一步还是没什么思路,只能又想办法多获取了一些对应关系(前面提到了没有统一的入口,这一步也费了一些周折)
id | base64 | base64解码 |
---|---|---|
1 | YQ== | a |
2 | aw== | k |
3 | dQ== | u |
4 | MTQ= | 14 |
5 | MWU= | 1e |
6 | MW8= | 1o |
7 | MXk= | 1y |
8 | Mjg= | 28 |
9 | Mmk= | 2i |
10 | MnM= | 2s |
11 | MzI= | 32 |
12 | M2M= | 3c |
13 | M20= | 3m |
14 | M3c= | 3w |
15 | NDY= | 46 |
16 | NGc= | 4g |
17 | NHE= | 4q |
18 | NTA= | 50 |
19 | NWE= | 5a |
20 | NWs= | 5k |
21 | NXU= | 5u |
22 | NjQ= | 64 |
23 | NmU= | 6e |
24 | Nm8= | 6o |
25 | Nnk= | 6y |
26 | Nzg= | 78 |
27 | N2k= | 7i |
28 | N3M= | 7s |
29 | ODI= | 82 |
30 | OGM= | 8c |
现在的主要考虑是如何从第一列到第三列。
刚开始我观察第三列的时候看到最后一位是18循环的,第一位有点类似4循环,还以为有什么mod18的套路在里面,后面看到组成是0~9和a~z(一共36),突然猜测会不会是36进制…
于是把ID转36进制。
ID | ID转36进制 | 解码结果 | 解码结果36进制转10进制 |
---|---|---|---|
1 | 1 | a | 10 |
2 | 2 | k | 20 |
3 | 3 | u | 30 |
4 | 4 | 14 | 40 |
5 | 5 | 1e | 50 |
6 | 6 | 1o | 60 |
7 | 7 | 1y | 70 |
8 | 8 | 28 | 80 |
9 | 9 | 2i | 90 |
10 | a | 2s | 100 |
11 | b | 32 | 110 |
12 | c | 3c | 120 |
13 | d | 3m | 130 |
14 | e | 3w | 140 |
15 | f | 46 | 150 |
16 | g | 4g | 160 |
17 | h | 4q | 170 |
18 | i | 50 | 180 |
19 | j | 5a | 190 |
20 | k | 5k | 200 |
21 | l | 5u | 210 |
22 | m | 64 | 220 |
23 | n | 6e | 230 |
24 | o | 6o | 240 |
25 | p | 6y | 250 |
26 | q | 78 | 260 |
27 | r | 7i | 270 |
28 | s | 7s | 280 |
29 | t | 82 | 290 |
30 | u | 8c | 300 |
于是结果就比较明显了。
所用到的规则为:ID*10转36进制后进行base64编码。
事后反思了一下,主要是极少见到36进制,所以从一开始就没往那边动心思,直到看到了很多数据以后觉得18循环,跟常见的4,8,16没关系,才猜测到36进制上面。
这也算是一点小记录和小经验。 还真是设计规则的人随便胡诌一个规则,破解规则的人就要费很大力气了…… 最后的代码是这么写的:
import base64
def baseN(num, b):
return ((num == 0) and "0") or (baseN(num // b, b).lstrip("0") + "0123456789abcdefghijklmnopqrstuvwxyz"[num % b])
def wtf(num):
return base64.b64encode(baseN(num * 10, 36).encode()).decode()