如何用Golang+goquery爬取动态验证码并维持会话提交表单?
解决方案
步骤1:保存首次请求页面的验证码图片
核心是复用会话(Cookie),因为验证码与当前会话绑定,单独请求验证码地址会拿到新的无效验证码。具体实现:
package main import ( "io" "net/http" "net/http/cookiejar" "net/url" "os" "github.com/PuerkitoBio/goquery" ) func main() { // 初始化带CookieJar的客户端,维持会话一致性 jar, _ := cookiejar.New(nil) client := &http.Client{Jar: jar} // 1. 请求目标页面(首次请求,必须保留会话) targetURL := "https://example.com/your-target-page" resp, err := client.Get(targetURL) if err != nil { panic(err) } defer resp.Body.Close() // 2. 解析页面,提取验证码图片的src属性 doc, err := goquery.NewDocumentFromReader(resp.Body) if err != nil { panic(err) } captchaSrc, exists := doc.Find("img[src*='/nn/mm/captchaimg']").Attr("src") if !exists { panic("未找到验证码图片标签") } // 拼接完整的验证码请求URL(处理相对路径) baseURL, _ := url.Parse(targetURL) captchaURL := baseURL.ResolveReference(&url.URL{Path: captchaSrc}).String() // 3. 用同一个客户端请求验证码,保存图片到本地 captchaResp, err := client.Get(captchaURL) if err != nil { panic(err) } defer captchaResp.Body.Close() file, err := os.Create("captcha.png") if err != nil { panic(err) } defer file.Close() _, err = io.Copy(file, captchaResp.Body) if err != nil { panic(err) } }
关键要点:
- 必须复用同一个
http.Client(带CookieJar)请求页面和验证码,确保会话一致; - 处理验证码的相对路径,拼接成完整URL避免请求错误;
- 保存的图片就是首次页面绑定的有效验证码,可直接传入深度学习模型识别。
步骤3:维持会话提交表单
核心是提取首次页面的所有表单参数(包括隐藏字段),复用会话提交,不能重新请求页面。代码示例(承接上面的main函数):
// 接步骤1的代码,在保存验证码之后 // 1. 解析表单的action地址和所有字段 form := doc.Find("form").First() formAction, exists := form.Attr("action") if !exists { panic("未找到表单提交地址") } // 拼接完整的表单提交URL formSubmitURL := baseURL.ResolveReference(&url.URL{Path: formAction}).String() // 收集表单所有字段(含隐藏字段,如CSRF Token) formData := url.Values{} form.Find("input").Each(func(i int, s *goquery.Selection) { name, _ := s.Attr("name") value, _ := s.Attr("value") if name != "" { formData.Set(name, value) } }) // 2. 填入深度学习模型识别的验证码结果(替换为实际字段名,如"verify_code") recognizedCode := "XXXX" // 替换为你的识别结果 formData.Set("captcha_code", recognizedCode) // 3. 用同一个客户端提交表单,维持会话 submitResp, err := client.PostForm(formSubmitURL, formData) if err != nil { panic(err) } defer submitResp.Body.Close() // 读取提交结果(可选,用于验证是否提交成功) submitBody, _ := io.ReadAll(submitResp.Body) println(string(submitBody))
关键要点:
- 必须从首次请求的页面提取所有表单字段,包括CSRF Token这类会话绑定的隐藏字段,重新请求页面会导致字段失效;
- 提交时复用同一个
http.Client,确保Cookie与首次请求一致; - 确认验证码对应的表单字段名(需根据目标页面实际调整,比如可能是
captcha、verify_code等)。
额外注意事项
- 验证码有效期1-2分钟,需控制识别和提交的总时长,避免超时;
- 部分网站会验证请求头,可给客户端统一设置模拟浏览器的请求头:
// 替换原有的client.Get(targetURL),手动构造请求添加头 req, err := http.NewRequest("GET", targetURL, nil) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36") req.Header.Set("Referer", targetURL) resp, err := client.Do(req)
内容的提问来源于stack exchange,提问作者Hong Sup Lee
相关产品推荐
相关产品推荐

