网易云爬虫爬取用户粉丝信息

网易云爬虫爬取用户粉丝信息

0x01 前言

前不久听说女神挺喜欢小狗小猫的,就给女神发了些小猫图片和视频,女神于是就给推荐了个网易云的博主。

发现女神平常用网易云听歌,就突想知道平常女神都喜欢听一些什么歌。之前有见女神分享自己网易云图片,隐约记得账户名中有‘chocolate’,就登上网易云一通搜索,一个一个用户信息挨着去看,也没发现符合的;突然灵机一闪,女神不是也关注了之前给分享的那个“萌宠图刊”的账户吗,可以去他的粉丝里去找啊!说干就干,打开之后,emmm……将近四万的粉丝,一个一个去翻要到猴年马月了……直接写个爬虫爬取粉丝用户名再搜索吧!

0x02 网站内容分析

打开网易云网站,发现不需要登录就能搜索查看用户粉丝,发现简单了不少,不用关心登录了!

搜索“萌宠图刊”然后点击粉丝,F12,挨着一个一个查看响应内容,很快就找到了我们需要的内容:

然后双击打开,确实为我们需要的响应包:

查看其请求内容,提交了两个参数,明显为加密之后的:

先尝试获得第一页的粉丝昵称!

0x03 爬取单页粉丝昵称

 1 import urllib.request
 2 import urllib.parse
 3 import json
 4 
 5 headers = {
 6     "Referer": "http://music.163.com",
 7     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4350.7 Safari/537.36",
 8 }
 9 url = "https://music.163.com/weapi/user/getfolloweds?csrf_token="
10 
11 formdata = {
12     "params": "DxYWvntrh5oSYJ0o3vIBh/q/zJSFyiqQ1jAWx/B9tH5j6RdTabrnEP1WCj3wmx1l+WfGB0FQh3L3/lqcoozHZRXSMMqF+thJcWpR7Wwq53EGDa4XSiKhnBWSUdHBq9Io9H8WUmXBRVoa/3O4yG5gKg==",
13     "encSecKey": "bbee09bca20f8a7f73a9d6ecf4e6f1a008235e696fd2f1e756a05b94a6a936ff6ee50b0a8dda8b9cf3ac5d3a1fcc231afbc2585b7fbfc6c4751a14f989ddd44c5f76c9516921173c44c3d844064a07a49f615494501e227d57ead86a058d73bf99260be7e6cb92fea65e56174c2dd63e1a93322d655bc654f728bbab6eafac78"
14 }
15 
16 temp_data = json.loads(urllib.request.urlopen(urllib.request.Request(url=url, data=urllib.parse.urlencode(formdata).encode("utf-8"),headers=headers)).read().decode("utf-8"))["followeds"]
17 
18 for eve_data in temp_data:
19     nickname = eve_data["nickname"]
20     print(nickname)

 

运行结果正常

0x04 分析加密参数

由于获取粉丝信息是由两个请求参数获得的,要对这两个参数进行分析。

查看不同页数两个请求参数值内容不同,因此可以确定翻页功能确实是由这两个参数控制的!

这两个参数很明显是经过加密之后的,而这个请求又与一个js文件息息相关,因此判断有可能是请求数据通过这个js加密之后再发出请求的,打开这个js文件格式化后进行分析,对两个参数进行搜索。

通过分析发现最终所传递的参数应该就是从这个地方来的,想要知道他到底传的参数是什么内容,我可以对这个js进行简单的修改,即在这两个语句之前加一个alert语句,让 bZj0x 内的四个变量进行弹出:

1 alert("wyy" + JSON.stringify(i2x) + "||" + bkk0x(["流泪", "强"]) + "||" + bkk0x(YS7L.md) + "||" + bkk0x(["爱心", "女孩", "惊恐", "大笑"]))

然后利用 charles 软件,将原本网站的js文件用修改过的文件进行替换,再次访问粉丝页面

访问第一页:

访问第二页:

分析弹窗的内容:

wyy{"userId":"30982607","offset":"0","total":"true","limit":"20","csrf_token":""}||010001||00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7||0CoJUm6Qyw8W8jud

wyy{"userId":"30982607","offset":"20","total":"false","limit":"20","csrf_token":""}||010001||00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7||0CoJUm6Qyw8W8jud

 

可以看出,四个参数用 “||” 隔开,且只有第一个参数有变化,其他三个参数为常量。

其中 “userId” 为该用户的id,“offset” 发生了变化,且刚好对应每一页粉丝个数为20.因此 “offset“ 用来控制翻页。

接下来就是重新回到js文件中,查看加密的方法。

0x05 加密流程分析

在js文件中重新查找encText 和 encSecKey

可以看出,加密部分就是在这里。

 1   function a(a) {
 2         var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
 3         for (d = 0; a > d; d += 1) e = Math.random() * b.length, e = Math.floor(e), c += b.charAt(e);
 4         return c
 5     }
 6 
 7     function b(a, b) {
 8         var c = CryptoJS.enc.Utf8.parse(b), d = CryptoJS.enc.Utf8.parse("0102030405060708"),
 9             e = CryptoJS.enc.Utf8.parse(a), f = CryptoJS.AES.encrypt(e, c, {iv: d, mode: CryptoJS.mode.CBC});
10         return f.toString()
11     }
12 
13     function c(a, b, c) {
14         var d, e;
15         return setMaxDigits(131), d = new RSAKeyPair(b, "", c), e = encryptedString(d, a)
16     }
17 
18     function d(d, e, f, g) {
19         var h = {}, i = a(16);
20         return h.encText = b(d, g), h.encText = b(h.encText, i), h.encSecKey = c(i, e, f), h
21     }
22 
23     function e(a, b, d, e) {
24         var f = {};
25         return f.encText = c(a + e, b, d), f
26     }

 根据相同的加密流程,进行处理。

 1 def get_params_first(userId, offset):
 2     params_first = '{"userId":"' + userId + '","offset":"' + str(offset) + '","total":"false","limit":"100","csrf_token":"6f033a3138de5a06cea24807ba88e40b"}'
 3     return params_first
 4 #userid 是用户id    offset 是控制翻页
 5 #params_first = '{"userId":"30982607","offset":"20","total":"false","limit":"20","csrf_token":""}'
 6 params_second = "010001"
 7 params_thrid = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
 8 params_forth = "0CoJUm6Qyw8W8jud"
 9 
10 #params 需要第一个参数和第四个参数
11 #encSecKey 需要第二个和第三个参数,还需要一个随机的16个字符串
12 
13 def aesEncrypt(text, key):
14     # 文本
15     pad = 16 - len(text) % 16
16     text = text + pad * chr(pad)
17     key = key.encode('utf-8')
18     encryptor = AES.new(key, 2, b'0102030405060708')
19     ciphertext = encryptor.encrypt(text.encode('utf-8'))
20     ciphertext = base64.b64encode(ciphertext)
21     return ciphertext
22 
23 def get_params(text, userId, offset):
24     # 第一个参数
25     params_first = get_params_first(userId, offset)
26     params = aesEncrypt(params_first, params_forth).decode('utf-8')
27     params = aesEncrypt(params, text)
28     return params
29 
30 def rsaEncrypt(pubKey, text, modulus):
31     #进行rsa加密
32     text = text[::-1]
33     rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(pubKey, 16) % int(modulus, 16)
34     return format(rs, 'x').zfill(256)
35 
36 def get_encSecKey(text):
37     pubKey = params_second
38     moudulus = params_thrid
39     encSecKey = rsaEncrypt(pubKey, text, moudulus)
40     return encSecKey

 

至此,加密部分已经完成,可以通过控制“offset”的数值来控制翻页。

0x06 最终完整代码

 1 import urllib.request
 2 import urllib.parse
 3 import json
 4 import base64
 5 from Crypto.Cipher import AES
 6 import codecs
 7 
 8 headers = {
 9     "Referer": "http://music.163.com",
10     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4350.7 Safari/537.36",
11 }
12 url = "https://music.163.com/weapi/user/getfolloweds?csrf_token="
13 
14 def get_params_first(userId, offset):
15     params_first = '{"userId":"' + userId + '","offset":"' + str(offset) + '","total":"false","limit":"20","csrf_token":"6f033a3138de5a06cea24807ba88e40b"}'
16     return params_first
17 #userid 是视频的标志    offset 是控制翻页
18 #params_first = '{"userId":"30982607","offset":"20","total":"false","limit":"20","csrf_token":""}'
19 params_second = "010001"
20 params_thrid = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
21 params_forth = "0CoJUm6Qyw8W8jud"
22 
23 #params 需要第一个参数和第四个参数
24 #encSecKey 需要第二个和第三个参数,还需要一个随机的16个字符串
25 
26 def aesEncrypt(text, key):
27     # 文本
28     pad = 16 - len(text) % 16
29     text = text + pad * chr(pad)
30     key = key.encode('utf-8')
31     encryptor = AES.new(key, 2, b'0102030405060708')
32     ciphertext = encryptor.encrypt(text.encode('utf-8'))
33     ciphertext = base64.b64encode(ciphertext)
34     return ciphertext
35 
36 def get_params(text, userId, offset):
37     # 第一个参数
38     params_first = get_params_first(userId, offset)
39     params = aesEncrypt(params_first, params_forth).decode('utf-8')
40     params = aesEncrypt(params, text)
41     return params
42 
43 def rsaEncrypt(pubKey, text, modulus):
44     #进行rsa加密
45     text = text[::-1]
46     rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(pubKey, 16) % int(modulus, 16)
47     return format(rs, 'x').zfill(256)
48 
49 def get_encSecKey(text):
50     pubKey = params_second
51     moudulus = params_thrid
52     encSecKey = rsaEncrypt(pubKey, text, moudulus)
53     return encSecKey
54 
55 userId = "30982607"
56 offset = 0
57 path = "./" + userId + ".txt"
58 while True:
59     text = "A"* 16
60     params = get_params(text, userId, offset)
61     encSecKey = get_encSecKey(text)
62     formdata = {
63         "params": params,
64         "encSecKey": encSecKey
65     }
66     temp_data = json.loads(urllib.request.urlopen(urllib.request.Request(url=url, data=urllib.parse.urlencode(formdata).encode("utf-8"), headers=headers)).read().decode("utf-8"))["followeds"]
67     length = len(temp_data)
68 
69     for eve_data in temp_data:
70         nickname = eve_data["nickname"]
71         print(nickname)
72         with open(path, "a") as file:
73             file.write(nickname + "\n")
74     if offset < 38168:
75         offset = offset + 20
76         #print(offset)
77     else:
78         break

 

 0x07 杯具

运行了半天才发现,只能爬到最近关注的1000个粉丝。。。同样,网站上也是只能查看50页。。。也就是“offset“的值最大为1000

不死心的我,再次看了下第一个参数,发现还有一个“limit“字段,于是尝试改变limit值,发现其控制的是一页显示粉丝的数量,但是经过测试,其上限为100

改变“limit”值再次运行,发现一次能爬取100个粉丝昵称,速度大大提高,但也只能够爬取到1100个粉丝昵称。

 

既然网站只能查看50页的粉丝,pc客户端呢?

看到638页,感觉有戏!直接点击最后一页,然后,emmmm。。。

再去看手机app,还好,并没有被限制。。。

只能去一点点手动翻了,只能庆幸粉丝只有3w多了。。。

posted @ 2021-01-18 16:45  果冻ya  阅读(1709)  评论(1编辑  收藏  举报