爬虫之抓js教程
2019-07-13 17:07
1636 查看
在初学的爬虫过程中,很多人还不知道有些字段是如何生成的,怎样模拟生成这些字段来拼接头部。为了再次纪念【宏彦获水】成语初次面世,特地用【百度登陆】写下一篇登陆百度的教程,以供大家参考。
前面学习了如何在
get的时候想服务器发送多变的请求数据,从而达到搜索的效果,而实际上
搜索是简单的登陆!所以本文将要介绍如何向百度服务器发送
post数据,从而达到模拟登陆百度的效果。
- 首先打开
firefox浏览器,清除网页所有的历史纪录,这是为了防止以前的Cookie影响服务器返回的数据。 F12打开firebug,进入百度首页,点击 网络 -> 清除 ,这是为了删掉打开百度首页而弹出来的html,方便后面的查找html数据。- 点击登陆按钮,依次填写账号、密码、验证码,点击 登陆 ,在
firebug中点击保持,这是为了防止登陆成功后,登陆表单的html被清除。
在
firebug中,找到如下一行
POST?login:
点击前面的
+号 ->
post,可以看到提交的表单,这个就是点击登陆后,网页向百度服务器后端发送的 登陆请求表单,表单中包含了 账号、密码、其他 等信息:
如果百度后台认为此 登陆表单请求 是正确的后,会在 头信息 -> 响应头信息 中返回一个
Set-Cookie。当我们登陆成功后,关闭浏览器,下次再打开浏览器的时候发现百度还是处于一种登陆的状态,这就是和
Cookie有关。在百度登陆成功后会返回一个
Cookie储存到浏览器中,下次再打开百度的时候,浏览器中的 头信息 -> 请求头信息 中会携带一个
Cookie,这个
Cookie就是百度服务器判断你以前是否登陆过百度。而这个
Cooike就是
Set-Cookie加工而来的!那么重点来了,如果要用代码模拟登陆百度,应该要具备以下几个步骤:
- 构造请求表单
- 请求成功后获取
Cookie(这个Cookie并非Set-Cookie)- 在请求头部
header中携带这个Cookie,就可以以登陆过后的身份访问百度
原理讲清楚了,那么下面开始实践!
构造请求表单
在上面的
POST?login中发现百度的请求表单还是挺多的,那么如何表单中判断哪些是变化的那些事不变的?再一次清空
firefox的全部历史纪录,清除
firebug的
html,重新在百度首页点击 登陆 ,填写 账号、错误的密码、验证码,复制
POST?login中的
post信息下来,然后重复前面的步骤,就可以得到很多
post信息,拿出来对比就可以知道哪些信息是变化的了。这里要解释一下为什么要填写 错误的密码,因为密码错误啦,登陆框就会一直都在啊,免去了清除 全部历史纪录 和清除
html的步骤。最后对比的情况如下:
可以发现,请求的表单有
staticpage charset token tpl subpro apiver tt codestring safeflg u isPhone detect gid quick_user logintype logLoginType idc loginmerge splogin username password verifycode mem_pass rsakey crypttype ppui_logintime countrycode fp_uid fp_info loginversion ds tk dv traceid callback
其中,被红框框起来的表单是多次请求变化的:
callback tt token ppui_logintime rsakey verifycode
而
ds、
tk在第一次请求网页直接生成了:
https://passport.baidu.com/viewlog?ak=1e3f2dd1c81f2075171a547893391274&as=74a154e4&fs=MEBGsUNpNVBMjs8Tdudr8aAjW%2BFklVpfnDnMkmxZ3DMXZisUPveYoxrXH2HKd5pgidlfhvibzXjlMLk28ZUvrUpDazz2aivj1lT0DHKRrMLZBBvYrMBdY%2BenNlaoF8h%2F8s18t0DQtONJZoRMOt%2FDotooaXA1bPuODU3XkP5iOBv9GpK6mApUn2xQXIpSEFTInDKJEiFBfC04IfPyCVCe766QJT%2FS4CHeqIJsjVLa7aoNnh3%2BHSdvRx1Uay1Fy60q%2Fkz5TJ%2B8Ib25o8yDfFBcOdbIdhVwmDHp3R87v3%2BY0M9rl2MUlr4ZJO2vn98yspz9t60LrqhUsObz7FZIdG9sWRP6JNt00%2BeQIY6Z7liRZI75mSRTWGDHYMT8LU7KdOELrxdrM7OfHfoD%2BlJ8PpCPFPT8dOgJUKGwa0tkL6t5UKpOUUXoxbx3lkRUNSj5NxdNcRt3YZbDShJmXnRbfza7yDpgvzKBRULis%2BzxhbBijS5onMCPOB59OVGE6lges8nr9xhi0ZNM9f96V7S4elo4fsXUgQzmJJwsM69ah0RSVNFQbBNoGszbT47%2BHDORP%2Fd7OLGOeG8D9i5tMIf%2BYRgN6ing5B5lLpn5nn3KtshIWiAwrR5mijWZai7uheFiE2cHCovVBRAlfCp3yDtKRWN4cE55F9b0wvoDHSJmHqlVKp1%2BgbE9b1oUFmqOGWcWMakVQfrEFg6phufPuuaQLLdtX3%2Bir7yeC8rx8tHdcTz6CtJsWtVcavFV8Q8j8Ta90bSKp%2BjQlmOXmct7PeM3tRM8%2B946o67jwNX7CP1EjKw%2FYk5lP%2BmCqNjwK3eZf46pQGLmZYUOLuGBK73HeCPAlj4YlEfGrZYpCuLp1vthWK%2B5oZZR9c%2BLpu1aOGotEqebe2N6UaKbXhC2qn6h3glylAV%2B2HfY4wut%2Bj%2Frr3iJEhWLj7J7qD0fr5ojR993ru8qrZSxKYu1f5W6NhdGPz7ZpWRfBrIaxtMjliEgdrIZ82RSe930OeXJaXMzytvoxvsZaUYvODivXMsPXDlnEQ%2FiZPPAO1B3F06Y8so67piru9hrXdkBwGLP6G07wo2dCMPvSFHHuLSvFYWduRFscftm3qJ1XUSDHDYIe8t5y5ClLJJd%2FCAkdlhQc3iOQJUgOXp4tAjoSkkiLnramq%2BIa6ycbi%2BcfzE6recOWVsuTFC4rX0t4RLdY5yf%2BRkED6qYcR8LLorK0dVKTX34rRsvLFElzgbi%2FW1%2Fq8y8tU9X%2F3pQXzHEsw28si6pjHvbPd4rJoQTIoI5asbCbxKqjRCJCfJPXRbUxo%2BZeWwik4F5UiTzwpas3pQ%3D&callback=jsonpCallbackb5819&v=4646
直接返回结果
json,所以得到
ds、
tk:
jsonpCallbackb5819({"code":0,"data":{"tk":"1966\/wzQ9fSm481E1Dd6MPpdaM08fX2AB5MkNq1aMZHDBoekhU51\/8+yOdlYGlLXJVKpduaYRnOVNhfERmTiBXB1Vw==","as":"a82478bc","ds":"oLZa10fYIvKavmDHTaWvTF5D9f3NBzweejdgFGUJB9yI6TFVGHZ8EtWhXcLshwfDL0sU7ymlQe3uVByWIXCym03HZTZxZmGaXl8Jw+unuO5D3SN29KiMO0oj1fSH58BU"}})
其中很明显可以看出来的是:
tt时间戳 verifycode验证码
那么就剩下几个请求表单还暂时无法得知,首先查找
callback:
>1. 在 `firebug` 中勾选脚本,点击 `{}` >2. 搜索中勾选 **多个文件** >3. 在搜索框中搜索 `callback`
一直搜索,最后发现
callback和
getUniqueId的生成有关:
那么换着
getUniqueId来搜索,得到如下
javascrip代码:
e.getUniqueId = function (e) { return e + Math.floor(2147483648 * Math.random()).toString(36) },
和上面的
JavaScrip整理起来,那么
callback的生成代码为:
function callback(){ return 'bd__cbs__'+Math.floor(2147483648 * Math.random()).toString(36) }
找到
callback后,接下来要去寻找
token。在
firebug中寻找
token第一次在哪里出现。最后发现首次出现在网址:
https://passport.baidu.com/v2/api/?getapi&tpl=mn&apiver=v3&tt=1495185331704&class=login&gid=63F95D8-F402-4128-A98B-C7D3C19B8F89&logintype=dialogLogin&callback=bd__cbs__3cagws
这个网址返回的是一个
Json:
bd__cbs__3cagws({"errInfo": {"no": "0"}, "data": {"rememberedUserName": "", "codeString": "", "token": "6245a75e6ba48d39033a8c31dfcb37c7", "cookie": "1", "usernametype": "", "spLogin": "rate", "disable": "", "loginrecord": {'email': [], 'phone': []}}})
第一次
token出现的地方找到了,那么分析一下请求出
token的网址,网址中涉及到的变量有:
tpl apiver tt class gid logintype callback
为了查看哪些变量是变化的,就再次进行多次登陆。最后发现,变化的是:
tt gid callback
其中
tt为长时间戳,
callback在前面已经找到并且能生成了,那么只剩下
gid这个变量。老规矩,按照下面的步骤再去找出
gid:
- 在
firebug中勾选脚本,点击{}- 搜索中勾选 多个文件
- 在搜索框中搜索
gid
搜索发现
gid是由
gid: e.guideRandom这个函数生成的:
那么接着搜搜这个函数
guideRandom,找到如下
JavaScrip代码:
this.guideRandom = function () { return 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (e) { var t = 16 * Math.random() | 0, n = 'x' == e ? t : 3 & t | 8; return n.toString(16) }).toUpperCase() }(),
整理一下让其可以在
python中运行:
function gid(){ return 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (e) { var t = 16 * Math.random() | 0, n = 'x' == e ? t : 3 & t | 8; return n.toString(16) }).toUpperCase() }
这样,根据
gid和
callback就能得到
token了!
下面继续寻找
ppui_logintime,按照规矩来:
- 在
firebug中勾选脚本,点击{}- 搜索中勾选 多个文件
- 在搜索框中搜索
ppui_logintime,找到了timeSpan: 'ppui_logintime'
接着搜索
timeSpan,得到如下信息:
s.timeSpan = (new Date).getTime() - e.initTime
现在还是看不出什么东西,那么就继续搜索
initTime,得到如下代码:
_initApi: function (e) { var t = this; t.initialized = !0, t.initTime = (new Date).getTime(), passport.data.getApiInfo({ apiType: 'login', gid: t.guideRandom || '', loginType: t.config && t.config.diaPassLogin ? 'dialogLogin' : 'basicLogin' }).success(function (n) { var i = t.fireEvent('getApiInfo', { rsp: n });
继续查找
initApi,找到位置:
原来是在登陆的时候发生点击,内容改变,按键按下等事件的时候,会调用
_initApi,再看源码和
ppui_logintime的数值可以发现,
ppui_logintime代表的是从输入信息开始到点击登陆后结束的时间差!那么在后面
post的时候直接可以自己构造这个数据了。
那么最后还剩下一个变量
rsakey,在查找网页的时候发现第一次出现
rsakey的地方是:
https://passport.baidu.com/v2/getpublickey?token=fcd1f6684072372c6812a44c1d94bf51&tpl=mn&apiver=v3&tt=1461222170065&gid=C539A37-9B0C-4538-9920-E150AC6AE0D5&callback=bd__cbs__tpdrlq
也就是这里面的
key,虽然名字不一样,但是值是一样的:
bd__cbs__tpdrlq({"errno": '0', "msg": '', "pubkey": '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDiA2HxDW2iVnunx7faCBG3YGBy\nvvF+ysFAIXIVjFTseU7x\/f+Gpr1VTWe2Kxc2dlzBkn5NuRHVxbyXCawu0QlMUfb8\nI2ukM1cIlL0e+B1nBnIp03oXjFvQNhIu58SI6vCoihWX6Qwhb6ZOvJdA249zCNBU\nlTyd7RVwgwaAthI6gQIDAQAB\n-----END PUBLIC KEY-----\n', "key": 'wS27H0665CWXK64i2VP02AYtjQUTujkb'})
分析一下这个网址,出现的变量有:
token tpl apiver tt gid callback
这些变量在前面都能找到,所以也就是说,根据
gid、
callback、
token就能得到
rsakey了!
下面开始讲解怎么用
python来实现模拟百度登陆!首先需要构造一个持续登陆的链接,持续登陆的链接能够一直自我保存打开网页或者登陆后留下的
cookie,当然这是存放在内存中的,例如:
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0"} session = requests.session() session.get("https://passport.baidu.com/v2/?login", headers=headers)
session就是一个持续的链接,一般用
python访问网页是这样子调用的
requests.get,这样子的访问不会保存历史访问留下的
cookie,而用
session.get能持续保存
cookie,只要后面访问都用
session.get或者
session.post即可。
获取callback
callback是由
JavaScrip生成的:
function callback(){ return 'bd__cbs__'+Math.floor(2147483648 * Math.random()).toString(36) }
所以直接在
python中运行这个
JavaScrip就行了。
python运行
JavaScrip需要安装库
pyexecjs,在命令指示符下直接输入
pip3 install pyexecjs即可。调用方式为:
import execjs js = '''function callback(){ return 'bd__cbs__'+Math.floor(2147483648 * Math.random()).toString(36) } ''' ctx = execjs.compile(js) callback = ctx.call("callback")
获取traceid
traceid同样是可以用
JavaScrip生成的,直接调用即可:
import execjs js = '''function traceid(){ var e = {a: 1, b: 1, c: 1} e.traceID = { headID: e.traceID && e.traceID.headID || "", flowID: e.traceID && e.traceID.flowID || "", cases: e.traceID && e.traceID.cases || "", initTraceID: function(e) { var t = this; e && e.length > 0 ? (t.headID = e.slice(0, 6), t.flowID = e.slice(6, 8)) : t.destory() }, createTraceID: function() { var e = this; return e.headID + e.flowID + e.cases }, startFlow: function(e) { var t = this , n = t.getFlowID(e); 0 === t.flowID.length || t.flowID === n ? (t.createHeadID(), t.flowID = n) : t.finishFlow(n) }, finishFlow: function() { var e = this; e.destory() }, getRandom: function() { return parseInt(90 * Math.random() + 10, 10) }, createHeadID: function() { var e = this , t = (new Date).getTime() + e.getRandom().toString() , n = Number(t).toString(16) , i = n.length , s = n.slice(i - 6, i).toUpperCase(); e.headID = s }, getTraceID: function(e) { var t = this , n = e && e.traceid || ""; t.initTraceID(n) }, getFlowID: function(e) { var t = { login: "01", reg: "02" }; return t[e] }, setData: function(e) { var t = this; return e.data ? e.data.traceid = t.createTraceID() : e.url = e.url + (e.url.indexOf("?") > -1 ? "&" : "?") + "traceid=" + t.createTraceID(), e }, destory: function() { var e = this; e.headID = "", e.flowID = "" } }; e.traceID.initTraceID() e.traceID.createHeadID() return e.traceID.createTraceID() + "01" }''' ctx = execjs.compile(js) traceid = ctx.call("traceid")
获取gid
gid同样是可以用
JavaScrip生成的,直接调用即可:
import execjs js = '''function gid(){ return 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (e) { var t = 16 * Math.random() | 0, n = 'x' == e ? t : 3 & t | 8; return n.toString(16) }).toUpperCase() }''' ctx = execjs.compile(js) gid = ctx.call("gid")
获取时间tt
时间
tt是一个毫秒级别的长时间,而
python生成的时间戳是短时间,所以要在短时间戳后面加上毫秒的长度即可,这里处理的方法是:在短时间戳的后面加上
3位数的随机数,从而构造出长时间戳。
import time import random timerandom = random.randint(100, 999) nowtime = int(time.time()) tt = str(nowtime) + str(timerandom)
获取token
有了
callback、
gid、
tt后,可以获取到
token,构造请求参数:
tokendata = { "tpl": "pp", "subpro": "", "apiver": "v3", "tt": tt, "class": "login", "gid": gid, "logintype": "basicLogin", "callback": callback }
更新头部,携带头部访问:
headers.update(dict(Referer="http://passport.baidu.com/", Accept="*/*", Connection="keep-alive", Host="passport.baidu.com")) resp = session.get(url="https://passport.baidu.com/v2/api/?getapi", params=tokendata, headers=headers)
顺利抓到包含
token的返回值,将其放到字典里面即可:
data = json.loads(re.search(r".*?\((.*)\)", resp.text).group(1).replace(""", """)) token = data.get('data').get('token')
顺利返回
token!
获取rsakey
获取
rsakey只需要构造如下请求参数即可:
tt = get_tt() get_data = { "token": token, "tpl": "pp", "subpro": "", "apiver": "v3", "tt": tt, "gid": gid, "callback": callback, }
在得到的返回值中提取出
rsakey和
pubkey既可:
tt = get_tt() rsakeydata = { "token": token, "tpl": "pp", "subpro": "", "apiver": "v3", "tt": tt, "gid": gid, "callback": callback, } resp = session.get(url="https://passport.baidu.com/v2/getpublickey", headers=headers, params=rsakeydata) data = json.loads(re.search(r".*?\((.*)\)", resp.text).group(1).replace("'", '"')) dicts = {} dicts["rsakey"] = data.get("key") dicts["pubkey"] = data.get("pubkey")
加密密码
百度登陆的密码加密方式是很简单的
RSA加密,这个只是用
JavaScrip就能实现,翻译成
python的代码为:
def base64_password(password, pubkey): pub = rsa.PublicKey.load_pkcs1_openssl_pem(pubkey.encode("utf-8")) encript_passwd = rsa.encrypt(password.encode("utf-8"), pub) return base64.b64encode(encript_passwd).decode("utf-8")
需要安装库
rsa,只需要在命令指示符下输入:
pip3 install rsa
登陆
构造一个登陆的
postdata:
post_data = { "staticpage": "https://passport.baidu.com/static/passpc-account/html/v3Jump.html", "charset": "utf-8", "token": token, "tpl": "pp", "subpro": "", "apiver": "v3", "tt": tt, "codestring": "", "safeflg": 0, "u": "http://passport.baidu.com/disk/home", "isPhone": "", "detect": 1, "gid": gid, "quick_user": 0, "logintype": "basicLogin", "logLoginType": "pc_loginBasic", "idc": "", "loginmerge": "true", "foreignusername": "", "username": username, "password": newpassword, "mem_pass": "on", # 返回的key "rsakey": rsakey, "crypttype": 12, "ppui_logintime": 33554, "countrycode": "", "dv": dv, "callback": "parent." + callback }
直接登陆:
resp = session.post(url="https://passport.baidu.com/v2/api/?login", data=post_data, headers=headers)
如果检测到自己在账号包含在返回的
html里面,则说明登陆成功:
if username in resp.content.decode("utf-8", "ignore"): print("登录成功") else: print("登录失败")
登陆成功后有两种方式在登陆状态下访问网页:
- 持续使用
session- 获取登录后的
cookie
第一种方法在本次程序跑完后就会自动将后台保存下来的
cookie丢弃掉,如果下次需要访问则需要重新登陆;第二种方法只要在头部增加这个
cookie值,就能一直使用
cookie保证是登陆状态,获取登录后的
cookie的方法为:
cookies = requests.utils.dict_from_cookiejar(session.cookies) print(cookies)
等到的
cookies为一个字典,将这个字典保存在本地的
json中:
import json file = open("cookie.json","w") file.write(json.dumps(cookies)) file.close()
下次访问携带这个
cookies即可:
json_file = open("cookie.json") cookies = json.load(json_file) json_file.close() header = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0', 'Host': 'passport.baidu.com', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3', 'Connection': 'keep-alive'} home_page = requests.get("https://passport.baidu.com/center", headers=headers,cookies=cookies).content.decode("utf-8", "ignore") print(home_page)
这里需要提醒的是
cookie会过期,一般是
7天,如果发现使用
cookie登陆失败,那么就需要重新使用账号密码登陆获取
cookie。
欢迎关注公众号【 机器学习和大数据挖掘 】,联系作者以及获取最新资讯
相关文章推荐
- <node.js爬虫>制作教程
- pyspider 爬虫教程(三):使用 PhantomJS 渲染带 JS 的页面
- [置顶] <node.js爬虫>制作教程
- 基于node.js制作简单爬虫教程
- 使用Node.js制作爬虫教程
- 使用Node.js制作爬虫教程(续:爬图)
- Python3网络爬虫教程8——有道在线翻译项目(JS加密)
- jsPDF的避坑教程
- 【HTML/JS】Pdf.js使用教程
- 爬虫、前端模块化、Node.js三种类型
- JavaScript强化教程 —— JS实现一个基本的打地鼠游戏
- [译] THREE.JS入门教程-2.着色器-上
- Windows系统下Node.js的简单入门教程
- 迷你MVVM框架 avalonjs 学习教程3、绑定属性与扫描机制
- Pyspider爬虫教程
- 看程序学Vue.js 14- AXIOS.JS 教程实例
- 微信小程序开发教程(基础篇)3-app.js 解析
- 从零开始学习Node.js系列教程三:图片上传和显示方法示例
- ReportStudio入门教程(六十) - JS-设置下拉框标题
- 廖雪峰JS教程学习记---数组和对象