主要库

  • requests,json,re

分析网页

第一眼就看到网页后缀是.aspx,也就是用的ASP.NET技术,不算老熟人有兴趣可以了解下https://baike.baidu.com/item/aspx/203251?fr=aladdin

登录网页称为网页1,随便输入一下账密fiddle抓一下,可以看到网页url,注意是POST
http://jw.dlust.edu.cn/default2.aspx

fiddle 抓登录界面

F12 里RadioButtonList1

重要参数__VIEWSTATE

也没加密,就一个__VIEWSTATE和RadioButtonList1这两个参数要注意一下,RadioButtonList1这个参数网上查了下,这个参数用的gb2312编码(正码就是“学生”),乱码不太清楚,看F12也是显示不出来,就剩下__VIEWSTATE。

在登录界面搜一下,网页源代码里就有这个参数的值

get获取__VIEWSTATE参数

现在需要思考一下,刚才fiddle的抓包可以看到是POST请求,表单数据要填写才能发送request。其实不然,post网页不一定要用post方法访问才能返回响应,通常query参数是GET请求时常用的携带参数方式。如果是POST请求也要携带query类型的参数,可以把它拼接到url里,至于能不能获取就看服务端。对应的get网页也不一定非要在url里传递参数,重要的是看服务器后台是如何解析的,网页请求的本质也就在于后台的解析,与get、post这些表面俗称的文字无太多区别只是人们定义的概念罢了。

网页1可以先使用get请求去获取到响应文本,应该是隔段时间每个ip访问的时候分配一个__VIEWSTATE数据和cookie一样,所以与填不填写表单并不重要,用的re,xpath当然也可都好用,结构化强的用xpath,数据简单易拿的用re。

注意对于这种登录网页一定要保持回话,re写个规则,header随便看看几个必要参数就可,而且第一次访问不需要cookie,直接get(url)就行,我们只要返回的文本和cookie

pattern1 = re.compile('__VIEWSTATE" value=\"(.*?)\"', re.S)   # 获取vs
headeri = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cookie": "",
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
    "Referer": "http://jw.dlust.edu.cn/default2.aspx",
    "Host": "jw.dlust.edu.cn",
    "Cache-Control": "max-age=0"
}
# vs要经过两次更换,登录之间是vs1,登录后还要再获取一次vs2
vs = 0
# cookie要保持一直,不管是申请验证码还是模拟登录,都要一致,所以cookie变量用一次改一次
cookie = 0
def get_first_VS():
    global cookie, vs
    url = "http://jw.dlust.edu.cn/default2.aspx"
    resp1 = s.get(url)
    text = resp1.text
    # 进去之前的 vs1
    vs = re.search(pattern1, text).group(1)
    cookie = dict(resp1.cookies)
    cookie = 'ASP.NET_SessionId=' + cookie['ASP.NET_SessionId']
    headeri['Cookie'] = cookie
    print("============第一次get访问获取vs和cookie成功============\n进入之前的vs:{}\nCookie:{}".format(vs,cookie))

ok,现在我们已经有vs1(后面还有vs2,把登录界面的__VIEWSTATE参数称作vs1),和cookie了 。

验证码获取

不会机器学习,后期准备找个轮子先用着,现在就先把验证码图片保存下来手动输入吧

验证码的获取就比较简单了,就是个简单的GET请求,url如下
需要注意的是get请求要用第一步中的vs1,和cookie,其他的没啥了
http://jw.dlust.edu.cn/CheckCode.aspx

因为使用的session保持会话,所以没有加headers,但原理要理解

def get_yzm():
    url = 'http://jw.dlust.edu.cn/CheckCode.aspx'
    resp = s.get(url)
    with open('./yzm.jpg', 'wb') as f:
        f.write(resp.content)
    print("============获取验证码成功============")

进入系统

至此,POST请求所需的参数都已准备好,body参数,用@RequestBody注解注入(json数据注入到类的对象属性中)在requests.post表现为data参数,不要忘了解码下(“gb2312”),顺便拿一下学生的xh(学号),xm(姓名),后面有用

__VIEWSTATE第一次get访问得到的:vs1
txtUserName学号
TextBox2密码
txtSecretCode手动输入验证码
RadioButtonList1%D1%A7%C9%FA(“学生”的gb2312编码)

pattern2 = re.compile('<span id="xhxm">(.*?)同学',re.S)  # 获取姓名
def join_system():
    un = input("学号:")
    pd = input("密码:")
    yzm = input("验证码:")

    data = {
        "__VIEWSTATE": vs,
        "txtUserName": un,
        "TextBox2": pd,
        "txtSecretCode": yzm,
        "RadioButtonList1": "%D1%A7%C9%FA",  # 学生选项
        "Button1": "",
        "lbLanguage": ""
    }
    ss = s.post(url='http://jw.dlust.edu.cn/default2.aspx', data=data, headers=headeri)
    # 保存会话很关键
    text = ss.content.decode('gb2312')
    if ss.status_code == 200:
        print("============进入系统成功==============")
        xh = un
        try:
            xm = re.search(pattern2,text).group(1)
            print("==========获取姓名学号成功=========\n姓名:{}\n学号:{}".format(xm,xh))
        except Exception as e:
            print("==========获取姓名学号失败=========")
            print(e)

运行后输出下text看有没有成功的信息,比如text中出现了自己的学号和姓名则表明进入系统成功

如果出现了Object moved to here.,典型的302跳转后面会详细说,referer参数修改下:

http://jw.dlust.edu.cn/xs_main.aspx?xh=自己的学号

获取成绩

点击查询成绩按钮fiddle抓的这个连接,进入后的系统页面称为网页2
http://jw.dlust.edu.cn/xscj.aspx?xh=学号&xm=姓名&gnmkdm=N121604

可以发现网页2的vs参数和网页1使用的不同,F12再看一下果然,vs不一样了,网页2的vs参数称为vs2

同理步骤

  1. get访问,拿到vs2
  2. 再用post,vs2当做参数

其他参数

xh,xm,Button2

xh就是你的账号(学号),xm和网页1参数中的RadioButtonList1很相似,盲猜是你姓名的gb2312编码呈乱码,Button2参数在body主体中,应该和vs2参数一样通过网页body主体获取,从F12元素获取中定位了该元素,value值为中文,可以大胆的猜测这个乱码同样是“在校学习成绩查询”的gb2312编码呈乱码。

为了验证,用fiddle replay重写参数再次访问观察返回结果,果然验证了我们的猜想成绩出来了。

gnmkdm,txtQSCJ,txtZZCJ

gnmkdm:发现个很有意思的事,很可能是网站搭建员懒这完全是中文缩写gnmkdm(功能模块代码),这个参数也就顾名思义的代表不同的模块的代码,在校学习成绩查询的代码就是N121601,当然还有别的模块这里不再说明,方法一样

txtQSCJ,txtZZCJ:和Button2那个一样是从body主体中获取的,对应的value值

关于Object moved to here

需要注意的是,post访问该url时http://jw.dlust.edu.cn/xscj.aspx?xh=学号&xm=姓名&gnmkdm=N121604,必须修改headers中的Referer参数,不然会出现Object moved to here,该问题的出现是网页在执行一个重定向操作,但并没有给出具体定向连接所以抛出了here。所以必须在post成绩url时加上referer参数,可以在fiddle中找到

获取成绩

至此所有参数已准备好

xh学号
xm姓名(直接写姓名就行,不用gb2312转码)
gnmkdm不同的模块对应不同的代码,成绩的是N121604
__VIEWSTATE进入网页2get获取
txtQSCJ元素value值
txtZZCJ元素value值
Button2同样是元素value值(中文就行,不用gb2312转码)

pattern3 = re.compile('<tr class="alt">.*?'+
                      '<td>(.*?)</td>.*?'+     # 课程编号
                        '<td>(.*?)</td>.*?'+   # 课程名
                        '<td>(.*?)</td>.*?'+   # 课程类型
                        '<td>(.*?)</td>.*?'+   # 期末成绩
                        '<td>(.*?)</td>.*?'+   # 总成绩
                        '<td>.*?</td>.*?'+   # null
                        '<td>.*?</td>.*?'+   # null
                        '<td>.*?</td>.*?'+   # null
                        '<td>(.*?)</td>.*?'+   # 学分
                        '<td>.*?</td>.*?'+   # 辅助标记
                      '</tr>',re.S)

#  获取成绩
def get_score(xh,xm):

    url = 'http://jw.dlust.edu.cn/xscj.aspx'    # xscj = ??成绩
    # querystring,用params,可以写在url后
    params = (
        ("xh", 学号),
        ("xm", 姓名),
        ("gnmkdm", "N121604"),  # gnmkdm功能模块代码,每个按钮对应的不一样,成绩查询是N121604
    )
    # print(header)
    resp = s.get(url,params=params, headers=headeri)
    if resp.status_code == 200:
        print("获取新  vs  成功!")
        text = resp.text
        # 进来后的  VS
        try:
            vs = re.search(pattern1, text).group(1)
            print("进去后的vs:", vs)
        except:
            print("获取进入后的vs失败,可能由于验证码输入错误,请重新开启系统")
    else:
        print("第一次访问(获取vs失败)")

    # 接收参数时,用@RequestBody注解注入,用data
    data={
        "__VIEWSTATE": vs,
        "txtQSCJ": "0",  # 学生选项
        "txtZZCJ": "100",
        "Button2": "在校学习查询"
    }
    resp2 = s.post(url,params=params,data=data,headers=headeri)
    text2 = resp2.text
    form_table(text2)


def form_table(text):
    # 包含成绩的列表
    score_list = re.findall(pattern3,text)
    for score_item in score_list:
        print(score_item)
    # print(score_list)

如果运行不成功,很可能是参数问题,仔细观察


拓展

试了下课表查询,还行。只要进来后基本上vs和cookie就不用再动了,用初始的session会话再加上相应的参数就可以畅通无阻,其实主要的两个功能就是查成绩和查课表。后期可以做个网站,查成绩就登陆(单向加密),只想看课表就游客只看课表就行。查课表的参数都在HTML代码里,常见的传value。有时间再写个模型,跑一下验证码不然自动不了。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐