前两天学习了Python的requests模块的相关内容,对于用GET和PSOT请求访问网页以抓取需要的内容有了初步的了解,想要再从一些复杂的网站积累些经验。最开始我采用最简单的get(url)方法想要抓取知乎热搜的标题,想着是个很简单的任务。但是耗费了我五天的时间才堪堪解决模拟登录知乎的问题,期间还查阅了十几个相关网站,解决了一堆问题,还没有实现抓取热搜的目的,不过最难的一步解决了,相信之后的提取网页内容的问题也会解决。

至于为什么学习内容会从“抓取知乎热搜”变成“模拟登录知乎”,是因为知乎它比较坑,不登录的话不显示热搜的内容,哪怕你访问了热搜的URL——www.zhihu.com/hot,它也给你跳到登录页面——www.zhihu.com/signin,所以要想抓取内容,就绕不开登录知乎。

也许还有其他简单的模块selenium,但本次并没有涉及到,等到以后有机会再去学习了解^_^。

接下来我就按照步骤说明我是如何一步一步的在前人文档的帮助下模拟登录知乎的,也许以后写下的代码会随着知乎的更新而失效,但是在此过程中的思想和思考方式却对类似问题的解决有所帮助。

一、查看请求的Headers

我们首先要清楚登录请求是发给谁的

①打开知乎登录页面,输入错误的账号和密码;同时F12打开开发者模式,监视在此过程中发送的各项请求。

②看到一个POST请求sign_in,看一下它的Response

说明没错,就是这个Post请求;一般Post请求都会带一个FormData,检查后边,发现这个请求也不例外

但是这个参数不是常见的Key:Value格式,说明做了加密处理,那么我们现在就需要找到它的加密逻辑,然后模拟出这个参数。

二、加密方法

①加密函数

目前已知的就只有这个sign_in,在开发者模式的Source模块中,通过全局搜索CTRL+SHIFT+F搜索一下sign_in

只有这么一个js文件里边有sign_in,点进去之后,通过CTRL+F看看sign_in在这个文件中具体在哪。这里需要注意的是,网页为了节省资源,通常会省去语法格式,而将大量代码挤到一段中去,比如这样:

这样我们的开发就显得十分不便,好在我们可以通过左下方的一个按钮来还原它们的格式:

接着正文讲,我们在这个js文件中搜索到的sign_in有三个。可以通过调试+设置断点的方法判断哪个是我们需要的。这里就不贴调试的过程了。

运行到其中一个断点处,通过右边的Scope监视局部变量Local,可以发现这部分的局部变量包含了很多信息,可能以后会用到它。

信息有了相当于加密函数的部分参数就有了,那么这些参数是通过哪个函数被加密从而形成了FormData了呢?这里的加密方法的寻找,我参考了别人的方法:加密一般都是用encrypt之类的名字,所以可以直接搜索encrypt:

把鼠标停留在这个return后边的值,会自动显示这个值;将它与FormData相比较,发现一模一样,说明调用return后边的代码,起到了把信息加密的作用。

②加密函数的输入参数

接上文,如果我们把鼠标放在参数e上,

可以得到加密函数的输入参数,即下面这些参数用&连接构成的字符串:

client_id=c3cef7c66a1843f8b3a9e6a1e3160e20
grant_type=password
timestamp=1603276798679
source=com.zhihu.web
signature=d570b4b3cd3b7e473933ed5e9a10f714c383aa81
username%2B8615947657687
password=1111111
captcha=5e9x
lang=en
utm_source=
ref_source=other_https%3A%2F%2Fwww.zhihu.com%2Fsignin%3Fnext%3D%252F

其中只有timestamp、signature、captcha是会变化的,经过分析,发现:

timestamp:13位时间戳——Python自带的时间戳函数生成的是10位时间戳,乘以1000即可

captcha:动态输入的验证码——验证码的获取会在第三大部分的第②模块请求验证码中介绍,你也可以直接跳过去看。

signature:则是一个经过加密的属性,它和captcha都要通过额外编写函数来获取,不是可以直接得到的。但是获取方法并不难,下面介绍如何获取signature:

step1、在网页源码中,全局搜索signature,寻找看上去像进行加密的位置

step2、再调试一次,分别在该处和之前的加密函数encrypt处设置断点,观察encrypt的输入参数e的signature的值是否和该处signature的值相同,比较结果如下:

一模一样,看来这里就是给signature进行加密的地方了。这就有两个问题了——如何加密,对谁加密。

观察这部分代码,可以得到上边两个问题的答案:

a、如何加密——HMac加密模式,Hash函数:SHA-1

b、对谁加密——就是面代码中的a

a的内容呢?

可以看到,一共5项,分别是①“d1b964811afb40118a12068ff74a12f4”;②"password";③clientID;④"com.zhihu.web"⑤timestamp

所以我们在Python中编写对Signature的加密函数时,就是利用上面提到的五项参数和HMac函数进行的。这部分的代码如下:

def get_signature(self):
#获取Signature
clientID=b'c3cef7c66a1843f8b3a9e6a1e3160e20'
SK=b'd1b964811afb40118a12068ff74a12f4'
h=hmac.new(SK,digestmod=hashlib.sha1)
h.update(clientID)
h.update(b'password')
h.update(b'com.zhihu.web')
h.update(self.timestamp.encode())
return h.hexdigest()

知道了加密函数的位置,我们就可以把参与加密的所有js方法都提取出来,放在一个html文件内执行就可以了。

向上寻找这个return所在函数的头,把这个函数的内容全部复制到一个JS文件中, 总共400多行。

为了看看这个函数正确与否,我们可以把函数中的内容直接拿出来,就是去掉最外层的function(module,exports,webpack_require),并把exports相关代码去掉(不去掉exports的话,是无法输出到html文件中的)。然后调用下面的函数b,把我们的FormData传进去。

将上边的JS文件嵌入一个html文件中,放在script标签内即可。

这里贴出检验时的HTML文件和JS文件的写法:

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body style="word-break:break-all;">
<h1>获得参数</h1>
<script type="text/javascript" src="T.js">
</script>
</body>
</html>

T.js

//中间代码和之前400多行代码相同,我就不写了,只是注释掉所有和exports相关的代码,注意是 "所有",你可以通过CTRL+F查找与exports相关的代码
var b = function (e) {
return __g._encrypt(encodeURIComponent(e));
};
document.write(b("client_id=c3cef7c66a1843f8b3a9e6a1e3160e20&grant_type=password&timestamp=1603252105039&source=com.zhihu.web&signature=1f47545c2389e3356a47837daadef46d63c8530d&username=%2B8615947657687&password=111111&captcha=&lang=en&utm_source=&ref_source=other_https%3A%2F%2Fwww.zhihu.com%2Fsignin%3Fnext%3D%252F"))

用浏览器打开,就可以看到加密的字符串也就是发送的FormData了。它与我们之前Request Header中的FormData的值相同(这里由于我是第二天做的,所以和前一天的FormData不同)。

搞完这个后,我们就可以继续使用Python来操作了,因为加密方法格式化后有400多行,实在太多,也全都是混淆,不太可能用Python一个一个实现,所以这里选择用Python的execjs来直接执行JavaScript代码从而获得FormData,简单且方便。

不过要注意的是,前边我们写的JS文件是用来嵌入HTML并在网页中打开的,所以给这个JS文件提供的是Web环境。但是想在Python中通过execjs执行的JS是需要Node环境,所以需要对之前的JS代码稍加修改。

修改一、开头添加抬头

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
window = dom.window;
document = window.document;

修改二、atob => window.atob

之后运行一下JS检查一下结果(这里我是在Pycharm中执行的JS文件,环境是NodeJS。在这个JS文件最后一行用console.log(b("client=..."))代替我们之前写的document.write将FormData输出到命令行检查)

可见Node环境下配置正确,运行结果正确。

这就是提取的加密函数并且进行加密的部分。

另外还有个问题我们一直忽略了,那就是加密函数的参数问题,是的,光有加密函数还不行,你得知道它是对哪些参数进行了加密才得到了最后的FormData;换言之,我们现在只是知道了输入-处理-输出中的处理和输出部分,接下来,我们要看看输入的参数是哪些。

为什么我把输入放在最后说呢?是因为我们有个取巧的方法帮助我们获得这些参数,使我可以用较短的篇幅来说明有哪些参数。

①还是回到我们之前的encrypt部分的代码

三、模拟登录

①请求头headers信息

必须要有三个要素:User-Agent、Content-Type、x-zse-83

        headers.update({
'content-type': 'application/x-www-form-urlencoded',
'x-zse-83': '3_2.0',
'x-xsrftoken': self._get_xsrf(),
'User-Agent':'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
})

不带Content-Type,会出现错误:

Missing argument grant_type

不带x-zse-83,会出现错误:

请求参数异常,请升级客户端后重试

User-Agent就不用说了

②请求验证码

登录时的请求顺序是:

  • 请求验证码地址,看需不需要填写验证码,如果需要就再请求一次,而且还需要再再请求一次查看验证码输入的正确与否;不正确就重复上述步骤。不需要填写验证码的时候就可以直接请求登录网址了。

此外,上述三次请求的验证码地址的请求方法都是不同的,从前到后分别是GET、PUT和POST,且这三次请求的内容都是通过json编码,可以在请求之后通过json()方法解码为dict类型的对象。

cptcha验证码,是通过GET请求单独的API接口返回是否需要验证码(无论是否需要,都要请求一次),如果是True则需要再次PUT请求获取图片的base64编码。

知乎验证码有两种形式,一种是点击倒立文字,另一种是输入英文字符串。区别在于传入的参数lang是cn还是en。这里只实现第二种验证方式。

def getcapture(self):
api='https://www.zhihu.com/api/v3/oauth/captcha?lang=en'
message=self.session.get(api).json() #第一次请求
if message['show_captcha']=='False': #此时不需要验证码
self.picture=''
else:
print('需要验证码:')
while True:
self.picture_url=self.session.put()api.json()#第二次请求,获得图片的base64编码
with open('captchar.jpg','wb') as f:
f.write(base64.b64decode(self.picture_url['img_base64']))
image=Image.open('captcha.jpg')
image.show()
self.picture=input('请输入验证码:')
time.sleep(2)
message1=self.session.post(api,data={'input_text':self.picture}) #第三次请求,POST提交验证码
if message1.status_code==201:
break
else:
print(f'{message1.status_code} 请提交正确的验证码')

四、补充内容

a、验证验证码

验证验证码时的请求头只需要有User-Agent字段就可以了。

b、请求的所有阶段都要带上Cookie

知乎的Cookie值是验证码票据,来源于第一次GET请求验证码地址,就是第一次GET请求的Response头部中的set-cookie要素中的那个。

如果不带Cookie请求或者请求顺序不一样就有可能返回错误:

{"error":{"message":"缺少验证码票据","code":120002,"name":"ERR_CAPSION_TICKET_NOT_FOUND"}}

参考(按对我帮助由大到小排列):

最新 Python 模拟登录知乎

知乎最新版模拟登陆详解

2020年最新 Python 模拟登录知乎 支持验证码

爬虫之爬取B站视频及破解知乎登录方法(进阶)

python爬虫执行js代码-execjs

代码来自Github:

2020 年最新 Python 模拟登录知乎 支持验证码和 Cookies

2020.10.20 利用POST请求模拟登录知乎的更多相关文章

  1. 利用POST请求模拟登录豆瓣

    需要用requests库 豆瓣上次更新后,就不能通过直接的requests.post()方式直接传递参数登录了.必须新建session,先GET请求,然后POST才能成功.原因未知 data参数中的四 ...

  2. 利用scrapy模拟登录知乎

    闲来无事,写一个模拟登录知乎的小demo. 分析网页发现:登录需要的手机号,密码,_xsrf参数,验证码 实现思路: 1.获取验证码 2.获取_xsrf 参数 3.携带参数,请求登录 验证码url : ...

  3. Python post请求模拟登录淘宝并爬取商品列表

    一.前言 大概是一个月前就开始做淘宝的爬虫了,从最开始的用selenium用户配置到selenium模拟登录,再到这次的post请求模拟登录.一共是三篇博客,记录了我爬取淘宝网的经历.期间也有朋友向我 ...

  4. urllib库利用cookie实现模拟登录慕课网

    思路 1.首先在网页中使用账户和密码名登录慕课网 2.其次再分析请求头,如下图所示,获取到请求URL,并提取出cookie信息,保存到本地 3.最后在代码中构造请求头,使用urllib.request ...

  5. requests_模拟登录知乎

    如何登录知乎? 首先要分析,进行知乎验证的时候,知乎服务器需要我们提交什么数据,提交的地址.先进行几次登录尝试,通过浏览器中network中查看数据流得知,模拟登录知乎需要提供5个数据,分别是_xsr ...

  6. Python爬虫初学(三)—— 模拟登录知乎

    模拟登录知乎 这几天在研究模拟登录, 以知乎 - 与世界分享你的知识.经验和见解为例.实现过程遇到不少疑问,借鉴了知乎xchaoinfo的代码,万分感激! 知乎登录分为邮箱登录和手机登录两种方式,通过 ...

  7. 【爬虫】python requests模拟登录知乎

    需求:模拟登录知乎,因为知乎首页需要登录才可以查看,所以想爬知乎上的内容首先需要登录,那么问题来了,怎么用python进行模拟登录以及会遇到哪些问题? 前期准备: 环境:ubuntu,python2. ...

  8. Python爬虫入门(基础实战)—— 模拟登录知乎

    模拟登录知乎 这几天在研究模拟登录, 以知乎 - 与世界分享你的知识.经验和见解为例.实现过程遇到不少疑问,借鉴了知乎xchaoinfo的代码,万分感激! 知乎登录分为邮箱登录和手机登录两种方式,通过 ...

  9. 利用jsoup进行模拟登录

    因为工作的原因,近段时间开始接触jsoup.大概也弄清了用java来爬网页是怎样一个过程.特此,写篇日志以便他日方便查看. Jsoup是一个java平台的能够对xml文档结构的文档进行解析.有点类似于 ...

随机推荐

  1. DBeaver下载安装与连接MySQL数据库

    一.Dbeaver下载 官网下载地址:Download | DBeaver Community 点击"Windows 64 bit (installer)"即可进行下载. 二.Db ...

  2. MySQL语句SQL应用

    目录 一:sql语句 1.什么是SQL语句? 二:基本SQL语句之库操作 三:基本SQL语句之表操作 1.查看当前所在库名称 2.切换数据库 四:基本SQL语句之记录操作 五:创建表的完整语法 一:s ...

  3. Azure AD Domain Service(一)将 Azure VM 实例添加到域服务里

    一,引言 有网友提到实际项目中如何将 Azuer VM 实例加入到 Azure AD 域,那我们今天就带着整个问题开始今天的分析!首先我们得了解什么是 Azure AD 域服务,Azure AD 域服 ...

  4. eureka 集群的实现方式?

    注意,本文还是对上一篇博客的延续,需要的配置,在前面的博客里面可以找到. eureka集群版 (正宗的eureka!) 2.1.配置eureka的集群之前首先先配置HOSTNAME和IP的映射 具体的 ...

  5. CEH v8~v11 Module Slides 和 Lab Manual 下载

    课程内容 CEH v8 01 Introduction to Ethical Hacking CEH v8 02 Footprinting and Reconnaissance CEH v8 03 S ...

  6. Sklearn 与 TensorFlow 机器学习实用指南第二版

    零.前言 一.机器学习概览 二.一个完整的机器学习项目 三.分类 四.训练模型 五.支持向量机 六.决策树 七.集成学习和随机森林 八.降维 十.使用 Keras 搭建人工神经网络 十一.训练深度神经 ...

  7. 错误代码 insufficient-isv-permissions 错误原因: ISV权限不足

    前言 PC网页吊起支付宝支付界面报了如下这么个错误: 我明明申请的支付宝支付功能列表的签约已经生效了呀!怎么回事呢? 解决 支付宝商户中心:https://b.alipay.com/signing/p ...

  8. JS let, var, const的用法以及区别

    本文摘自多位前辈的博文,另外还有一些我的多余补充,摘自地址已补充.非常感谢各位前辈.仅以笔记学习为目的! 深入学习ES6的知识还请访问阮一峰老师的ES6教程 如果不使用let或者const,在JS只有 ...

  9. vue+element ui中select组件选择失效问题原因与解决方法

    codejing 2020-07-10 09:13:31  652  收藏 分类专栏: Web Vue Element UI 版权 .当表单form赋完值后,如果后续又对form中某一属性值进行操作如 ...

  10. 简介GitHub的使用方法--管理个人代码

    转自 http://blog.csdn.net/tengyeyijiu/article/details/46446283git是一个分布式版本控制系统,最初由linus torvalds编写,用作Li ...