You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go使用goquery提取csrfmiddlewaretoken时出现字符串长度异常问题

问题原因
  • 核心原因是http.Response.Body是流式io.Reader实现,仅支持单次读取:读取完成后流指针会停在末尾,再次读取不会返回任何内容。
  • 你的代码执行流程中,先调用goquery.NewDocumentFromReader(response.Body)读取了全部响应内容解析课程表,此时response.Body已经被读空,后续再将其传入findCsrfMiddlewareToken函数时,函数读取到的是空内容,自然无法匹配到csrf令牌,返回空字符串。
  • 你打印的长度64是第一次正常读取响应体时拿到的令牌长度,0是传入已读空的响应体时的查询结果。
解决方法

有两种可选方案,推荐第二种:

方案1:缓存全量响应内容重复使用

拿到响应后先把完整内容读到字节数组中,需要使用时再生成新的Reader传入:

func (parser *TimetableParser) ParseTimetable(timetableFilterInfo internal.TimetableInfo) (internal.Timetable, error) {
    timetable := internal.Timetable{}

    requestBody := makeFormValues(timetableFilterInfo, parser.csrfMiddlewareToken).Encode()
    request, err := http.NewRequest("POST", baseUrl, strings.NewReader(requestBody))
    if err != nil {
        return timetable, err
    }
    request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    request.Header.Add("Content-Length", strconv.Itoa(len(requestBody)))
    request.Header.Add("Referer", baseUrl)

    response, err := parser.client.Do(request)
    if err != nil {
        return timetable, err
    }
    defer response.Body.Close()

    // 新增:读取全量响应内容缓存
    bodyBytes, err := io.ReadAll(response.Body)
    if err != nil {
        return timetable, err
    }

    // 用缓存内容生成新Reader解析课程表
    document, err := goquery.NewDocumentFromReader(bytes.NewReader(bodyBytes))
    if err != nil {
        return timetable, err
    }

    document.Find("table#schedule").Find("tr").Each(func(rowIndex int, row *goquery.Selection) {
        subjectTimeElement := row.Closest("td")
        subjectTimeElement.NextAll().Each(func(columnIndex int, cell *goquery.Selection) {
            subjectInfo := extractSubjectInfoFromCell(cell)
            subjectInfo.Order = rowIndex
            timetable.Subjects[columnIndex][rowIndex] = subjectInfo
        })
    })

    // 用缓存内容生成新Reader查询csrf令牌
    parser.csrfMiddlewareToken, err = findCsrfMiddlewareToken(bytes.NewReader(bodyBytes))
    if err != nil {
            log.Println("csrfMiddlewareToken: " + err.Error())
    }
    return timetable, nil
}

方案2:复用已解析的document对象(推荐)

你已经解析过一次响应内容得到了document对象,完全不需要重复解析响应体,直接调整findCsrfMiddlewareToken的入参即可,性能更高:

  1. 首先修改令牌查询函数的参数:
func findCsrfMiddlewareToken(document *goquery.Document) (string, error) {
    var foundCsrfToken string
    document.Find("script").Each(func(_ int, scrpt *goquery.Selection) {
        scriptText := scrpt.Text()
        if funcDefIndex := strings.Index(scriptText, "function Filter"); funcDefIndex != -1 {
            csrfTokenValueStart := strings.Index(scriptText, "csrfmiddlewaretoken: '")
            // 新增边界判断,避免匹配不到时切片越界panic
            if csrfTokenValueStart == -1 {
                return
            }
            offset := csrfTokenValueStart + len("csrfmiddlewaretoken: '")
            if offset + csrfMiddlewareTokenLength > len(scriptText) {
                return
            }
            foundCsrfToken = scriptText[offset : offset+csrfMiddlewareTokenLength]
        }
    })
    if len(foundCsrfToken) == 0 {
        return "", errNoCsrfMiddlewareToken
    }
    return foundCsrfToken, nil
}
  1. 调用时直接传入已有的document对象即可:
// 替换原来的findCsrfMiddlewareToken调用行
parser.csrfMiddlewareToken, err = findCsrfMiddlewareToken(document)
补充优化点

原令牌查询逻辑没有做边界判断,如果脚本中没有匹配到csrfmiddlewaretoken: '字符串,或者后续长度不足csrfMiddlewareTokenLength,会直接触发切片越界panic,上面方案2的代码中已经补充了对应的判断逻辑,建议加上。

内容的提问来源于stack exchange,提问作者Akmubi Lin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 00:27:04