前言

  • 前不久,学校有个校园帮助型app某某圈因为一件评教功能被教务批评了。现在有很多查成绩,查课程的app,他们的实现原理到底是啥了。这两天来到发小扬州这边,学习实现了这个功能,并且用java和python都写了一下,但是鉴于web端只会java,最后集成到springboot部署到服务器提供的小服务。
  • 以前只知道这些东西是爬虫爬出来的,原本以为非常简单,自己研究了一下发现还是有很多的坑和坎。大致有一下几点:
  • 模拟登陆,登陆是所有校园app要解决的第一难题,如果没验证码还好,但是如果有验证码解决思路有两个,抓包找图片手动打码,如果会点图像识别可以自己调算法,恰巧咱们科大有个不需要验证码的入口,在官网可以找到。
  • 提交表单问题。在我的测试过程中遇到两点错误,第一就是正确提交表单后发现只提交一个按钮,这个原因是因为他有多个key-value是相同参数,也就是他有提交key-set内容。还有一个是提交可以完整提交但是只是保存没有真正提交,原因是他的js更改参数内容,需要修改,下面会介绍到。
  • 异常处理,提交过的和没提交的表单内容有所不一致,需要进行异常处理保证程序可执行性,
  • 当然写完了还发现一个问题就是速度有点慢的问题,我才发现没用多线程执行,算了算了,反正自己玩玩,就不加了。

步骤

用到的包:
java:jsoup
python:request,re,Beautifulsoup

  • 下面开始介绍攻克的整个步骤(因为账号被测试用完无法给出更多fidder抓包图)
  • 模拟登陆:
  • 模拟教务评教(强智教务)—一件评教实现原理_java

  • 这部分算是简单的了,因为以前只是试过一些简单的模拟登陆。但是对一些session和cookie理解不够深刻。导致在这个地方 当时卡了很久。我原本以为是先请求登陆页面->带着这个页面的cookie和页面参数进行登陆->用返回界面的cookie进行操作。这个流程,实际上他第三部不会返回cookie,我当时以为那个地方出问题了 ,用fidder一直找不到问题。其实他将所有信息都存在session中,不过你执行的登陆的操作你的cookie的sessionid这个参数在服务器就是你的身份
  • 所以,你只需要模拟登陆拿着初始界面的cookie就可以完成所有操作了。根据图片和网页的form结构你可以发现只有两个参数。没有加密或者隐藏参数,直接怼就可以模拟登陆成功。
  • 接着就要根据初始界面找到评教的链接,有实验课评教和理论课评教。然后就是找到各个老师的评教地址,这里他的地址有些不规则,要用正则或者切割获得,爬虫细节不做过多介绍
  • 模拟教务评教(强智教务)—一件评教实现原理_html_02


  • 模拟教务评教(强智教务)—一件评教实现原理_java_03

  • 最后就是进入评教的表单
  • 模拟教务评教(强智教务)—一件评教实现原理_爬虫_04

  • 我这个是已经评教过的,没评教过的你会发现他有很多隐藏元素,还有就是一个button多个选项你要选一个,并且不能重复一样。就这样我遍历元素提交时候发现几个按钮只有最后一个提交成功,老师评价也成功,经过fidder抓参数和我控制台打印的参数几十个一一对比,发现他不是所有都是key-value,有的是key-set(图中画框的部分),在处理上java需要多加几组data的map,而python自带的字典支持各种形式可以直接用。
  • 模拟教务评教(强智教务)—一件评教实现原理_爬虫_05

  • 就这样,发现所有提交都有效,但是,只是达到保存效果,没有提交。通过抓包再次查看参数。查看原文代码会发现问题所在—js修改参数
  • 模拟教务评教(强智教务)—一件评教实现原理_java_06

    模拟教务评教(强智教务)—一件评教实现原理_一件评教_07

  • 这样你就发现问题所在,更改这个参数就可以提交成功。但是,会发现速度不快啊。。。原来忘记把线程池弄进来了。。哎,,算了,不想改代码了,清冷呵呵的。。附上Java和python版本代码:

java(已更新加入线程)

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;


public class moni {

public static void main(String[] args) throws IOException {
//Scanner sc=new Scanner(System.in);
//test test=new test();
String url="http://jwgl.just.edu.cn:8080/jsxsd/";
Connection con=Jsoup.connect(url).timeout(30000);
Response re=con.execute();
Document doc=Jsoup.parse(re.body());
Elements link=doc.select("form");

Elements links=link.select("input");
Map map = new HashMap<>();
for(Element e:links)
{
if(e.attr("name").equals("USERNAME"))
{
e.attr("value","162210702210");
}
if(e.attr("name").equals("PASSWORD"))
{
e.attr("value","285511");
}
if(e.attr("name").length()>0) {map.put(e.attr("name"), e.attr("value"));}
}
//登陆部分
Connection con2=Jsoup.connect(url "xk/LoginToXk").cookies(re.cookies()).timeout(2000);
con2.data(map);
con2.followRedirects(true);
con2.method(Method.POST);
Response re2=con2.execute();
System.out.println("succss");

// /*
// * 登陆成功
// */
String url3=url "xspj/xspj_find.do?Ves632DSdyV=NEW_XSD_JXPJ";
Connection con3=Jsoup.connect(url3).timeout(2000);
con3.header("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36");
con3.header("Connection", "keep-alive");
con3.cookies(re.cookies());
Response res3=con3.ignoreContentType(true).method(Method.GET).execute();
Document doc3=Jsoup.parse(res3.body());
Elements elements=doc3.select("td a[href]");
String shiyan="http://jwgl.just.edu.cn:8080" elements.get(0).attr("href");
String lilun="http://jwgl.just.edu.cn:8080" elements.get(2).attr("href");
judgle(shiyan,re.cookies());//实验课理论课评教
judgle(lilun,re.cookies());
// System.out.println(shiyan);
}
private static void judgle(String url, Map cookies) throws IOException {
// TODO 自动生成的方法存根
Document doc=Jsoup.connect(url).cookies(cookies).get();
Elements elements=doc.select("td a[href]");
ExecutorService ex= Executors.newFixedThreadPool(10);
int i=0;
for(Element e:elements)
{
String judurl=e.attr("href");
//javascript:JsMod('/js.................pe=view',1000,700)需要正则匹配//我直接用字符串切割
String judur[]=judurl.split("'");
judurl="http://jwgl.just.edu.cn:8080" judur[1];
try {
judThread judThread=new judThread(judurl, cookies);
ex.execute(judThread);
//judteacher(judurl,cookies);
} catch (Exception e2) {
System.out.println(e2);
}
}
ex.shutdown();
}
static class judThread implements Runnable{

String url;
Mapcookies;
public judThread(String url,Map map ) {
this.url=url;
this.cookies=map;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
judteacher(url, cookies);
System.out.println(Thread.currentThread().getName() " jud" url);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}
private static void judteacher(String judurl, Map cookies) throws IOException {
// TODO 自动生成的方法存根
Document doc=Jsoup.connect(judurl).cookies(cookies).get();
Elements link=doc.select("form");
String actionurl="http://jwgl.just.edu.cn:8080" link.attr("action");
Elements links=link.select("input");
Mapmap=new TreeMap<>();
for(Element e:links)//先处理所有隐藏的参数
{
if(e.attr("type").equals("hidden"))
{
map.put(e.attr("name"),e.attr("value"));
}
}
Elements links2=link.select("#table1 tr");
int index=0;//第一个不是参数
for(Element e:links2)//处理button类元素
{
if(index==0) {index ;continue;}
else if(index==links2.size()-1){
String text=e.select("textarea").first().attr("name");
map.put(text, "老师很认真负责!");
}
else if(index==1) {
Elements ele=e.select("input[type=radio]");
Element NO2=ele.get(1);
map.put(NO2.attr("name"), NO2.attr("value"));
//System.out.println(NO2.attr("name"));
}
else {
Elements ele=e.select("input[type=radio]");
Element NO1=ele.get(0);
map.put(NO1.attr("name"), NO1.attr("value"));
//System.out.println(NO1.attr("name"));
}
index ;
//System.out.println(e);
}
map.put("tj", "提 交");

//最后提交form表单
for(String a:map.keySet())
{
System.out.println(a " " map.get(a));
}
Mapmap2=new HashMap<>();
Mapmap3=new HashMap<>();
Mapmap4=new HashMap<>();
Mapmap5=new HashMap<>();
map.put("pj06xh", "1");
map2.put("pj06xh", "2");
map3.put("pj06xh", "3");
map4.put("pj06xh", "4");
map5.put("pj06xh", "5");
map.put("issubmit", "1");
Connection con4=Jsoup.connect(actionurl).cookies(cookies).timeout(2000);
con4.data(map).data(map2).data(map3).data(map4).data(map5);
con4.referrer(judurl);
Response res4=con4.method(Method.POST).execute();

}
}

python版本:

import requests
from bs4 import BeautifulSoup
import re
def judteacher(url,cookiedict):
res=requests.get(url,cookies=cookiedict)
html=res.text
soup=BeautifulSoup(html,'lxml')
form=soup.select('form')[0]
actionurl='http://jwgl.just.edu.cn:8080'+form.get('action')#需要post提交的表单

allinput=form.select('input')
dictkey={}
for link in allinput:
if not str(link.get('type')).__eq__('button'):
dictkey[str(link.get('name'))]=str(link.get('value'))
buttoninput=form.select('#table1 tr')
index=0
for button in buttoninput:
# print(index,button)
if index==0:
index+=1
continue
elif index==(buttoninput.__len__()-1):
text=button.find('textarea').get('name')
dictkey[text]='老师很负责任,收益良多'
elif index==1:
buttonselected=button.select('input[type=radio]')[1]
dictkey[str(buttonselected.get('name'))]=str(buttonselected.get('value'))
else:
buttonselected = button.select('input[type=radio]')[0]
dictkey[str(buttonselected.get('name'))] = str(buttonselected.get('value'))
index+=1#第0个是没用的,第一个是选满意,剩下非常满意,倒数第一个是文本评论
dictkey['tj'] = '提 交'
dictkey['pj06xh'] = ["1","2","3","4","5"]
dictkey['issubmit']="1"
post=requests.post(actionurl,data=dictkey,cookies=cookiedict)
print(dictkey)

def jud(url,cookiedict):
res=requests.get(url,cookies=cookiedict)
soup=BeautifulSoup(res.text,'lxml')
teachers=soup.select('td a[href]')
pattern=re.compile(r'.*[\'](.*)[\'].*')#正则提取javascrit:href='----'
for teacherurl in teachers:
teacherurl=str(teacherurl.get('href'))
m=pattern.search(teacherurl)
teacherurl='http://jwgl.just.edu.cn:8080'+str(m.group(1))#得到完整的teacherurl
try:
judteacher(teacherurl,cookiedict)
except Exception as e:
print(e)
if __name__ == '__main__':
url="http://jwgl.just.edu.cn:8080/jsxsd/"
res=requests.get(url)
cookiejar=res.cookies
cookiedict=requests.utils.dict_from_cookiejar(cookiejar)

html=res.text
soup=BeautifulSoup(html,'lxml')
data={}
inputkey=soup.select("input")
for canshu in inputkey:
if not str(canshu.get("name")).__eq__(None):
data[str(canshu.get('name'))]=canshu.get('value')
data['USERNAME']='162210702236'
data['PASSWORD']='zhongad3344'
# print(data)
urlLogin=url+'xk/LoginToXk'
res2=requests.post(urlLogin,data=data,cookies=cookiedict)


url3=url+'xspj/xspj_find.do?Ves632DSdyV=NEW_XSD_JXPJ'
res3=requests.get(url3,cookies=cookiedict)
soup3=BeautifulSoup(res3.text,'lxml')
hrefs=soup3.select("td a[href]")
shiyan='http://jwgl.just.edu.cn:8080'+str(hrefs[0].get('href'))
lilun='http://jwgl.just.edu.cn:8080'+str(hrefs[2].get('href'))
jud(lilun,cookiedict)
jud(shiyan,cookiedict)

因为是用java版本试水的,所以加了一些反爬措施,发现教务没啥限制,py版本就简写了。另外,只要把第一个java改成函数式就可以融入ssm或者sb了。前端给个模板即可

模拟教务评教(强智教务)—一件评教实现原理_html_08


由于时间和天气恶劣,没有写太多具体步骤,如果有兴趣可以一起研究探讨,来年下次再用吧。。?。来年把线程池整进去。

本人比较菜,代码不足之处还请大佬指正。

如果对​​后端、爬虫、数据结构算法​​​等感性趣欢迎关注我的个人公众号交流:​​bigsai​