diff --git a/internal/service/recruitment/crawler.go b/internal/service/recruitment/crawler.go index b77d38e..6eb6817 100644 --- a/internal/service/recruitment/crawler.go +++ b/internal/service/recruitment/crawler.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/url" + "os/exec" "regexp" "strings" "time" @@ -189,20 +190,60 @@ func walkJSON(node any, out *[]dto.RecruitmentInput) { } // fetchHTML 带超时与浏览器 UA 的请求,降低被反爬概率。 +// 部分政府站对 Go HTTP 客户端做 TLS 指纹级拦截(返回无锚点页面), +// 此时回退系统 curl 重试(容器镜像内置 curl,已验证可拿到完整列表页)。 func fetchHTML(ctx context.Context, u string) (string, error) { client := g.Client() client.SetTimeout(15 * time.Second) client.SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36") client.SetHeader("Accept", "text/html,application/xhtml+xml,*/*") + client.SetHeader("Accept-Language", "zh-CN,zh;q=0.9") resp, err := client.Get(ctx, u) if err != nil { + // 网络层失败:尝试 curl 兜底,避免单点失败。 + if body, e := fetchWithCurl(u); e == nil { + return body, nil + } return "", gerror.Wrapf(err, "fetch %s", u) } defer resp.Close() if resp.StatusCode != 200 { + // 非 200(如 302/403):回退 curl 重试(curl 自动跟随重定向)。 + if body, e := fetchWithCurl(u); e == nil { + return body, nil + } return "", gerror.Newf("HTTP %d for %s", resp.StatusCode, u) } - return resp.ReadAllString(), nil + body := resp.ReadAllString() + // 内容可疑(疑似 WAF/JS 挑战页:无 html 特征或锚点过少)时回退 curl。 + if !looksLikeHtml(body) || len(body) < 3000 { + if cb, e := fetchWithCurl(u); e == nil { + return cb, nil + } + } + return body, nil +} + +// fetchWithCurl 调用系统 curl 抓取(自动跟随重定向,带浏览器 UA)。 +func fetchWithCurl(u string) (string, error) { + args := []string{ + "-s", "-L", "--max-time", "25", + "-A", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36", + "-H", "Accept: text/html,application/xhtml+xml,*/*", + "-H", "Accept-Language: zh-CN,zh;q=0.9", + u, + } + out, err := exec.Command("curl", args...).Output() + if err != nil { + return "", gerror.Wrapf(err, "curl %s", u) + } + return string(out), nil +} + +// looksLikeHtml 粗略判断响应是否为正常 HTML 文档(含