Files
WuYuuuub 04ad865bf2
All checks were successful
Build / build (push) Successful in 9m51s
cet成绩查询
2026-06-17 22:41:52 +08:00

185 lines
4.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package parser
import (
"fmt"
"regexp"
"strings"
"github.com/PuerkitoBio/goquery"
)
// CETScore 四六级成绩条目。
type CETScore struct {
Level string // 等级:英语四级/英语六级
ExamTime string // 考试时间:如 2025年06月
TicketNum string // 准考证号
Score string // 成绩
}
// CETStudentInfo 四六级查询中的学生信息。
type CETStudentInfo struct {
StudentID string // 学号
Name string // 姓名
Gender string // 性别
IDCard string // 身份证号
Duration string // 学制
Grade string // 年级
College string // 学院
Major string // 专业
}
// CETResult 四六级查询结果。
type CETResult struct {
StudentInfo CETStudentInfo
Scores []CETScore
}
// cetScoreRegex 匹配四六级成绩行。
// 示例:英语六级 时间 2025年06月 准考证号 372220251213105 成绩 445
var cetScoreRegex = regexp.MustCompile(`(英语[四六]级)\s+时间\s*([0-9]{4}年[0-9]{1,2}月)\s+准考证号\s*([0-9A-Za-z]+)\s+成绩\s*([0-9]+|缺考|合格|不合格|优秀|良好|通过|未通过)`)
// FetchCETHTML 解析四六级成绩 HTML 页面。
func ParseCETHTML(html string) (*CETResult, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, err
}
result := &CETResult{}
table := findCETTable(doc)
if table.Length() == 0 {
return nil, fmt.Errorf("未找到四六级成绩表格")
}
parseCETStudentInfo(table, &result.StudentInfo)
result.Scores = parseCETScores(table)
if len(result.Scores) == 0 {
result.Scores = parseCETScoresFromText(doc.Find(".jdcx_area").Text())
}
if len(result.Scores) == 0 {
result.Scores = parseCETScoresFromText(doc.Find(".Contentbox").Text())
}
if len(result.Scores) == 0 {
result.Scores = parseCETScoresFromText(doc.Text())
}
return result, nil
}
func findCETTable(doc *goquery.Document) *goquery.Selection {
selectors := []string{
"table.addlist",
".jdcx_area table",
"form#ff table",
"table:contains('已有成绩')",
}
for _, selector := range selectors {
table := doc.Find(selector).First()
if table.Length() > 0 {
return table
}
}
return doc.Find("__not_found__")
}
func parseCETStudentInfo(table *goquery.Selection, info *CETStudentInfo) {
table.Find("tr").Each(func(_ int, row *goquery.Selection) {
var label string
row.Find("th, td").Each(func(_ int, cell *goquery.Selection) {
text := normalizeCETText(cell.Text())
if text == "" {
return
}
if cell.Is("th") {
label = normalizeCETLabel(text)
return
}
assignCETStudentField(info, label, text)
label = ""
})
})
}
func assignCETStudentField(info *CETStudentInfo, label, value string) {
switch label {
case "学号":
info.StudentID = value
case "姓名":
info.Name = value
case "性别":
info.Gender = value
case "身份证号":
info.IDCard = value
case "学制":
info.Duration = value
case "年级":
info.Grade = value
case "学院":
info.College = value
case "专业":
info.Major = value
}
}
func parseCETScores(table *goquery.Selection) []CETScore {
var scores []CETScore
table.Find("tr").Each(func(_ int, row *goquery.Selection) {
rowText := normalizeCETText(row.Text())
if !strings.Contains(rowText, "英语四级") && !strings.Contains(rowText, "英语六级") {
return
}
scores = append(scores, parseCETScoresFromText(rowText)...)
})
return deduplicateCETScores(scores)
}
func parseCETScoresFromText(text string) []CETScore {
text = normalizeCETText(text)
matches := cetScoreRegex.FindAllStringSubmatch(text, -1)
scores := make([]CETScore, 0, len(matches))
for _, match := range matches {
if len(match) < 5 {
continue
}
scores = append(scores, CETScore{
Level: strings.TrimSpace(match[1]),
ExamTime: strings.TrimSpace(match[2]),
TicketNum: strings.TrimSpace(match[3]),
Score: strings.TrimSpace(match[4]),
})
}
return deduplicateCETScores(scores)
}
func deduplicateCETScores(scores []CETScore) []CETScore {
if len(scores) <= 1 {
return scores
}
seen := make(map[string]struct{}, len(scores))
result := make([]CETScore, 0, len(scores))
for _, score := range scores {
key := strings.Join([]string{score.Level, score.ExamTime, score.TicketNum, score.Score}, "|")
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
result = append(result, score)
}
return result
}
func normalizeCETLabel(label string) string {
label = normalizeCETText(label)
label = strings.ReplaceAll(label, " ", "")
label = strings.ReplaceAll(label, "", "")
label = strings.ReplaceAll(label, ":", "")
return label
}
func normalizeCETText(text string) string {
text = strings.ReplaceAll(text, "\u00a0", " ")
return strings.Join(strings.Fields(strings.TrimSpace(text)), " ")
}