scores = new ArrayList<>();
62 | String regex = "\\d{6}(.+?) | .+?(\\d\\.\\d) | (\\d+) | .+?(\\d+) | .+?(\\d+) | ";
63 |
64 | Pattern pattern = Pattern.compile(regex);
65 | Matcher matcher = pattern.matcher(scoreHtml);
66 | while(matcher.find()){
67 | Score score = new Score();
68 | score.setCourseName(matcher.group(1));
69 | score.setCredit(matcher.group(2));
70 | score.setRegularScore(matcher.group(3));
71 | score.setPaperScore(matcher.group(4));
72 | score.setTotalScore(matcher.group(5));
73 | scores.add(score);
74 | }
75 | return gson.toJson(scores);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/com/guang/jw/utils/JwUtils.java:
--------------------------------------------------------------------------------
1 | package com.guang.jw.utils;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStreamReader;
6 | import java.io.UnsupportedEncodingException;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 | import org.apache.http.Consts;
13 | import org.apache.http.Header;
14 | import org.apache.http.HttpResponse;
15 | import org.apache.http.NameValuePair;
16 | import org.apache.http.client.ClientProtocolException;
17 | import org.apache.http.client.HttpClient;
18 | import org.apache.http.client.config.RequestConfig;
19 | import org.apache.http.client.entity.UrlEncodedFormEntity;
20 | import org.apache.http.client.methods.HttpGet;
21 | import org.apache.http.client.methods.HttpPost;
22 | import org.apache.http.impl.client.CloseableHttpClient;
23 | import org.apache.http.impl.client.HttpClientBuilder;
24 | import org.apache.http.message.BasicNameValuePair;
25 | import org.apache.http.util.EntityUtils;
26 |
27 | /**
28 | * 从教务处网站 获取信息的工具类,返回html代码
29 | * 查成绩和课表需要先获取页面的ViewState,一切需先登录
30 | */
31 | public class JwUtils {
32 | private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36";
33 | private static final String Base_Referer = "http://jwc.gdufe.edu.cn/";
34 | private static final String Jw_Host = "http://jwxt2.gdufe.edu.cn:8080";
35 |
36 | //下面这些URL要根据Base_URL刷新
37 | private static String Base_URL = "http://jwxt2.gdufe.edu.cn:8080/(S(100000000000000000000002))";
38 | private static String Login_URL = Base_URL + "/default2.aspx";
39 | private static String LOGIN_URL_NO_Verify = Base_URL + "/default_ysdx.aspx";
40 | private static String Referer = Base_URL + "/xs_main.aspx?xh=";// + sno; //登陆后的reference
41 | private static String Score_URL = Base_URL + "/xscjcx_dq.aspx?xh=";
42 | private static String KeBiao_URL = Base_URL + "/xskbcx.aspx?xh=";
43 | private static String KeChengJieShao_URL = Base_URL + "/tjkbcx.aspx?xh=";
44 |
45 | private static String VIEW_STATE = "";
46 | private static String EVENT_STATE = "";
47 |
48 | private static HttpClient httpclient;
49 | private static String encoding = "gb2312";
50 |
51 | static {
52 | httpclient = HttpClientBuilder.create().build();
53 | }
54 |
55 | /**
56 | * 登陆教务系统,成功返回true,失败返回false
57 | */
58 | public static boolean LoginToSystem(String sno, String password) {
59 | if(!getAndSetBaseUrl(Jw_Host)){//教务系统崩溃
60 | return false;
61 | }else{
62 | refreshUrlByBaseUrl();
63 | getAndSetViewState(Login_URL,Base_Referer);
64 | if(LoginToSystem(Login_URL, sno, password) ){
65 | return true;
66 | }else{
67 | return false;
68 | }
69 | }
70 | }
71 | public static String getCurSemesterScore(String sno) {
72 | return getCurSemesterInfo(Score_URL,sno);
73 | }
74 | public static String getCurSemesterKeBiao(String sno) {
75 | return getCurSemesterInfo(KeBiao_URL,sno);
76 | }
77 | public static String getCurSemesterJieShao(String sno) {
78 | return getCurSemesterInfo(KeChengJieShao_URL,sno);
79 | }
80 | /**
81 | * 获取指定学期课表
82 | * @param sno 学号
83 | * @param xuenian 学年 example:2015-2016
84 | * @param xueqi 学期 example:2
85 | * @return
86 | */
87 | public static String getKebiao(String sno, String xuenian, String xueqi) {
88 | getAndSetViewState(KeBiao_URL + sno ,Referer + sno);
89 | return getKebiao(KeBiao_URL, sno, xuenian, xueqi);
90 | }
91 | /**
92 | * 查分,提供学号和学年、学期
93 | * @param sno 学号
94 | * @param xuenian 学年,2015-2016格式 或者 全部
95 | * @param xueqi 学期,1 或 2 或者 全部
96 | * @return html,([^<>]+) 可以过滤无关html信息,再进一步过滤科目分数等。
97 | * \d{6}[\r\n] | (.*)[\r\n] | .*[\r\n] | .*[\r\n] | (\d\.\d)过滤科目名、学分,具体分数的没写
98 | */
99 | public static String getScore(String sno, String xuenian, String xueqi) {
100 | getAndSetViewState(Score_URL + sno ,Referer + sno);
101 | return getScore(Score_URL, sno, xuenian, xueqi);
102 | }
103 |
104 |
105 |
106 | /**
107 | * 当BaseUrl改变后刷新相关的Url,但不附加学号
108 | */
109 | private static void refreshUrlByBaseUrl() {
110 | Login_URL = Base_URL + "/default2.aspx";
111 | LOGIN_URL_NO_Verify = Base_URL + "/default_ysdx.aspx";
112 | Referer = Base_URL + "/xs_main.aspx?xh=";// + sno; //登陆后的reference
113 | Score_URL = Base_URL + "/xscjcx_dq.aspx?xh=";
114 | KeBiao_URL = Base_URL + "/xskbcx.aspx?xh=";
115 | KeChengJieShao_URL = Base_URL + "/tjkbcx.aspx?xh=";
116 | }
117 | /**
118 | * 获取隐藏的__VIEWSTATE和__EVENTVALIDATION属性,用于获取当前页面的信息。
119 | * 在 登陆 查课表 查分 前需要调用获取对应的默认视图__VIEWSTATE
120 | */
121 | private static void getAndSetViewState(String url,String referer) {
122 | HttpGet httpGet = new HttpGet(url);
123 | try {
124 | httpGet.setHeader("User-Agent", USER_AGENT);
125 | httpGet.setHeader("Referer", referer);
126 | HttpResponse httpResponse = httpclient.execute(httpGet);
127 | BufferedReader rd = new BufferedReader(new InputStreamReader(
128 | httpResponse.getEntity().getContent(), encoding));
129 | StringBuffer result = new StringBuffer();
130 | String line = "";
131 | while ((line = rd.readLine()) != null) {
132 | result.append(line);
133 | }
134 | String pattern1 = "__VIEWSTATE\" value=\"(.*?)\" />";
135 | String pattern2 = "__EVENTVALIDATION\" value=\"(.*?)\" />";
136 | Pattern r1 = Pattern.compile(pattern1);
137 | Pattern r2 = Pattern.compile(pattern2);
138 | Matcher m1 = r1.matcher(result);
139 | Matcher m2 = r2.matcher(result);
140 | if (m1.find()) {
141 | VIEW_STATE = m1.group(1);
142 | }
143 | if (m2.find()) {
144 | EVENT_STATE = m2.group(1);
145 | }
146 | } catch (Exception e) {
147 | e.printStackTrace();
148 | }
149 | }
150 | /**
151 | * 访问一次Jw_Host,根据返回信息获取当前正确sessionId,写回BaseUrl
152 | * 注意不能是访问带有具体sessionId的链接,否则第一次获取成功,之后一段时间就返回200登陆了
153 | * 貌似是因为如果带有sessionId的话即使访问其他sessionId也被认为这个客户端ip登陆成功,新旧sessionId都有效
154 | * @param url Jw_Host,直接填地址+端口,不能带sessionId
155 | * @param sno
156 | * @param password
157 | */
158 | private static boolean getAndSetBaseUrl(String url) {
159 | CloseableHttpClient client = HttpClientBuilder.create().build(); //只用一次,选择可close的
160 | HttpGet get = new HttpGet(url);
161 | RequestConfig requestConfig = RequestConfig.custom().setRedirectsEnabled(false).build();
162 | get.setConfig(requestConfig); //禁止自动跳转,方便获取header的跳转信息
163 | HttpResponse response;
164 | try {
165 | response = client.execute(get);
166 | Header header = response.getFirstHeader("Location");//header => [Location: /(S(mabs01f0sjr3jo45gy4atk55))/default2.aspx]
167 | int statuCode = response.getStatusLine().getStatusCode();
168 | if (statuCode == 302) { //跳转情况,到我们要的url
169 | String sessionId = header.getValue().replace("/default2.aspx", ""); //sessionId => [/(S(mgg3q5fvspor3o2xlady0g45))]
170 | Base_URL = Jw_Host + sessionId; //设置正确的Base_URL
171 | client.close();
172 | return true;
173 | }else{ //教务系统崩溃
174 | System.out.println("header="+header +" statu="+statuCode);
175 | client.close();
176 | return false;
177 | }
178 | } catch (ClientProtocolException e) {
179 | e.printStackTrace();
180 | } catch (IOException e) {
181 | e.printStackTrace();
182 | }
183 | return true;
184 | }
185 |
186 | /**
187 | * 实际的登陆教务系统代码,主要是获取Cookie,之后httpClient自动管理Cookie,需在getViewState()后调用
188 | */
189 | private static boolean LoginToSystem(String url, String sno, String password) {
190 | HttpPost httpost = new HttpPost(url);
191 | httpost.setHeader("Referer", Base_Referer);
192 | httpost.setHeader("User-Agent", USER_AGENT);
193 | List nvps = new ArrayList();
194 | nvps.add(new BasicNameValuePair("__VIEWSTATE", VIEW_STATE));
195 | nvps.add(new BasicNameValuePair("__EVENTVALIDATION", EVENT_STATE));
196 | nvps.add(new BasicNameValuePair("txtUserName", sno));
197 | nvps.add(new BasicNameValuePair("TextBox2", password));
198 | nvps.add(new BasicNameValuePair("txtSecretCode", ""));
199 | nvps.add(new BasicNameValuePair("RadioButtonList1", "%D1%A7%C9%FA"));
200 | nvps.add(new BasicNameValuePair("Button1", ""));
201 | nvps.add(new BasicNameValuePair("lbLanguage", ""));
202 | httpost.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
203 |
204 | HttpResponse response;
205 | try {
206 | response = httpclient.execute(httpost);
207 | // String result = EntityUtils.toString(response.getEntity());
208 | /* 登陆成功后会返回302跳转,登陆失败则是200返回错误信息 */
209 | int statuCode = response.getStatusLine().getStatusCode();
210 | if (statuCode == 302) {
211 | return true;
212 | } else {
213 | return false;
214 | }
215 | } catch (ClientProtocolException e) {
216 | e.printStackTrace();
217 | } catch (IOException e) {
218 | e.printStackTrace();
219 | }
220 | return false;
221 | }
222 |
223 | /**
224 | * 无验证码的登陆链接,也可以用
225 | * @param url LOGIN_URL_NO_Verify
226 | * @param sno
227 | * @param password
228 | * @return
229 | */
230 | private boolean LoginToSystemNoVerify(String url, String sno, String password) {
231 | HttpPost httpost = new HttpPost(url);
232 | httpost.setHeader("Referer", Base_Referer);
233 | httpost.setHeader("User-Agent", USER_AGENT);
234 | List nvps = new ArrayList();
235 | nvps.add(new BasicNameValuePair("__VIEWSTATE", VIEW_STATE));
236 | nvps.add(new BasicNameValuePair("__EVENTVALIDATION", EVENT_STATE));
237 | nvps.add(new BasicNameValuePair("TextBox1", sno));
238 | nvps.add(new BasicNameValuePair("TextBox2", password));
239 | nvps.add(new BasicNameValuePair("RadioButtonList1", "%D1%A7%C9%FA"));
240 | nvps.add(new BasicNameValuePair("Button1", ""));
241 | httpost.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
242 |
243 | HttpResponse response;
244 | try {
245 | response = httpclient.execute(httpost);
246 | int statuCode = response.getStatusLine().getStatusCode();
247 | if (statuCode == 302) {
248 | return true;
249 | } else {
250 | return false;
251 | }
252 | } catch (ClientProtocolException e) {
253 | e.printStackTrace();
254 | } catch (IOException e) {
255 | e.printStackTrace();
256 | }
257 | return false;
258 | }
259 |
260 | /**
261 | * 直接GET方式访问带学号的链接
262 | * 可获取当前学期课表、当前学期成绩、课程介绍
263 | * 传入KeBiao_URL 获取当前学期课表
264 | * 传入Score_UR 获取当前学期成绩
265 | * 传入KeChengJieShao_URL 获取课程介绍
266 | */
267 | private static String getCurSemesterInfo(String url, String sno) {
268 | HttpGet httpGet = new HttpGet(url + sno);
269 | httpGet.setHeader("User-Agent", USER_AGENT);
270 | httpGet.setHeader("Referer", Referer + sno);
271 | HttpResponse httpResponse;
272 | String result = "";
273 | try {
274 | httpResponse = httpclient.execute(httpGet);
275 | result = EntityUtils.toString(httpResponse.getEntity());
276 | } catch (Exception e) {
277 | e.printStackTrace();
278 | }
279 | return result.toString();
280 | }
281 | private static String getKebiao(String url,String sno, String xuenian, String xueqi) {
282 | HttpPost httpost = new HttpPost(url+sno);
283 | httpost.setHeader("User-Agent", USER_AGENT);
284 | httpost.setHeader("Referer", url+sno); //当前学生查询课表的get链接,[&xm=%u97e9%u88d5%u5149&gnmkdm=N121603]这部分不需要
285 | List nvps = new ArrayList();
286 |
287 | nvps.add(new BasicNameValuePair("__EVENTTARGET", "xqd"));
288 | nvps.add(new BasicNameValuePair("__EVENTARGUMENT", ""));
289 | nvps.add(new BasicNameValuePair("__LASTFOCUS", ""));
290 | nvps.add(new BasicNameValuePair("__VIEWSTATE", VIEW_STATE));
291 | nvps.add(new BasicNameValuePair("__EVENTVALIDATION", EVENT_STATE));
292 |
293 | nvps.add(new BasicNameValuePair("xnd", xuenian));
294 | nvps.add(new BasicNameValuePair("xqd", xueqi));
295 | try {
296 | httpost.setEntity(new UrlEncodedFormEntity(nvps, "GBK"));
297 | } catch (UnsupportedEncodingException e1) {
298 | e1.printStackTrace();
299 | }
300 | HttpResponse response;
301 | try {
302 | response = httpclient.execute(httpost);
303 | String result = EntityUtils.toString(response.getEntity());
304 | return result;
305 | } catch (Exception e) {
306 | e.printStackTrace();
307 | }
308 | return "";
309 | }
310 |
311 | private static String getScore(String url,String sno, String xuenian, String xueqi) {
312 | HttpPost httpost = new HttpPost(url+sno);
313 | httpost.setHeader("User-Agent", USER_AGENT);
314 | httpost.setHeader("Referer", url+sno); //当前学生查询成绩的get链接
315 | List nvps = new ArrayList();
316 |
317 | nvps.add(new BasicNameValuePair("__EVENTTARGET", "ddlxn"));
318 | nvps.add(new BasicNameValuePair("__EVENTARGUMENT", ""));
319 | nvps.add(new BasicNameValuePair("__LASTFOCUS", ""));
320 | nvps.add(new BasicNameValuePair("__VIEWSTATE", VIEW_STATE));
321 | nvps.add(new BasicNameValuePair("__EVENTVALIDATION", EVENT_STATE));
322 |
323 | nvps.add(new BasicNameValuePair("ddlxn", xuenian));
324 | nvps.add(new BasicNameValuePair("ddlxq", xueqi));
325 | nvps.add(new BasicNameValuePair("btnCx", "+%B2%E9++%D1%AF+"));
326 | try {
327 | httpost.setEntity(new UrlEncodedFormEntity(nvps, "GBK"));
328 | } catch (UnsupportedEncodingException e1) {
329 | e1.printStackTrace();
330 | }
331 |
332 | HttpResponse response;
333 | try {
334 | response = httpclient.execute(httpost);
335 | String result = EntityUtils.toString(response.getEntity());
336 | return result;
337 | } catch (Exception e) {
338 | e.printStackTrace();
339 | }
340 | return "";
341 | }
342 | }
343 |
--------------------------------------------------------------------------------
/src/help.md:
--------------------------------------------------------------------------------
1 | # 说明
2 | 1. 教务系统登陆页地址的`(S(hpuxisy320bpi2iunqzwd445))/`下称为cookieId,是系统为了防止浏览器禁用Cookie而存在URL上的,每次都会变化,一段时间后失效。
3 |
4 | 所以sessionId需要每次登陆前获取,获取方式:Get请求[http://jwxt2.gdufe.edu.cn:8080](http://jwxt2.gdufe.edu.cn:8080)会返回302跳转,header里的`location`有跳转链接,内含sessionId,需要禁止自动跳转才能获取。
5 |
6 | 有sessionId之后登陆并用Cookie进行其他操作,全程同一个HttpClient对象会自动管理Cookie(4.x版本HttpClient)
7 |
8 | 2. 登陆前需要获取一次登陆页的`__VIEWSTATE`信息,__VIEWSTATE存储了当前视图结构,表示当前页面有什么东西,编码转换地址:[http://viewstatedecoder.azurewebsites.net/](http://viewstatedecoder.azurewebsites.net/)
9 |
10 | 3. 登陆后可以直接GET查当前学期课表、分数等,需要指定学期的话要获取当前学期课表/分数页面的__VIEWSTATE,再Post过去。
11 |
12 | 4. 登陆后基本上所有请求都含`xh=...&xm=...&gnmkdm=N121603`,xm是GBK/gb2312编码的姓名,gnmkdm固定值,这2个不是必要属性。
13 |
14 | 5. 代码中的成员变量URL均不包含学号,所以除登陆时外,`getAndSetViewState()`的url需带上学号。
15 |
16 | 6. `Base_URL`有改变的话(登陆时获取sessionId之后就改变)需要调用refreshUrlByBaseUrl()更新相关的URL。
17 |
18 | # 特殊情况
19 |
20 | 如果从课表html里正则匹配课程时不对劲,则可能是这种情况
21 |
22 | 第3节 | 人工智能基础 必修 1-16(3) 李某 4-110
| 创业基础 必修 1-16(3,4) 何某人 2-203
| | 计算机图形学 1-16(3,4) 某A 4-205
| | | |
23 |
24 | 第4节 | | | | | |
25 |
26 | 下午 | 第5节 | | 形势与政策 必修 7,11,15(5,6) 某B 2-107
|
27 |
28 |
--------------------------------------------------------------------------------
|