Python 网络爬虫¶
Python实现单点登录(SSO)¶
CAS协议流程:
第1步: 浏览器正常发起Web服务请求(带过期cookie或者无cookie)
第2步: web服务发现cookie无效, 重定向请求到CAS登录界面进行认证
第3步: 用户填写认证信息后, 将认证信息POST给CAS服务器
第4步: CAS服务器判定认证信息有效, 生成一个Cas-cookie, 并返回一个一次性有效ticket
第5步: 浏览器存储cas-cookie, 并带上ticket再次访问web服务
第6步: web服务会请求CAS服务器以验证ticket有效
第7步: CAS验证ticket有效后CAS即删除ticket, 返回结果给web服务
第8步: web服务生成一个service-cookie, 并返回web资源给浏览器
- 分析网页
使用Chrome浏览器打开登录页面, 右键->检查, 打开分析工具, 在Network选卡上可以看到当前浏览器显示页面和提交登录信息的详情

输入账号密码, 点击登录, 当我们点击登录的时候, 因为我们还没有登录过, SSO会把我们自动重定向到登录页面, 所以http status是302重定向
从分析可以看到, 登录按钮提交的时候会用POST方式提交一个表格, 而表格里面的除了账号密码等显眼的字段以外还有一个lt, 经验告诉我们这个字段应该隐藏在之前的登录页面上, 用来校验登录页面的合法性, 所以我们要从登录页面上找到并提取这个信息. 同时还要注意http的消息头, 最好按照浏览器抓取的消息头去构造, 因为网站同样会校验这里面的信息.

实现代码:
import os
import sys
import time
import json
import requests
import urllib3
from lxml import etree
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sess = requests.session()
sess.trust_env = False # faster
cookie = None
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Origin': 'https://<webaddr>',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded'
}
def login_build():
global cookie
# 这里的URL为登录链接中service=后面的链接, 即登录后的URL, 也是APP的实际地址
url = 'http://<serveraddr>/ngcf_build/servlet/5gnrindex'
form_data = {
"username": "<username>",
"password": "<password>",
"submit": "",
"_eventId": "submit"
}
res = sess.get(url, headers=header, verify=False) #通过实际地址获取cookie, lt参数和实际的登录地址; 直接通过实际地址无法登录, 这里需要先获取cookie, lt参数才能登陆成功
cookie = res.cookies
login_url = res.url #这里获取到的URL才是浏览器中输入的登录URL, 即登录前, 输入APP实际地址后跳转到的登录地址, 就是网页中302重定向的地址
header['Referer'] = login_url
header['Cookie'] = 'JSESSIONID=' + cookie['JSESSIONID']
form_data['lt'] = str(etree.HTML(res.content).xpath('//input[@name="lt"]/@value')[0])
res = sess.post(login_url, headers=header, params=form_data, cookies=cookie, allow_redirects=False, verify=False)
cookie = res.cookies
res = sess.send(res.next, allow_redirects=False, verify=False)
cookie = res.cookies
if res.status_code == 200 or res.status_code == 302:
if cookie is not None and cookie.get(name='JSESSIONID') is not None :
print('Login succeeded')
return 0
else:
print('Login failed')
return 1
return 1
def get_image_link():
url = 'http://<serveraddr>/ngcf_build/servlet/dailyBuild'
form_data = {
"start": "0",
"limit": "15",
"type": "query",
"queryBean.treeName": "5g-build-platform",
"queryBean.branchName": "master",
"queryBean.product": "bbu_bb_x86_64",
"queryBean.buildTypeId": "",
"queryBean.buildType": "",
"queryBean.buildDate": "",
"queryBean.queryStartTime": "{}T00:00:00".format(time.strftime('%Y-%m-%d')),
"queryBean.queryEndTime": "",
"queryBean.result": "",
"queryBean.errorMsg": "",
"queryBean.host": "",
"queryBean.taskAttr": "",
"queryBean.owner": "",
"queryBean.sort": "id",
"queryBean.dir": "desc",
"queryBean.start": "0",
"queryBean.limit": "15"
}
res = sess.post(url, data)
result = str(res.content, 'utf-8')
res_datas = json.loads(result)
print(res_datas)
compile_list = res_datas['list']
for compile_info in compile_list:
if 'debug' in compile_info['buildTypeShow'] and compile_info['result'] == '[success]':
url_image = compile_info['outputUrl'] + '/image.tar.bz2'
print('Image url: {}'.format(url_image))
return url_image
raise Exception('Get image failed')
这里要特别注意一点, 因为http是无状态的, web页面要保存登录状态需要用到cookie, 等成成功以后页面的response里面会包含一个带有有效标记的cookie, 登录最终的目标就是获取并保存这个有效的cookie, 这样后续的访问就不会被重定向到登录页.
在requests的方法里面只要向这样吧cookie带到请求里即可
对于Python2.7接口稍微有点变化¶
# -*- coding: utf-8 -*-
import os
import sys
import time
import json
import traceback
import requests
import urllib3
from sshlib import sshlib
from lxml import etree
sess = requests.session()
sess.trust_env = False # faster
cookie = None
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Origin': 'https://yfsso.ruijie.com.cn:8443',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded'
}
def login_build():
global cookie
url = 'http://build.ruijie.net:8080/ngcf_build/servlet/5gnrindex'
form_data = {
"username": "goujunping",
"password": "gjp71016",
"submit": "",
"_eventId": "submit"
}
res = sess.get(url, headers=header, verify=False)
cookie = res.cookies
login_url = res.url
header['Referer'] = login_url
header['Cookie'] = 'JSESSIONID=' + cookie['JSESSIONID']
form_data['lt'] = str(etree.HTML(res.content).xpath('//input[@name="lt"]/@value')[0])
res = sess.post(login_url, headers=header, params=form_data, cookies=cookie, allow_redirects=False, verify=False)
cookie = res.cookies
url_with_ticket = res.headers['location']
res = sess.get(url=url_with_ticket, allow_redirects=False, verify=False)
cookie = res.cookies
if res.status_code == 200 or res.status_code == 302:
if cookie is not None and cookie.get(name='JSESSIONID') is not None:
print('Login succeeded')
return 0
print('Login failed')
return 1
参考: https://blog.csdn.net/zhutou_xu/article/details/114212377
分析动态内容页面¶
在动态页面里, 页面上显示出来的内容往往都是js或者AJAX异步获取到的, 跟静态html页面的分析过程有明显的不同. 用Chrome的分析工具也可以很容易的获取到该信息.

在动态页面加载完成后, 我们从所有的请求中过滤XHR类型, 从中找到我们要的那一次请求, 然后在该请求的Preview里面就可以看到完整的相应信息, 同时该请求的URL也可以从Headers选卡中得到.
接下来要做的事情跟上面类似, 构造报文模拟浏览器向该网站发送请求:
def get_content(order_id):
form_data = {
'roarand': 'BW09el5W3mW2sfbGbtWe7mWlwBsWqXg6znppnqkW3woJ5fcz5DnhfWXGonqkLsd0',
'start': '0',
'limit': '20',
'orderid': order_id,
'serviceId': 'test_gscsocsecurityincidentmanage_log_getList2'
}
header = {
'Accept': 'text/plain, */*; q=0.01',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'Host': '<your web>.com',
'Origin': 'https://<your web>.com',
'Referer': 'https://<your web>.com/app/104h/spl/test/ID_480_1511441539904_workflowdetail.spl?orderid=SOC-20180220-00000003',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest'
}
print('Start to scan order id ' + order_id)
url = 'https://<your web>.com/app/pageservices/service.do?forAccessLog={serviceName:test_gscsocsecurityincidentmanage_log_getList2,userId:571bdd42-10ca-4ce1-b41c-8a3f6632141f,tenantId:104h}&trackId=fec68f8e-f30a-4fa1-a8b1-41d3dd11fa4c'
res = s.post(url, headers=header, params=form_data, cookies=cookie, allow_redirects=False, verify=False) #要加载上面登录成功的cookie
print(res.content)
return res
要点其实就是从XHR里找到请求的URI, 构造请求报文头和提交表格, 最后务必要加上登录成功的cookie, 否则会被重定向到登录页面.
抓取动态页面的方法还有很多, 这种方法依赖的包相对较少, 代码比较灵活, 在爬取复杂的登录页面的时候效果比较好, 只是在分析页面登录机制的时候要尤其细心.
参考:
https://www.jianshu.com/p/8cd6e9bc2680