{"record":{"id":"1d20142185c66275","repo":"xpzouying/xiaohongshu-mcp","slug":"failed-to-download-s-w","errorCode":null,"errorMessage":"failed to download %s: %w","messagePattern":"failed to download (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/downloader/images.go","lineNumber":114,"sourceCode":"\t}\n\n\t// 保存到文件\n\tif err := os.WriteFile(filePath, imageData, 0644); err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to save image\")\n\t}\n\n\treturn filePath, nil\n}\n\n// DownloadImages 批量下载图片\nfunc (d *ImageDownloader) DownloadImages(imageURLs []string) ([]string, error) {\n\tvar localPaths []string\n\tvar errs []error\n\n\tfor _, imageURL := range imageURLs {\n\t\tlocalPath, err := d.DownloadImage(imageURL)\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"failed to download %s: %w\", imageURL, err))\n\t\t\tcontinue\n\t\t}\n\t\tlocalPaths = append(localPaths, localPath)\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn localPaths, fmt.Errorf(\"download errors occurred: %v\", errs)\n\t}\n\n\treturn localPaths, nil\n}\n\n// isValidImageURL 检查是否为有效的图片URL\nfunc (d *ImageDownloader) isValidImageURL(rawURL string) bool {\n\t// 检查是否以http/https开头\n\tif !strings.HasPrefix(strings.ToLower(rawURL), \"http://\") &&\n\t\t!strings.HasPrefix(strings.ToLower(rawURL), \"https://\") {\n\t\treturn false","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/xpzouying/xiaohongshu-mcp/blob/332d196854a9eac0d2b8c2c0e3d0cc43139d724c/pkg/downloader/images.go#L96-L132","documentation":"DownloadImages loops over imageURLs, calls DownloadImage for each, and aggregates per-URL failures via %w wrapping while continuing with the rest. This error reports that a specific URL failed, with the underlying cause (e.g. the 'download failed with status %d' error from DownloadImage) available through errors.Unwrap/As.","triggerScenarios":"Any imageURL in the input slice for which d.DownloadImage returns an error (non-200 response, network failure, read failure); the error is wrapped as \"failed to download <imageURL>: <cause>\" and appended to errs; after the loop all wrapped errors are joined and returned.","commonSituations":"Batch-scraping a page where some images return 403/404 (hotlink protection, removed assets); one host rate-limits (429) mid-batch; DNS or TLS failures for a single CDN domain; expired signed URLs among otherwise valid ones.","solutions":["Unwrap the returned joined errors (errors.As / strings.Split on the joined message) to find which URLs failed and why, then handle per cause","Re-run DownloadImages with only the failed URLs after addressing the cause (backoff for 429, fresh URLs for 404/403)","Add per-host throttling or concurrency limits to avoid rate limiting during batch downloads","Treat it as partial failure: successful localPaths are still returned/collected, so continue processing what succeeded","Pre-validate URLs (HEAD request or scheme/host check) before the batch to skip known-bad ones"],"exampleFix":"// before\nlocalPath, err := d.DownloadImage(imageURL)\nif err != nil {\n\terrs = append(errs, fmt.Errorf(\"failed to download %s: %w\", imageURL, err))\n\tcontinue\n}\n// after — retry transient failures once before giving up\nlocalPath, err := d.DownloadImage(imageURL)\nif err != nil {\n\ttime.Sleep(2 * time.Second)\n\tlocalPath, err = d.DownloadImage(imageURL)\n\tif err != nil {\n\t\terrs = append(errs, fmt.Errorf(\"failed to download %s: %w\", imageURL, err))\n\t\tcontinue\n\t}\n}","handlingStrategy":"try-catch","validationCode":"// 批量前预检\nvalid := make([]string, 0, len(imageURLs))\nfor _, u := range imageURLs {\n\tif pu, err := url.Parse(u); err == nil && (pu.Scheme == \"http\" || pu.Scheme == \"https\") && pu.Host != \"\" {\n\t\tvalid = append(valid, u)\n\t}\n}","typeGuard":null,"tryCatchPattern":"paths, err := d.DownloadImages(ctx, urls)\nif err != nil {\n\t// 部分失败：paths 中成功的仍可用；对 err 逐条解包定位失败 URL\n\tfor _, u := range strings.Split(err.Error(), \"\\n\") {\n\t\tlog.Printf(\"批量下载部分失败: %s\", u)\n\t}\n\tretryFailed(extractFailedURLs(err))\n}","preventionTips":["Treat batch downloads as partial-failure operations; check both returned paths and error","Unwrap wrapped errors to identify per-URL causes before blanket retries","Limit concurrency and add per-host rate limits for large batches","Persist failed URLs to a queue for a later retry pass"],"tags":["network","batch-download","error-wrapping","image-download"],"backgroundTag":"batch-download-partial-failure","analyzedSha":"332d196854a9eac0d2b8c2c0e3d0cc43139d724c","analyzedAt":"2026-09-05T22:22:55.988Z","contentChangedAt":"2026-09-05T22:22:55.988Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}