-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcet.go
186 lines (158 loc) · 4 KB
/
cet.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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
package cet
import (
"context"
"errors"
"fmt"
"math"
"math/rand"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/PuerkitoBio/goquery"
)
var (
query Querier = &querier{}
)
// Query 根据准考证号、姓名查询四六级成绩
func Query(ctx context.Context, ticket, name string) (*Result, error) {
return query.Query(ctx, ticket, name)
}
// Querier 提供四六级成绩查询的接口
type Querier interface {
// 根据准考证号、姓名查询
Query(ctx context.Context, ticket, name string) (*Result, error)
}
// Result 查询结果
type Result struct {
Name string // 姓名
University string // 学校
Level string // 考试级别
WrittenTicket string // 笔试准考证号
Score float32 // 总分
Listening float32 // 听力
Reading float32 // 阅读
WritingTranslation float32 // 写作和翻译
OralTicket string // 口试准考证号
OralLevel string // 口试等级
}
const (
// 四六级查询的网址
cetHost = "www.chsi.com.cn"
)
var (
// ErrNotFound not found
ErrNotFound = errors.New("not found")
)
// NewQuerier 创建Querier接口
func NewQuerier() Querier {
return &querier{}
}
type querier struct {
}
func (q *querier) Query(ctx context.Context, ticket, name string) (result *Result, err error) {
if ctx == nil {
ctx = context.Background()
}
u := &url.URL{
Scheme: "http",
Host: cetHost,
Path: "cet/query",
}
req, err := http.NewRequest(http.MethodPost, u.String(), nil)
if err != nil {
return
}
uq := req.URL.Query()
uq.Set("zkzh", ticket)
uq.Set("xm", name)
req.URL.RawQuery = uq.Encode()
req.Header.Set("Referer", fmt.Sprintf("http://%s/cet/", cetHost))
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36")
req.Header.Set("X-Forwarded-For", q.randomIP())
err = q.httpDo(ctx, req, func(resp *http.Response, err error) error {
if err != nil {
return err
}
r, err := q.parse(resp)
if err != nil {
return err
}
result = r
return nil
})
return
}
var (
rd = rand.New(rand.NewSource(math.MaxInt64))
)
func (q *querier) randomIP() string {
return fmt.Sprintf("%d.%d.%d.%d", rd.Intn(255), rd.Intn(255), rd.Intn(255), rd.Intn(255))
}
func (q *querier) httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error {
tr := &http.Transport{}
client := &http.Client{Transport: tr}
c := make(chan error, 1)
go func() { c <- f(client.Do(req)) }()
select {
case <-ctx.Done():
tr.CancelRequest(req)
<-c
return ctx.Err()
case err := <-c:
return err
}
}
func (q *querier) parse(resp *http.Response) (result *Result, err error) {
doc, err := goquery.NewDocumentFromResponse(resp)
if err != nil {
return
}
childs := doc.Find(".m_cnt_m").Children()
if !childs.HasClass("cetTable") {
err = ErrNotFound
return
}
result = new(Result)
scoreType := 0 // 成绩类型:1笔试成绩 2口试成绩
childs.Find(".cetTable tr").Each(func(i int, s *goquery.Selection) {
val := strings.TrimSpace(s.Find("td").Last().Text())
if val == "--" {
return
}
text := strings.TrimSpace(s.Find("th").Text())
switch text {
case "姓 名:":
result.Name = val
case "学 校:":
result.University = val
case "考试级别:":
result.Level = val
case "笔试成绩":
scoreType = 1
case "准考证号:":
if scoreType == 1 {
result.WrittenTicket = val
} else if scoreType == 2 {
result.OralTicket = val
}
case "总 分:":
fv, _ := strconv.ParseFloat(val, 32)
result.Score = float32(fv)
case "听 力:":
fv, _ := strconv.ParseFloat(val, 32)
result.Listening = float32(fv)
case "阅 读:":
fv, _ := strconv.ParseFloat(val, 32)
result.Reading = float32(fv)
case "写作和翻译:":
fv, _ := strconv.ParseFloat(val, 32)
result.WritingTranslation = float32(fv)
case "口试成绩":
scoreType = 2
case "等 级:":
result.OralLevel = val
}
})
return
}