├── .gitignore
├── README.md
├── app.js
├── bin
└── www
├── controller
├── api.js
└── cet.html
├── dist
├── css
│ └── styles.min.css
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
├── img
│ └── top.jpg
└── js
│ └── scripts.min.js
├── gulpfile.js
├── package.json
├── public
├── css
│ ├── bootstrap.min.css
│ └── style.css
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
├── img
│ └── top.jpg
└── js
│ ├── angular.js
│ ├── bootstrap.min.js
│ ├── index.js
│ └── jquery.min.js
├── routes
├── api.js
└── index.js
└── views
├── error.ejs
└── index.ejs
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # koa2-cet
2 |
3 | 基于Angular和Koa2的英语四六级成绩查询系统,提供免费API接口
4 |
5 | # 预览
6 |
7 | 在线预览地址: https://cet.lenshen.com
8 |
9 | # 技术栈
10 |
11 | * **Angular**:实现前端页面构建
12 | * **Koa2**:实现服务端具体业务逻辑
13 | * **ES6**、**ES7**、**ES8**:服务端使用ES6语法,promise/async/await 处理异步
14 | * **superagent**:爬虫的核心,进行模拟请求
15 | * **cheerio**:解析DOM结构,爬取需要的数据
16 | * **cors**:服务端返回数据时做了cors设置,允许跨域
17 | * **jsonp**:支持JSONP请求,客户端需要传入回调函数名称
18 | * **pm2**:服务端使用pm2部署,常驻进程,比forever好用得多(https://github.com/Unitech/pm2)
19 | * **nginx**:服务端代理端口转发
20 |
21 | # 使用说明
22 |
23 | 使用cnpm i 安装所有依赖,然后运行npm run dev,浏览器打开 http://localhost:8001
24 |
25 | # API接口
26 |
27 | 本系统免费提供API接口,具体接口如下所示:
28 | ```
29 | URL: https://cet.lenshen.com/api/search?user=姓名&number=准考证号
30 | 参数说明:
31 | user 姓名(需要先将中文进行urlencode编码)
32 | number 准考证号
33 | 请求方式: GET
34 | 请求成功返回json:
35 | {
36 | "code":200,
37 | "message":"查询成功",
38 | "data":{
39 | "name":"成景文", //姓名
40 | "school":"山西大学", //学校
41 | "type":"英语四级", //考试类别
42 | "number":"140010171105929", //准考证号
43 | "total":"402", //总分
44 | "listen":"107", //听力
45 | "read":"153", //阅读
46 | "writing":"142" //写作和翻译
47 | }
48 | }
49 | 请求失败返回json:
50 | {
51 | "code":400,
52 | "message":"查询失败,请检查你的信息是否无误"
53 | }
54 | 注意:以上接口可以使用后台代理请求数据,也可以直接使用ajax/fetch/axios请求数据(因为设置了cors)
55 |
56 |
57 |
58 | 如果使用JSONP,则需要在url里传入callback:
59 | URL:https://cet.lenshen.com/api/search?callback=cb&&number=准考证号&user=姓名
60 | 参数说明:
61 | callback 回调函数名称
62 | user 姓名
63 | number 准考证号
64 | 请求方式: GET
65 | 请求成功返回jsonp:
66 | cb({
67 | "code":200,
68 | "message":"查询成功",
69 | "data":{
70 | "name":"成景文", //姓名
71 | "school":"山西大学", //学校
72 | "type":"英语四级", //考试类别
73 | "number":"140010171105929", //准考证号
74 | "total":"402", //总分
75 | "listen":"107", //听力
76 | "read":"153", //阅读
77 | "writing":"142" //写作和翻译
78 | }
79 | })
80 | 请求失败返回jsonp:
81 | cb({
82 | "code":400,
83 | "message":"查询失败,请检查你的信息是否无误"
84 | })
85 | ```
86 |
87 | 测试用户如下:
88 |
89 | 姓名:成景文
90 |
91 | 准考证号:140010171105929
92 |
93 | # FAQ
94 |
95 | 若使用的过程中遇到问题,可以加官方群交流:611212696
96 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | import Koa from 'koa'
2 | import views from 'koa-views'
3 | import json from 'koa-json'
4 | import bodyparser from 'koa-bodyparser'
5 | import logger from 'koa-logger'
6 | import index from './routes/index'
7 | import api from './routes/api'
8 |
9 | const app = new Koa()
10 |
11 | // middlewares
12 | app.use(bodyparser({
13 | enableTypes: ['json', 'form', 'text']
14 | }))
15 |
16 | app.use(json())
17 | app.use(logger())
18 | app.use(require('koa-static')(__dirname + '/dist'))
19 |
20 | app.use(views(__dirname + '/views', {
21 | extension: 'ejs'
22 | }))
23 |
24 | // logger
25 | app.use(async(ctx, next) => {
26 | const start = new Date()
27 | await next()
28 | const ms = new Date() - start
29 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
30 | })
31 |
32 | // routes
33 | app.use(index.routes(), index.allowedMethods())
34 | app.use(api.routes(), api.allowedMethods())
35 |
36 | module.exports = app
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 | require("babel-core/register")({
7 | presets: ['es2015', 'stage-0']
8 | });
9 | require("babel-polyfill");
10 |
11 | var app = require('../app');
12 | var debug = require('debug')('demo:server');
13 | var http = require('http');
14 |
15 | /**
16 | * Get port from environment and store in Express.
17 | */
18 |
19 | var port = normalizePort(process.env.PORT || '8001');
20 | // app.set('port', port);
21 |
22 | /**
23 | * Create HTTP server.
24 | */
25 |
26 | var server = http.createServer(app.callback());
27 |
28 | /**
29 | * Listen on provided port, on all network interfaces.
30 | */
31 |
32 | server.listen(port);
33 | server.on('error', onError);
34 | server.on('listening', onListening);
35 |
36 | /**
37 | * Normalize a port into a number, string, or false.
38 | */
39 |
40 | function normalizePort(val) {
41 | var port = parseInt(val, 10);
42 |
43 | if (isNaN(port)) {
44 | // named pipe
45 | return val;
46 | }
47 |
48 | if (port >= 0) {
49 | // port number
50 | return port;
51 | }
52 |
53 | return false;
54 | }
55 |
56 | /**
57 | * Event listener for HTTP server "error" event.
58 | */
59 |
60 | function onError(error) {
61 | if (error.syscall !== 'listen') {
62 | throw error;
63 | }
64 |
65 | var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
66 |
67 | // handle specific listen errors with friendly messages
68 | switch (error.code) {
69 | case 'EACCES':
70 | console.error(bind + ' requires elevated privileges');
71 | process.exit(1);
72 | break;
73 | case 'EADDRINUSE':
74 | console.error(bind + ' is already in use');
75 | process.exit(1);
76 | break;
77 | default:
78 | throw error;
79 | }
80 | }
81 |
82 | /**
83 | * Event listener for HTTP server "listening" event.
84 | */
85 |
86 | function onListening() {
87 | var addr = server.address();
88 | var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
89 | debug('Listening on ' + bind);
90 | }
--------------------------------------------------------------------------------
/controller/api.js:
--------------------------------------------------------------------------------
1 | import superagent from 'superagent'
2 | import cheerio from 'cheerio'
3 |
4 | export default class Api {
5 |
6 | /**
7 | * [search 查询成绩]
8 | * @param {[type]} user [姓名]
9 | * @param {[type]} number [准考证号]
10 | * @return {[type]} [json数据]
11 | */
12 | static async search(user, number) {
13 |
14 | // 抓取网页内容
15 | const data = await this.getData(user, number).catch((err) => {
16 | console.log(err)
17 | })
18 |
19 | // 解析网页内容
20 | const result = this.parseData(data)
21 | console.log(result)
22 |
23 | // 返回
24 | return result.total > 0 ? {
25 | "code": 200,
26 | "message": "查询成功",
27 | "data": result
28 | } : {
29 | "code": 400,
30 | "message": "查询失败,请检查你的信息是否无误"
31 | }
32 | }
33 |
34 | /**
35 | * [getData 抓取网页内容]
36 | * @param {[type]} user [姓名]
37 | * @param {[type]} number [准考证号]
38 | * @return {[type]} [json数据]
39 | */
40 | static async getData(user, number) {
41 | return new Promise((resolve, reject) => {
42 | superagent
43 | .get('http://www.chsi.com.cn/cet/query')
44 | .set({
45 | 'Referer': 'http://www.chsi.com.cn/cet/',
46 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36'
47 | })
48 | .query({
49 | zkzh: number,
50 | xm: user
51 | })
52 | .end(function(err, sres) {
53 | if (err) {
54 | reject(err)
55 | }
56 | resolve(sres.text)
57 | })
58 | })
59 | }
60 |
61 | /**
62 | * [parseData 解析网页内容]
63 | * @param {[type]} data [网页内容]
64 | * @return {[type]} [description]
65 | */
66 | static parseData(data) {
67 | let name, school, type, number, total, listen, read, writing
68 |
69 | //解析数据
70 | const $ = cheerio.load(data)
71 | $('table.cetTable tr').each((index, ele) => {
72 | let text = $(ele).find('td').text().trim()
73 | let lastText = $(ele).children().last().text().trim()
74 |
75 | name = index == 0 ? text : name // 姓名
76 | school = index == 1 ? text : school // 学校
77 | type = index == 2 ? text : type //考试类别
78 | number = index == 4 ? text : number //准考证号
79 | total = index == 5 ? ($(ele).find('span.colorRed').text().trim() - 0) : total
80 | listen = index == 6 ? lastText : listen //听力
81 | read = index == 7 ? lastText : read //阅读
82 | writing = index == 8 ? lastText : writing //写作和翻译
83 | })
84 |
85 | return {
86 | name,
87 | school,
88 | type,
89 | number,
90 | total,
91 | listen,
92 | read,
93 | writing
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/controller/cet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 全国大学英语四、六级考试官方成绩查询网站_中国高等教育学生信息网(学信网)
11 |
12 |
13 |
14 |
38 |
39 |
40 |
41 |
50 |
61 |
62 |
63 |
64 |
65 |
66 |
73 |
95 |
98 |
119 |
158 |
159 |
160 |
161 |
2016年下半年全国大学英语四、六级考试(含口试)成绩查询结果
162 |
163 |
164 |
165 |
166 | 姓 名: |
167 | 汪磊
168 | |
169 |
170 |
171 | 学 校: |
172 | 江西师范大学
173 | |
174 |
175 |
176 | 考试级别: |
177 | 英语四级
178 | |
179 |
180 |
181 |
182 | 笔试成绩
183 | |
184 |
185 |
186 | 准考证号: |
187 | 360021162100112
188 | |
189 |
190 |
191 | 总 分: |
192 |
193 |
194 |
195 | 434
196 |
197 |
198 | |
199 |
200 |
201 |
202 | |
203 |
204 | 听 力:
205 | |
206 |
207 |
208 | 171
209 |
210 | |
211 |
212 |
213 | |
214 |
215 | 阅 读:
216 | |
217 |
218 |
219 | 136
220 |
221 | |
222 |
223 |
224 | |
225 |
226 | 写作和翻译:
227 | |
228 |
229 |
230 |
231 |
232 | 127
233 |
234 | |
235 |
236 |
237 |
238 |
239 |
240 | 口试成绩
241 | |
242 |
243 |
244 | 准考证号: |
245 | --
246 | |
247 |
248 |
249 |
250 | 等 级: |
251 | --
252 | |
253 |
254 |
255 |
256 |
261 |
262 | 主管单位:教育部高等教育司 成绩查询承办单位:全国高等学校学生信息咨询与就业指导中心
263 |
264 |
265 |
266 |
273 |
274 |
275 |
276 |
277 |
279 |
301 |
306 |
307 |