【Python】requests模拟登陆房天下的总结
最近想爬取些与房价有关的数据,看了几个网站,感觉房天下包含的内容比较多,于是,先对房天下入手。为了保证后面数据爬取,想先模拟登陆获取cookies,维护一个cookies池来用,后续的爬取直接带cookies请求。
一,常规操作
一般的模拟登录,都是模拟网站登录请求的发送。打开房天下的登录页面,输入账号密码,再F12用来观察请求的发送,点击登录。
这里可以看到有个【login.api】,可以看到Request URL 为https://passport.fang.com/login.api,Method为POST。基本可以确定这就登录相关的请求,这里使用POST方法,那么POST什么?
在【login.api】的最底部有个【Form Data】,这就表单提交的内容:
uid:输入的账号,就是手机号,或者邮箱号
pwd:加密后的密码
Service:soufun-passport-web
AutoLogin:1
这几个参数中Service,AutoLogin都是固定的,uid就输入的账号,就pwd会在变。所以先要明白密码是如何进行加密的?
二,风骚操作
密码的加密最常用的应该就RSA加密,观察请求的顺序,在【login.api】之前没有任何与加密相关的请求发送,所以加密过程应该是在本地用js脚本加密。
在查看与【login.api】的js脚本时
有个【loginClickFn】,访问链接内容,看到一段与登录相关的代码:
所以,登录是通过ajax的post进行登录的,data里面就是【Form Data】的参数,
可以看到【pwd】是如何得到的。
pwd: encryptedString(key_to_encode, that.password.val()),
搜索这个脚本中没有找到与encryptedString,key_to_encode相关的东西。于是,返回登录的主页面中再搜索下,毕竟这个页面是登录的唯一入口,应该会包含相关信息。
在登录的页面中找到与RSA有关的js脚本,访问该链接,发现这是个RSA加密解密的脚本,贴出部分代码:两个关键的方法,其中一个加密函数是我们需要的。
function encryptedString(n, t) {
var e, o, s, h, c, i, f, u, v, l, y;
if (n.chunkSize > n.digitSize - 11) return "Error";
for (var a = [], p = t.length, r = 0; r < p;) a[r] = t.charCodeAt(r),
r++;
for (e = a.length, o = "", r = 0; r < e; r += n.chunkSize) {
for (c = new BigInt, s = 0, f = r + n.chunkSize > e ? e % n.chunkSize: n.chunkSize, u = [], i = 0; i < f; i++) u[i] = a[r + f - 1 - i];
for (u[f] = 0, v = Math.max(8, n.digitSize - 3 - f), i = 0; i < v; i++) u[f + 1 + i] = Math.floor(Math.random() * 254) + 1;
for (u[n.digitSize - 2] = 2, u[n.digitSize - 1] = 0, h = 0; h < n.digitSize; ++s) c.digits[s] = u[h++],
c.digits[s] += u[h++] << 8;
l = n.barrett.powMod(c, n.e);
y = n.radix == 16 ? biToHex(l) : biToString(l, n.radix);
o += y + " "
}
return o.substring(0, o.length - 1)
}
function decryptedString(n, t) {
for (var e = t.split(" "), i = "", r, u, o, f = 0; f < e.length; ++f) for (o = n.radix == 16 ? biFromHex(e[f]) : biFromString(e[f], n.radix), u = n.barrett.powMod(o, n.d), r = 0; r <= biHighIndex(u); ++r) i += String.fromCharCode(u.digits[r] & 255, u.digits[r] >> 8);
return i.charCodeAt(i.length - 1) == 0 && (i = i.substring(0, i.length - 1)),
i
}
接着继续寻找key_to_encode,也是在同个页面,
有个与key_to_encode相关的function :RSAKeyPair,往这个function传入参数后,就可以得到用于加密的key了,而RSAKeyPair正好也在刚才找到的js脚本中。
function RSAKeyPair(n, t, i) {
this.e = biFromHex(n);
this.d = biFromHex(t);
this.m = biFromHex(i);
this.digitSize = 2 * biHighIndex(this.m) + 2;
this.chunkSize = this.digitSize - 11;
this.radix = 16;
this.barrett = new BarrettMu(this.m)
}
综上,可以得到pwd加密的过程是执行一系列相关的js得到,先执行RSAKeyPair得到key_to_encode,再和密码一起传入encryptedString中完成加密。 所以在发送请求之前需要执行这一些的js。在这里不得不感叹下,python的库是真的强大。其中有个专门调用执行js的库,它可以加载整个js文件,然后调用文件中function。这个库就是PyExecJS
1,首先piip这个库,用的是douban地址
pip install PyExecJS -i https://pypi.douban.com/simple
2,修改下js文件,将前面的RSAjs脚本,复制内容保存为js格式文件,然后在文件最后添加以下代码:
var key_to_encode = new RSAKeyPair("010001", "", "978C0A92D2173439707498F0944AA476B1B62595877DD6FA87F6E2AC6DCB3D0BF0B82857439C99B5091192BC134889DFF60C562EC54EFBA4FF2F9D55ADBCCEA4A2FBA80CB398ED501280A007C83AF30C3D1A142D6133C63012B90AB26AC60C898FB66EDC3192C3EC4FF66925A64003B72496099F4F09A9FB72A2CF9E4D770C41");
function getpwd(t) {
return escape(encryptedString(key_to_encode,t))
}
由于文件太长,没把所有代码贴出来,需要的可以留言。
3,调用代码编写
def get_pwd(self):
#路径为js文件路径
f = open("E:\\PycharmProjects\\CookiesPool\\fang_password.js", 'r',encoding='UTF-8')
line = f.readline()
htmlstr = ''
while line:
htmlstr = htmlstr + line
line = f.readline()
jsstr = htmlstr
ctx = execjs.compile(jsstr) #加载js文件
return ctx.call('getpwd', self.password) #调用函数,返回加密后的结果
这样一波风骚操作,就获取到需要的pwd,后面就可以构建请求了。
三,迷之操作
下面开始构建请求,直接上代码:
import requests
import random
import execjs
from config import USER_AGENT_LIST
class FangLogin(object):
def __init__(self, username, password):
self.username = username
self.password = password
self.session = requests.session() # 登录用session
self.session.headers = {
'User-Agent': random.choice(USER_AGENT_LIST)
}
#获取密码加密后的结果
def get_pwd(self):
f = open("E:\\PycharmProjects\\CookiesPool\\fang_password.js", 'r',encoding='UTF-8')
line = f.readline()
htmlstr = ''
while line:
htmlstr = htmlstr + line
line = f.readline()
jsstr = htmlstr
ctx = execjs.compile(jsstr)
return ctx.call('getpwd', self.password)
#构建登录请求
def login(self):
#登录url
url = 'https://passport.fang.com/login.api'
#表单
data = {
'uid': self.username,
'pwd': self.get_pwd(),
'Service': 'soufun-passport-web',
'AutoLogin': 1}
headers = {
'User-Agent': random.choice(USER_AGENT_LIST) #随机抽取USER_AGENT
}
#发送请求
response = self.session.post(url=url, data=data, headers=headers)
#获取cookies
cookies = requests.utils.dict_from_cookiejar(response.cookies)
return cookies
if __name__ == '__main__':
fang = FangLogin('账号','密码')
print(fang.get_pwd())
cookies = fang.login()
# print(cookies)
#个人主页,用于测试cookies是否可用,判断是否成功登录
test_url = 'https://my.fang.com/'
headers = {
'User-Agent': random.choice(USER_AGENT_LIST)
}
#带cookies请求,禁止重定向跳转
response = requests.get(url=test_url, headers=headers,cookies=cookies, allow_redirects=False)
#根据返回的页面判断是否成功访问个人主页
print(response.text)
运行结果:
返回的页面是一个重定向跳转!!!说明没有登录成功,cookies没生效,你要访问个人主页,要先登录。后面一直在找问题,请求的代码也没有问题,一直找不到原因,后面想起之前说的ajax,post,于是,查了查ajax,post的原理,没有完全看懂,但是都有提到XmlHttpRequest,在Ajax应用程序中,XmlHttpRequest对象负责将用户信息以异步通信地发送到服务器端,并接收服务器返回的响应信息和数据。
好像在哪里看到过XmlHttpRequest,没错,就是在Request Header中
于是,不敢偷懒,把headers中补全,
headers = {
'User-Agent': random.choice(USER_AGENT_LIST), #随机抽取USER_AGENT
'X-Requested-With': 'XMLHttpRequest',
'Host': 'passport.fang.com',
'Origin': 'https://passport.fang.com',
'Referer': 'https://passport.fang.com/?backurl=http%3a%2f%2fmy.fang.com%2f'
}
再次请求,返回了个人主页的内容,说明cookies有效,登录成功。
但是,这波迷之操作背后的原理,还是不太明白。
四,总结
1,通过本次的练手,多了一种破解密码加密,模拟登录的思路,日后遇到其他的方式也可以博客记录下,形成自己的体系。
2,爬虫之所以难,之所以牛逼,估计就在与你知识面需要很广,基本的HTTP,ajax,js,html等等。
最后,如果有其他的更简单的方法,或者有什么错误,可以分享交流!!