目录
一、登录
(一)、登录4399
1.直接使用Cookie
2.使用账号密码进行登录
可选观看内容,使用python对密码进行加密(无结果代码,只有过程分析)
二、代理
免费代理
后续:协程,抓取视频
这节我们来尝试一下登录和代理。
一、登录
很多网站登录和不登陆显示的内容是不一样的,这主要和Cookie有关。用户先向网站发送账号密码以获取Cookie作为凭证,之后用户发送请求时,携带着Cookie就能让网页知道你是谁了,经过一段时间后,Cookie失效(过有效时段之类的)就需要重新登陆。
(一)、登录4399
4399童年回忆(应该有账号吧?这个账号注册可能要实名了,不想注册的换个其他的不需要验证码登录的网站,差不多),今天我们就试试进入登录4399新用户-4399用户中心_4399.com这个网址:
但你没Cookie肯定进不去,会跳转到登录界面,咱们先来整点简单的方法
1.直接使用Cookie
登陆后,我们可以抓到一些请求,其中有个 profile/ 请求,得到它就代表我们成功了。(如果获取到的依然是‘登录4399新用户’代表没登录成功)
直接看它的标头,其中Cookie这一段就是我们需要的信息,直接全部复制
之后直接在请求头中加入复制的Cookie就能够登录成功了。 用记事本看一眼打开的结果能看到“我的信息”几个字就代表成功了。
import requests
url = "https://u.4399.com/profile/"
headers = {
# 用户代理,某些网站验证用户代理,微微改一下,如果提示要验证码之类的,使用它
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0",
"Cookie":''# 这里用你自己的Cookie
}
session = requests.session()
with session.post(url=url, headers=headers,) as resp:
resp.encoding = "utf-8"
print(resp)
with open("4399_profile.html", mode="w",encoding="utf-8") as f:
f.write(resp.text) # 读取到网页的页面源代码"
2.使用账号密码进行登录
咱们可以去登录4399新用户-4399用户中心_4399.com这个网站进行登录。我先用i道i做用户名,123456做密码打个样。
先不要急不可耐的登录抓包,你尝试了就会发现所有抓到的包里面没有跟登录相关的部分。 因为这里跳转了其他的界面,自动清除了之前的抓包结果。咱们先选中装包工具上面的保留日志再开始抓包。(4399采用表单登录,提交表单后跳转到其他界面)
最后我们可以成功找到 login.do的请求,他的负载里面刚好有username(用户名)和password(密码),但是用户名能看到,密码却不是123456,这是因为有的网站会将密码加密后再发送,或者发送哈希值。(提一句,这种加密啊,哈希啊,很多是不可逆的,所以许多服务器也不知道你的密码具体是什么。)
接下来,我们就可以根据这条请求来编写我们的代码了。这里使用了session开启了一个会话,第一次请求,会话获取了cookie的值,后面的会话便会自动携带获取的cookie值,以保证连续。可以打断点看看 resp.cookies的变化。
import requests
url = "https://ptlogin.4399.com/ptlogin/login.do?v=1"
url_2 = "https://u.4399.com/profile/"
headers = {
# 用户代理,某些网站验证用户代理,微微改一下,如果提示要验证码之类的,使用它
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0",
}
data = {
'loginFrom': 'uframe',
'postLoginHandler': 'refreshParent',
'layoutSelfAdapting': 'false',
'externalLogin': 'qq',
'displayMode': 'embed',
'layout': 'vertical',
'bizId': '',
'appId': 'u4399',
'gameId': '',
'css': 'https://uc.img4399.com/root/css/ptlogin.css?a3993b7',
'redirectUrl': '',
'sessionId': '',
'mainDivId': 'embed_login_div',
'includeFcmInfo': 'false',
'level': '0',
'regLevel': '4',
'userNameLabel': '4399用户名',
'userNameTip': '请输入4399用户名',
'welcomeTip': '欢迎回到4399',
'sec': '1',
'password': 'U2FsdGVkX181bGhjYtZJJrfI3NjzazeojBKK+KVCcn4='.encode('utf-8'), # 这里用你自己的密码
'username': 'i道i',
}
session = requests.session()
with session.post(url=url, data=data, ) as resp:
resp.encoding = "utf-8"
print(resp)
with open("4399.html", mode="w", encoding="utf-8") as f:
f.write(resp.text) # 读取到网页的页面源代码"
resp = session.get(url=url_2)
print(resp)
with open("4399_profile.html", mode="w", encoding="utf-8") as f:
f.write(resp.text) # 读取到网页的页面源代码"
可选观看内容,使用python对密码进行加密(无结果代码,只有过程分析)
接下来,我们就要确定输入的密码是如何变成这段字符的。我们再尝试登陆一次,发现这段字符有变化。这表明它是跟时间有关的加密。(可能有时间种子随机数,所以一般加密都会让线程睡一小会,保证种子随机性足够)
第一想法是找到登录按钮(或者回车)的回调函数,向后寻找肯定能够发现包装请求的地方。第二想法是请求的url有login.do,我们就先看看网页源代码里有没有login.do存在,那里肯定和发送请求有关,向后找到请求的负载怎么产生的就知道如何加密了。
通过页面元素检查,找到登录按钮的位置,发现登录按钮没有什么有意义的回调函数,但有id和class,搜索id的值,可以发现上面有该按钮的事件处理器,这就是我们要找的地方。
或者直接在全部代码中搜索 login.do,刚好只发现一处有login.do,同样是这个代码。
找到位置后,我们在39行设置一个断点,(点击39数字,前面出现小红点即可)然后再次登录即可在断点处暂停,
然后选择右上角步入,进入check_login函数中。找到了一处注释有“密文传输的地方”(好注释),通过变量值的变化推断,红框标注的就是加密部分。
接下来,我们就需要通过 python实现上面的 encryptAES 加密,发送请求了。
AES是一种对称加密技术,即加密解密密钥相同,而所需密钥长度不及16倍数需要补齐,而且补齐方式比较特殊,比如少4个,需要补四个chr(4)。CryptoJS.AES.encrypt默认aes-256,加密模式CBC,填充方式Pkcs7,也就是说,上面的字符不是真正的密钥,而只是密钥“种子而已”。
这就比较麻烦了,我们需要用python模拟这一复杂过程,为了避免这一麻烦,我们继续往深处走,看看CryptoJS.AES.encrypt函数内部怎么执行的,能不能从中套出真正的密钥。
通过debug模式的步入,我们找到了这里,这段代码的b就是我们的密码 123456 ,c是密钥短文 'lzYW5qaXVqa' 最终对里面代码分析结果如下:
# encrypt: function(c, d, e) {return a(d).encrypt(b, c, d, e) # -》3行
# encrypt: function(a, b, c, d) { # b-》密码 c-》密钥短语 d-》无
# var e, f;
# return d = this.cfg.extend(d),
# e = d.kdf.execute(c, a.keySize, a.ivSize), # -》13行
# d.iv = e.iv, # b是密码,e.key是32位密钥,d是16位偏移量
# f = x.encrypt.call(this, a, b, e.key, d),-》43行
# f.mixIn(e),-》75行
# f
# },
# execute: function(a, b, c, d) { a-》密钥短语 b-》8 c-》4 d-》无
# var e, g;
# return d || (d = f.random(8)), # 生成8位随机数
# e = l.create({
# keySize: b + c
# }).compute(a, d), # -》28行 最后e长度48
# g = f.create(e.words.slice(b), 4 * c), # g是e最后16位
# e.sigBytes = 4 * b, # 相当于截断到32位
# u.create({
# key: e,
# iv: g,
# salt: d
# })
# }
# compute: function(a, b) { a-》密钥短语 b->8位随机数
# for (var j, k, c = this.cfg, # 密钥长度h-》12,迭代次数i-》1
# d = c.hasher.create(), f = e.create(), g = f.words, h = c.keySize, i = c.iterations; g.length < h; ) {
# for (j && d.update(j),若j存在,使用j(不执行下几行代码,短路),否则使用哈希器d包含j
# j = d.update(a).finalize(b), 使用哈希器包含a,并用b计算新哈希值,结果放在j
# d.reset(), 重置哈希器
# k = 1; i > k; k++)
# j = d.finalize(j),
# d.reset();
# f.concat(j)将计算得到的哈希值合并起来,简单拼接
# }
# return f.sigBytes = 4 * h,
# f
# }
# encrypt: function(a, b, c, d) { # cfg,密码,密钥,偏移量
# var e, f, g;
# return d = this.cfg.extend(d),
# e = a.createEncryptor(c, d), #创建加密器实例
# f = e.finalize(b), # 进行加密 -》61行
# g = e.cfg,
# u.create({
# ciphertext: f, # 16位加密结果
# key: c, # 32位密钥
# iv: g.iv, # 16位偏移量
# algorithm: a,
# mode: g.mode,
# padding: g.padding,
# blockSize: a.blockSize,
# formatter: d.format
# })
# },
# finalize: function(a) {
# a && this._append(a);
# var b = this._doFinalize(); -》67行
# return b
# },
# _doFinalize: function() {
# var b, a = this.cfg.padding;
# return this._xformMode == this._ENC_XFORM_MODE ? (a.pad(this._data, this.blockSize), 对数据进行填充
# b = this._process(!0)) : (b = this._process(!0), # 获得最终加密结果
# a.unpad(b)),
# b -》16位
# },
# mixIn: function(a) {
# for (var b in a)
# a.hasOwnProperty(b) && (this[b] = a[b]); # 如果b是a的属性,直接复制
# a.hasOwnProperty("toString") && (this.toString = a.toString)
# },
# stringify: function(a) { # toString内部,
# var d, b = a.ciphertext, c = a.salt; # ciphertext加密结果16位,salt 8位随机数
# return d = c ? f.create([1398893684, 1701076831]).concat(c).concat(b) : b,
# d.toString(j)
# },
也就是说,我们仿照上面的过程,产生最后一个函数的结果:d.toString(j)即可,
如果认为麻烦,可以尝试固定execute的随机数,这样密钥和偏移量就固定了,只需根据43行的函数,对密码使用固定密钥加密(AES,CBC,Pkcs7填充方式)之后参照81行的函数,使用加密结果和偏移量合并出完整参数即可。
有挺多常用的网站,发送消息都加密,所以自己看看,以后遇到加密的负载知道怎么做。这个login.do没有启动器,所以无法查看堆栈,需要自己找,有启动器的请求可以从启动器的链接里快速定位改变的位置。
这里挖个坑,等说完Selenium后,会单出一篇使用Selenium登录知乎的文章。没填的话提醒我一下。
总而言之,直接使用Cookie比较简单,不过时间久了会失效,账号密码自动化能够持久一点。
二、代理
有一些网站,会限制ip的访问频率,比如很多登录页面,一天内不能登录超过五次。超过次数了,你这个ip就无法访问这个网站了,也就是有人说的封ip。这个时候,我们就需要通过ip代理,让拥有其他ip的计算机代替我们发送或者转发请求。
免费代理
这里推荐一个网站:免费代理IP [ 实时更新 ] - 站大爷
有一些免费代理供临时使用,到时候选择响应时间短的一些代理。
类型有普匿 高匿 透明之分,网站可以通过REMOTE_ADDR、HTTP_VIA、HTTP_X_FORWARDED_FOR这三个值知道你的ip地址。透明和普匿能够让网站知道你在使用代理,高匿不会。普匿和高匿都可以让网站无法知道你的真实IP地址,所以很多人都喜欢用高匿。
REMOTE_ADDR | HTTP_VIA | HTTP_X_FORWARDED_FOR | |
无 | 真实ip | 无 | 无 |
透明 | 代理ip | 真实ip | |
普匿 | 代理ip | 代理ip | 代理ip |
高匿 | 代理ip | 无 | 无 |
现在来尝试通过代理访问百度,很多免费代理不支持https,所以访问http://baidu.com
将ip:端口号拼一起就是代理地址,(便宜没好货,多换几个,总有一个能用)。使用proxies参数即可使用ip
import random
import requests
url = "http://www.baidu.com/"
proxies_all = ["221.6.139.190:9002"] # 列表中可以放多个代理
def get_proxies(proxies_all): # 随机获取列表中的一个代理
ip = random.choice(proxies_all)
return { # 有的代理不支持https有的不支持http,注意
"https": "https://" + ip,
"http": "http://" + ip,
}
with requests.get(url=url, proxies=get_proxies(proxies_all)) as resp:
resp.encoding = "utf-8" # 当页面乱码改这里
print(resp)
print(resp.text)
后续:线程,进程,协程
思来想去,还是把协程和抓取视频分开写吧,下一篇写协程。四、线程,进程,协程