Files
tjzx-spider/main.go
T
2026-07-13 21:16:06 +08:00

170 lines
5.5 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
// 使用纯 Go 的 SQLite 驱动,免除 CGO 依赖
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
// ---- 1. 定义读取 JSON 用的结构体 ----
type JSONResponse struct {
ResponseCode string `json:"responseCode"`
ResponseData RespData `json:"responseData"`
}
type RespData struct {
Datas []QuestionData `json:"datas"`
}
type QuestionData struct {
QueId int `json:"queId"`
Stem string `json:"stem"`
QuestionAnswers []QuestionAnswer `json:"questionAnswers"`
}
type QuestionAnswer struct {
AnswerDesc string `json:"answerDesc"`
IsCorrect int `json:"isCorrect"`
}
// ---- 2. 定义对应 SQLite 的多选题 GORM 模型 ----
type Duoxuan struct {
ID int `gorm:"column:id;primaryKey"`
Nr string `gorm:"column:nr"`
Da1 *string `gorm:"column:da_1"` // 使用指针类型,当值为 nil 时,写入数据库就是 NULL
Da2 *string `gorm:"column:da_2"`
Da3 *string `gorm:"column:da_3"`
Da4 *string `gorm:"column:da_4"`
}
// TableName 指定绑定的表名为 duoxuan
func (Duoxuan) TableName() string {
return "duoxuan"
}
func main() {
// ==================== 第一步:发送多选题的网络请求 ====================
client := &http.Client{}
var data = strings.NewReader(`rows=200&page=1&ownerId=229&btId=102404796&queTypeCode=MC&clickQueId=240677024`)
req, err := http.NewRequest("POST", "https://sia.sinopec.com/ac/seiPracController/querySeiPracQuestionForPage.do", data)
if err != nil {
log.Fatalf("创建请求失败: %v", err)
}
// 设置 Headers 和 Cookie
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
req.Header.Set("Access-Control-Allow-Origin", "*")
req.Header.Set("Authorization", "00000-181M")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
req.Header.Set("Origin", "https://sia.sinopec.com")
req.Header.Set("Referer", "https://sia.sinopec.com/mobile/")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "same-origin")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0")
req.Header.Set("sec-ch-ua", `"Not;A=Brand";v="8", "Chromium";v="150", "Microsoft Edge";v="150"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
req.Header.Set("Cookie", "accessId=839d6250-5041-11ee-96f3-ef2d7900d919; x-forwarded-for=null; zshIo=https%3A%2F%2Fsia.sinopec.com%2Fmobile%2F%23%2Fapp%2Fautonomous%2Ftest%2Fpractice%2Ftopics%3FqbId%3D263%26titleName%3D7%25E7%259F%25B3%25E5%25B7%25A5%25E5%25BB%25BA%252F%25E4%25B8%2593%25E4%25B8%259A%25E7%259F%25A5%25E8%25AF%2586%26domainId%3D6026f6138212ebda9f65e27591ac399f; types=0; Admin-Token=00000-181M; LEARNSSIONID=ccdc9e86-f458-472e-a31c-5ff82876f93f; pageViewNum=4; SERVERID=08454d75353b657bc1b9c1181db254db|1783945192|1783941591")
resp, err := client.Do(req)
if err != nil {
log.Fatalf("发送请求失败: %v", err)
}
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("读取响应失败: %v", err)
}
// ==================== 第二步:解析返回的 JSON ====================
var apiResponse JSONResponse
err = json.Unmarshal(bodyText, &apiResponse)
if err != nil {
log.Fatalf("JSON 解析失败: %v", err)
}
// ==================== 第三步:连接数据库并写入 ====================
db, err := gorm.Open(sqlite.Open("osecdtzx_question.db"), &gorm.Config{})
if err != nil {
log.Fatalf("数据库连接失败: %v", err)
}
// 自动检查并迁移表结构
db.AutoMigrate(&Duoxuan{})
// 准备批量插入的数据切片
var rowsToInsert []Duoxuan
for _, item := range apiResponse.ResponseData.Datas {
// 1. 收集当前题目所有正确的答案
var correctAnswers []string
for _, ans := range item.QuestionAnswers {
if ans.IsCorrect == 1 {
correctAnswers = append(correctAnswers, ans.AnswerDesc)
}
}
// 2. 将正确的答案分配到 da_1, da_2, da_3, da_4 指针字段
// 默认都保持为 nil,对应的就是数据库里的 NULL
var d1, d2, d3, d4 *string
// 使用临时变量获取地址,防止迭代循环中的地址复用
if len(correctAnswers) > 0 {
val := correctAnswers[0]
d1 = &val
}
if len(correctAnswers) > 1 {
val := correctAnswers[1]
d2 = &val
}
if len(correctAnswers) > 2 {
val := correctAnswers[2]
d3 = &val
}
if len(correctAnswers) > 3 {
val := correctAnswers[3]
d4 = &val
}
// 3. 清洗可能残存的二次转义换行字符
cleanStem := strings.ReplaceAll(item.Stem, `\n`, "\n")
cleanStem = strings.ReplaceAll(cleanStem, `\r\n`, "\n")
// 4. 构建多选题模型
row := Duoxuan{
ID: item.QueId,
Nr: cleanStem,
Da1: d1,
Da2: d2,
Da3: d3,
Da4: d4,
}
rowsToInsert = append(rowsToInsert, row)
}
// 执行批量存入
if len(rowsToInsert) > 0 {
// db.Save 在执行具有 NULL 值的更新和插入时也完美支持
result := db.Save(&rowsToInsert)
if result.Error != nil {
log.Fatalf("数据保存进数据库失败: %v", result.Error)
}
fmt.Printf("🎉 多选题处理成功!已写入/更新 %d 条题目数据(空缺答案已被留空为 NULL)。\n", result.RowsAffected)
} else {
fmt.Println("⚠️ 未在接口返回中解析到有效的多选题数据。")
}
}