开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第29天,点击查看活动详情
前言
现在很多网站的数据都需要用户登录之后才可获取到,那么在爬虫程序中,自动登录功能就必不可少了,而登录功能,最大的一个问题就在于验证码的验证,现在市面上验证码种类繁多,层出不穷,今天我们就先拿一个简单的案例来学习一下如何实现自动验证登录吧。
难点分析
以【古诗文网】为例,我们想要实现自动登录,首先应该找到该网站的登录接口,排查分析该接口中所需要的参数都有哪些,才好在程序中给出所需的参数来请求该接口进行登录。
请求接口分析
打开登录页面之后,按F12打开开发者调试工具界面,选择Network标签页,随便输入一下账号密码,发出请求来查看请求的地址以及所需参数。
这里注意不要点击确定按钮,不然页面会被重定向,刚刚发出的请求就看不到了。
查看请求参数
在请求中,我们可以看到登录的接口路径为:
https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx
所需的参数有:
__VIEWSTATE: qwbRIOrUgvgsbvwrVcTuanjeeM5S2bHNbwCykNpfMdcZH3pZGaYfTLndbo8j2O2BZRqVoJVn/r113tQ+nXdNMnd9MN6w5Gr8kdjhjDQnpwldGkCHj/+wKhUYFN8aTU8s3+YIa0nH6aTVMXEKsuyZDN4xDR0=
__VIEWSTATEGENERATOR: C93BE1AE
from: http://so.gushiwen.cn/user/collect.aspx
email: 11@qq.com
pwd: 111111
code: VMOL
denglu: 登录
排查动态变量
上面的参数可以看到,其中固定不变的有:
- from
- pwd
- denglu
还有三个参数:code、__VIEWSTATE、__VIEWSTATEGENERATOR,其中code是验证码,这个需要我们去请求到,还有另外两个参数看起来像随机的字符,这应该是该接口的隐藏域,一般是随机生成后存放在网页中隐藏的,我们可以看看它的网页源码,看能不能找到这两个参数。
源码中确实能得到这两个隐藏域的值,那么就好办了,我们可以直接解析标签来拿到这两个参数。
解析页面源码获取动态变量
import requests
from bs4 import BeautifulSoup
def login(email, pwd):
login_page_url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
response = requests.get(login_page_url)
html = response.text
soup = BeautifulSoup(html, 'lxml')
viewstate = soup.select('#__VIEWSTATE')[0]['value']
viewstategenerator = soup.select('#__VIEWSTATEGENERATOR')[0]['value']
print(viewstate)
print(viewstategenerator)
if __name__ == '__main__':
login('x@qq.com', '111111')
获取验证码
通过requests和bs4结合,我们顺利的拿到了页面中的这两个隐藏域的值了。接下来就应该解决验证码的问题了。同样还是老方法,在F12开发者工具栏中拿到验证码的请求接口。
请求该接口,拿到验证码之后将其保存到本地。
def get_code():
code_url = 'https://so.gushiwen.cn/RandCode.ashx'
response = requests.get(code_url)
file = open('code.png', 'wb')
file.write(response.content)
file.close()
ocr识别
上面的验证码我们是拿到了,也正常保存到本地下来了,但是如果还得再次去手动输入还是很麻烦,那么这里我们可以使用ocr库来自动识别里面的内容,这里我用的是ddddocr库。
安装:pip install ddddocr
import ddddocr
def orc(fileName):
with open(fileName, 'rb') as f:
img_bytes = f.read()
return ddddocr.DdddOcr().classification(img_bytes)
自动登录
前面隐藏域跟验证码都拿到了,那么登录所需要的各种参数已经齐全了,我们就可以来尝试着发起登录请求,看看能不能登录成功。
import requests
from bs4 import BeautifulSoup
import ddddocr
def login(email, pwd):
login_page_url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
response = requests.get(login_page_url)
html = response.text
soup = BeautifulSoup(html, 'lxml')
viewstate = soup.select('#__VIEWSTATE')[0]['value']
viewstategenerator = soup.select('#__VIEWSTATEGENERATOR')[0]['value']
login_url = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.116 Safari/537.36'
}
formData = {
'__VIEWSTATE': viewstate,
'__VIEWSTATEGENERATOR': viewstategenerator,
'from': 'http://so.gushiwen.cn/user/collect.aspx',
'email': email,
'pwd': pwd,
'code': get_code(),
'denglu': '登录',
}
response = requests.post(url=login_url, headers=headers, data=formData)
f = open('login.html', 'w', encoding='utf-8')
f.write(response.text)
def get_code():
code_url = 'https://so.gushiwen.cn/RandCode.ashx'
response = requests.get(code_url)
file = open('code.png', 'wb')
file.write(response.content)
file.close()
return orc('code.png')
def orc(fileName):
with open(fileName, 'rb') as f:
img_bytes = f.read()
return ddddocr.DdddOcr().classification(img_bytes)
if __name__ == '__main__':
login('xxxx@qq.com', '111111')
session
前面的所有流程都没有什么问题呀,验证码也是校验过的并没有输入错误,那么为什么它会提示我们验证码有误呢?
这其实是我们请求的验证码和登录接口并非同一个会话导致的,在网站的一些请求中,存在着session会话域,只有同一个session中的请求他们直接的数据才能共享,不同session在服务端看来就是不同的人在请求。
完整代码
前面的几个代码片段中,我们需要将所有发出的请求全部保存在同一个session中:
- 请求隐藏域
- 请求验证码
- 请求登录
import requests
from bs4 import BeautifulSoup
import ddddocr
def login(email, pwd):
session = requests.session()
login_page_url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
response = session.get(login_page_url)
html = response.text
soup = BeautifulSoup(html, 'lxml')
viewstate = soup.select('#__VIEWSTATE')[0]['value']
viewstategenerator = soup.select('#__VIEWSTATEGENERATOR')[0]['value']
login_url = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.116 Safari/537.36'
}
formData = {
'__VIEWSTATE': viewstate,
'__VIEWSTATEGENERATOR': viewstategenerator,
'from': 'http://so.gushiwen.cn/user/collect.aspx',
'email': email,
'pwd': pwd,
'code': get_code(session),
'denglu': '登录',
}
response = session.post(url=login_url, headers=headers, data=formData)
f = open('login.html', 'w', encoding='utf-8')
f.write(response.text)
def get_code(session):
code_url = 'https://so.gushiwen.cn/RandCode.ashx'
response = session.get(code_url)
file = open('code.png', 'wb')
file.write(response.content)
file.close()
code = orc('code.png')
print(code)
return code
def orc(fileName):
with open(fileName, 'rb') as f:
img_bytes = f.read()
return ddddocr.DdddOcr().classification(img_bytes)
if __name__ == '__main__':
login('xxxx@qq.com', '123456')
结尾
将登录请求的响应页面源码保存到本地之后,打开查看,可以看到是登录成功的状态了。在该案例中,我们需要注意的地方有:
- 隐藏域的数值获取
- 验证码获取与识别
session统一请求
温馨提示:本案例仅供学习参考,切勿暴力爬取或用于其它非法途径。发生任何纠纷与本人无关,特此声明!
让我们一起弘扬中华民族优秀的传统文化!