做自己学校爬取的案例,学校保密,所以学校网址就不给出了
之所以使用Selenium框架,是因为该框架功能齐全而且有浏览器界面能完整模拟现实中用户的浏览操作,虽然比HtmlUnit笨重,但是的确用这个做爬虫会轻松不少
Selenium框架的使用方法请自行使用搜索引擎,本文章默认读者对该框架均有一定了解
首先先写一份关于ChromeDriver的工具类,便于后续调用,这里的选项我基本都没开启,各位根据自己的现实情况开启即可
public class ChromeUtil {
public static ChromeDriver getChromeDriver() {
// WebDriverManager.chromedriver().setup();
System.setProperty("webdriver.chrome.driver", "D:\\programmNoDelete\\chromedriver\\chromedriver.exe");
// ChromeOptions options = new ChromeOptions();
// options.addArguments("start-maximized"); // open Browser in maximized mode
// options.addArguments("disable-infobars"); // disabling infobars
// options.addArguments("--disable-extensions"); // disabling extensions
// options.addArguments("--disable-gpu"); // applicable to windows os only
// options.addArguments("--disable-dev-shm-usage"); // overcome limited resource problems
// options.addArguments("--no-sandbox"); // Bypass OS security model
// options.setHeadless(true);
// return new ChromeDriver(options);
return new ChromeDriver();
}
}
首先要解决的是登录问题,我的学校官网有验证码,我这里是通过购买验证码破解服务破解的验证码,如果你的学校也有而你又不想购买,可以自行百度写出一份验证码破解算法,大多是不难的
下面是我破解我学校登录页面的方法的代码
@Override
public Object login(String no, String psw) throws Exception {
WebDriver driver = ChromeUtil.getChromeDriver();
WebDriverWait wait = new WebDriverWait((driver), 3);
int i = 0;
//do...while循环一共尝试十次登录
do {
try {
driver.get("你的校园官网网址");
//获取账号密码和验证码对象
WebElement username = wait.until(webElement -> driver.findElement(By.id("userName")));
WebElement password = wait.until(webElement -> driver.findElement(By.id("password")));
WebElement captcha = wait.until(webElement -> driver.findElement(By.id("captcha")));
//填入账号密码
username.sendKeys(no);
password.sendKeys(psw);
String result;
int testCycle = 20;
do {
// 获取验证码对象
WebElement imgElement = wait.until(webElement -> driver.findElement(By.xpath("//*[@id=\"root\"]/span/div[3]/div[2]/div/div[1]/div/div/form/div[4]/div/div/span/div/div[2]/div/img")));
//调用Selenium中的截图方法将验证码截图
File screenshotFile = imgElement.getScreenshotAs(OutputType.FILE);
// 破解验证码
result = 我购买的验证码破解接口(screenshotFile);
//如果结果为空说明没有破解成功,此时再次尝试破解验证码
if (!StringUtils.isEmpty(result)) {
log.info("result为空,再来一次" + testCycle);
break;
}
imgElement.click();
testCycle--;
} while (testCycle > 0);
captcha.sendKeys(result);
//获取登录元素并点击
WebElement button = wait.until(webElement -> driver.findElement(By.xpath("//*[@id=\"root\"]/span/div[3]/div[2]/div/div[1]/div/div/form/div[5]/button[1]")));
button.click();
//如果下面的情况都说明没成功登录
WebElement tip = wait.until(webElement -> driver.findElement(By.xpath("/html/body/div[4]/div/div[2]/div/div[2]/div/div/div[1]/div")));
if (tip.getText().contains("账号不存在") || tip.getText().contains("密码已连续错误")) {
log.info(no + "==》账号或密码错误!");
throw new LoginException(400, "账号或密码错误");
}
if (tip.getText().contains("验证码错误")) {
log.info(no + "==》验证码错误!");
i++;
continue;
}
}catch (WebDriverException e){
// 如果是这种情况,说明登录成功
try {
wait.until(webElement -> driver.findElement(By.xpath("//*[@id=\"mainCenterPanle\"]")));
log.info(no + "==》登录成功!");
break;
}catch (Exception exception){
i++;
}
}catch (Exception e) {
//有些教务网晚11点之后会禁止访问,此时报出教务网异常
log.info(no + "==》教务网的问题导致无法登录!");
e.printStackTrace();
throw new LoginException(444, "教务网的问题导致无法登录");
}
try {
//没有抛出教务网异常则说明登录成功
wait.until(webElement -> driver.findElement(By.xpath("//*[@id=\"mainCenterPanle\"]")));
log.info(no + "==》登录成功!");
break;
}catch (Exception exception){
i++;
}
i++;
Thread.sleep(3000);
}while (i<10);
// 返回chromedriver对象用于下一步处理
return driver;
}
经过上面的处理,我们就已经做到了成功登录校园网了,之后我们就可以爬取我们想要的内容了,我们首先爬取比较简单的个人学籍
我们这里的get内部填入你查询你的学籍的网址,如果登录之后的该网址已经包含了学籍,那么根本不用使用get跳转,反之则需要使用get跳转
@Override
public void reptileStudentStatus(WebDriver driver, String no, int unId) throws Exception {
WebDriverWait wait = new WebDriverWait((driver), 3);
driver.get("你查询学籍的网址");
//获取到包括学生信息的元素
WebElement content_xsxxgl_xsjbxx = wait.until(webElement -> driver.findElement(By.id("xjkpTable")));
//从上面的元素上继续往内获取到名字
String name = wait.until(webElement -> content_xsxxgl_xsjbxx.findElement(By.xpath("//*[@id=\"xjkpTable\"]/tbody/tr[4]/td[2]"))).getText().substring(1);
// log.info(name);
//同理获取性别
String sex = wait.until(webElement -> content_xsxxgl_xsjbxx.findElement(By.xpath("//*[@id=\"xjkpTable\"]/tbody/tr[4]/td[4]"))).getText().substring(1);
// log.info(sex);
//哪个大学
String college = wait.until(webElement -> content_xsxxgl_xsjbxx.findElement(By.xpath("//*[@id=\"xjkpTable\"]/tbody/tr[3]/td[1]"))).getText().substring(3);
// log.info(college);
//哪个班级
String className = wait.until(webElement -> content_xsxxgl_xsjbxx.findElement(By.xpath("//*[@id=\"xjkpTable\"]/tbody/tr[3]/td[4]"))).getText().substring(3);
// log.info(className);
//哪个专业
String major = wait.until(webElement -> content_xsxxgl_xsjbxx.findElement(By.xpath("//*[@id=\"xjkpTable\"]/tbody/tr[3]/td[2]"))).getText().substring(3);
// log.info(major);
//插入学籍表,将上面的内容组成一个对象并保存到数据库中即可
}
然后来爬取学生成绩
@Override
public void reptileGrade(WebDriver driver, String no, int unId, int isPush, String openid) throws Exception {
List<Grade> gradeList = new ArrayList<>();
WebDriverWait wait = new WebDriverWait((driver), 3);
driver.get("你的学生成绩网址");
//获得存储学生成绩的整个大元素
WebElement content_xsxxgl_xscjxx = wait.until(webElement -> driver.findElement(By.id("dataList")));
WebElement tabGrid = wait.until(webElement -> content_xsxxgl_xscjxx.findElement(By.xpath("//*[@id=\"dataList\"]/tbody")));
//注意有些学生的网站是需要点选择学年学期才能下一步的,此时可以获取该元素并选择选择其下拉框的内容即可,下面是例子
/*
wait.until(webElement -> selbox.findElement(By.xpath("//*[@id=\"pager_center\"]/table/tbody/tr/td[8]/select/option[16]"))).click();
wait.until(webElement -> driver.findElement(By.xpath("//*[@id=\"xnm_chosen\"]"))).click();
wait.until(webElement -> driver.findElement(By.xpath("//*[@id=\"xnm_chosen\"]/div/ul/li[8]"))).click();
wait.until(webElement -> driver.findElement(By.xpath("//*[@id=\"xqm_chosen\"]"))).click();
wait.until(webElement -> driver.findElement(By.xpath("//*[@id=\"xqm_chosen\"]/div/ul/li[1]"))).click();
wait.until(webElement -> driver.findElement(By.id("search_go"))).click();
*/
// 获取到成绩的tbody元素并获得内部的tr集合
List<WebElement> trs = wait.until(webElement -> tabGrid.findElements(By.tagName("tr")));
//对tr进行for循环处理
for (int i = 1; i < trs.size(); i++) {
List<WebElement> tdd = trs.get(i).findElements(By.tagName("td"));
// 如果内部的td没有值,那么直接跳过
if(tdd.isEmpty()) {
continue;
}
String studyYear = tdd.get(1).getText(); // 学年
// log.info("studyYear:" + studyYear);
String termNum = studyYear.substring(studyYear.length()-1); // 学期
// log.info("termNum:" + termNum);
String subject = tdd.get(3).getText();//科目名
// log.info("subject:" + subject);
String scoreStr = tdd.get(7).getText();//学分
// log.info("scoreStr:" + scoreStr);
String grade = tdd.get(5).getText();// 成绩
// log.info("grade:" + grade);
String buRe = tdd.get(12).getText();//考试性质
// log.info("buRe:" + buRe);
String gpaStr = tdd.get(9).getText();//绩点
// log.info("gpaStr:" + gpaStr);
String property = tdd.get(13).getText(); // 课程性质
// log.info("property:" + property);
String term = studyYear + "-" + termNum;
int isBuRe = 0;
if(!buRe.equals("正常考试")){
isBuRe = 1;
}
if(!grade.equals(" ") && !gpaStr.equals(" ") && !scoreStr.equals(" ") && !grade.isEmpty() && !gpaStr.isEmpty() && !scoreStr.isEmpty()) { // 学分、成绩、绩点都不为空
float score = Float.parseFloat(scoreStr);
float gpa = Float.parseFloat(gpaStr);
将上面的内容组成一个新对象并加入到集合中
}
}
//将上面存储好的集合加入到数据库中
}
最后我们来讲最难的获取课程表,所有学生的课程表无非需要的关键信息在于教师名、上课教室、上课时间是几几节、上课从第几周到第几周、该课程所包括的班级以及课程名,这些信息在学生课程表里肯定都是有的,问题在于不同的学校会使用不同的系统,而往往不同的系统存储这些信息的格式也不同,所以要怎么去除掉其中的无用信息,过滤出自己所需要的关键信息,这个就需要自己去构建算法了,我下面的代码里就调用了很多处理的算法
总的来说这个也不难,无非就是一些字符串的处理而已,并不是很难,花点时间都可以做出来
@Override
public void reptileCourse(WebDriver driver, String no, int unId) throws Exception {
List<CoursePlusPlus> courseList = new ArrayList<>();
WebDriverWait wait = new WebDriverWait((driver), 3);
WebDriverWait wait1 = new WebDriverWait((driver), 1);
driver.get("你的课程表网址");
//获得tbody
WebElement tb = wait.until(webElement -> driver.findElement(By.id("kbtable")));
String dayOfWeek = null;
//获得tr集合
List<WebElement> trs = wait1.until(webElement -> tb.findElements(By.tagName("tr")));
String section = null;
String sectionList = null;
//一般来说我们只需要处理周一到周六的,因此我们直接用for循环
for(int j=1;j<trs.size()-1;j++) {
int finalJ = j;
//获得td集合
List<WebElement> tds = wait1.until(webElement -> trs.get(finalJ).findElements(By.tagName("td")));
int index = 1;//代表星期几
for(WebElement td : tds) {
//下面的内容都是对课程表名的算法处理
WebElement kbcontent = wait1.until(webElement -> td.findElement(By.className("kbcontent")));
String[] split = kbcontent.getText().split("\n");
String courseName = split[0];
if(courseName.equals(" ")){
continue;
}
if(courseName.contains(" ")){
courseName = courseName.split(" ")[0];
}
if(split.length>1 && split[1].contains("(")){
courseName=courseName+split[1];
}
//上面的内容都是对课程表名的算法处理
// log.info("courseName "+courseName);
//下面的内容同理
List<WebElement> fonts = wait1.until(webElement -> kbcontent.findElements(By.cssSelector("font")));
int length = fonts.size();
String teacher = null;
if(length>=4){
teacher = fonts.get(length-4).getText();
}
// log.info("teacher "+teacher);
String includeClass = fonts.get(length-3).getText();
// log.info("includeClass "+includeClass);
//周数和节数往往连在一起,所以该字符串往往一起获得
String sectionAndWeek = fonts.get(length-2).getText();
// log.info("sectionAndWeek "+sectionAndWeek);
String local = fonts.get(length-1).getText();
// log.info("local "+local);
//对周数和节数的字符串进行处理,获得具体的周数和节数
String[] processSectionAndWeek = processSectionAndWeek(sectionAndWeek);
String week = processSectionAndWeek[1];
section = processSectionAndWeek[0];
//对节数进行处理,具体是让1-4节变成[1,2,3,4]这样的集合
sectionList = processSection(section);
//下面是对周数的算法处理,令周数也变成集合
List<String> weeklist = new ArrayList<>();
if (!week.equals(" ") && !week.isEmpty()) {
String[] weeks = new String[]{week};
if (week.contains(",") || week.contains(",")) {
String flag = null;
if (week.contains(",")) {
flag = ",";
} else {
flag = ",";
}
weeks = week.split(flag);
}
//如果存在双周,则weeks的值必然不止一个,此时重新对周数进行处理使两个周数合二为一
for(String week1 : weeks) {
weeklist.addAll(processWeek(week1)); // 可能存在1-7周,9-17周
}
}
dayOfWeek = index+"";
//获得今天是星期几,具体index是几今天就是星期几
String dayOfWeekStr = processDayOfWeek(dayOfWeek);
index++;
}
}
//将上面的数据组装成一个实体类并装入到数据库中
}