fish2018/pansou · error
解析 Anubis 验证题目失败
Error message
解析 Anubis 验证题目失败: %w
What it means
parseAnubisChallenge extracts the challenge JSON from the gate page via regex and unmarshals it into anubisChallengeDocument after HTML-unescaping. This error wraps a json.Unmarshal failure (miosou.go:262) — the embedded JSON does not match the expected schema.
Solutions
- Update the anubisChallengeDocument struct tags to match the current Anubis challenge JSON schema.
- Log the raw matched blob (html.UnescapeString(match[1])) to see the actual format and diff it against the struct.
- Verify the response was not truncated (1 MiB LimitReader) and no proxy rewrote the page.
- Run TestParseAndSolveAnubisChallenge with a fixture captured from the live server to confirm the parser.
Example fix
// before
type anubisChallengeDocument struct {
Rules struct {
Algorithm string `json:"algorithm"`
Difficulty int `json:"difficulty"`
} `json:"rules"`
...
}
// after — align tags with the server's current schema, e.g.
type anubisChallengeDocument struct {
Rules struct {
Algorithm string `json:"algorithm"`
Difficulty int `json:"difficulty"`
} `json:"rules"`
Challenge struct {
ID string `json:"id"`
RandomData string `json:"random_data"` // renamed upstream
} `json:"challenge"`
} Defensive patterns
Strategy: validation
Validate before calling
var probe map[string]any
blob := html.UnescapeString(string(match[1]))
if err := json.Unmarshal([]byte(blob), &probe); err != nil {
return fmt.Errorf("challenge blob is not valid JSON: %w", err)
}
if _, ok := probe["challenge"]; !ok || _, ok := probe["rules"]; !ok {
return fmt.Errorf("challenge JSON missing expected keys")
} Type guard
func looksLikeAnubisChallenge(blob string) bool {
var probe struct {
Rules map[string]any `json:"rules"`
Challenge map[string]any `json:"challenge"`
}
return json.Unmarshal([]byte(blob), &probe) == nil &&
probe.Rules != nil && probe.Challenge != nil
} Try / catch
challenge, found, err := parseAnubisChallenge(body)
if err != nil {
if strings.Contains(err.Error(), "解析 Anubis 验证题目失败") {
// schema drift: log raw blob and update struct tags
}
return err
} Prevention
- Keep anubisChallengeDocument tags in sync with upstream Anubis
- Capture and test against real gate-page fixtures (TestParseAndSolveAnubisChallenge)
- Log the raw matched blob on failure
- Watch for proxy/CDN page rewriting
When it happens
Trigger: The regex matched a challenge blob but json.Unmarshal fails: Anubis changed the JSON shape (renamed fields rules/challenge/id/randomData/algorithm/difficulty), HTML entity decoding left invalid JSON, or the body was truncated/corrupted.
Common situations: An Anubis server upgrade changing the challenge document format; a proxy/CDN mangling or truncating the page; a localized or customized Anubis deployment emitting different JSON.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/97cdba7c1d7c680b.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/miosou/miosou.go:262
type anubisChallengeDocument struct {
Rules struct {
Algorithm string `json:"algorithm"`
Difficulty int `json:"difficulty"`
} `json:"rules"`
Challenge struct {
ID string `json:"id"`
RandomData string `json:"randomData"`
} `json:"challenge"`
}
func parseAnubisChallenge(body []byte) (anubisChallengeDocument, bool, error) {
match := anubisChallengePattern.FindSubmatch(body)
if len(match) != 2 {
return anubisChallengeDocument{}, false, nil
}
var challenge anubisChallengeDocument
if err := json.Unmarshal([]byte(html.UnescapeString(string(match[1]))), &challenge); err != nil {
return anubisChallengeDocument{}, true, fmt.Errorf("解析 Anubis 验证题目失败: %w", err)
}
if challenge.Challenge.ID == "" || challenge.Challenge.RandomData == "" || challenge.Rules.Difficulty < 1 || challenge.Rules.Difficulty > 7 {
return anubisChallengeDocument{}, true, fmt.Errorf("Anubis 验证题目无效")
}
if challenge.Rules.Algorithm != "fast" && challenge.Rules.Algorithm != "slow" {
return anubisChallengeDocument{}, true, fmt.Errorf("不支持的 Anubis 验证算法: %s", challenge.Rules.Algorithm)
}
return challenge, true, nil
}
func solveAnubisChallenge(ctx context.Context, randomData string, difficulty int) (string, uint64, error) {
prefix := []byte(randomData)
buffer := make([]byte, len(prefix), len(prefix)+20)
copy(buffer, prefix)
for nonce := uint64(0); ; nonce++ {
if nonce&4095 == 0 {
select {
case <-ctx.Done():View on GitHub (pinned to beaa561337)