PanSou 插件开发实战Sousou 网盘聚合搜索 JSON API 数据结构与 Go 插件实现解析【免费下载链接】pansouPanSou是一款高性能的网盘资源搜索API服务支持TG频道和插件搜索。系统设计以性能和可扩展性为核心支持多频道多插件并发搜索、结果智能排序和网盘类型分类。docker集成前后端一键启动开箱即用。仅供学习研究请勿以各种形式用于盈利目的。 https://t.me/s/webhtv项目地址: https://gitcode.com/gh_mirrors/pan/pansou本篇文章以 PanSou 开源仓库中 plugin/sousou/json结构分析.md 为核心系统讲解 Sousou 数据源sousou.pro网盘聚合搜索 API的请求参数、响应 JSON 结构、字段映射规则与多页并发抓取策略并结合仓库内 sousou.go 的真实实现与 sousou_test.go 测试用例进行源码级印证。读完本文你将掌握在 PanSou 插件体系中从JSON 接口分析到标准 SearchResult 构建的完整开发路径并能直接复用文中的数据结构定义与并发分页模板。一、Sousou 插件与结构分析文档的定位Sousou 是 PanSou 网盘资源搜索服务中的一个异步搜索插件数据源类型为 JSON APIGET 请求提供网盘资源聚合搜索并支持多种网盘类型过滤。该插件在 sousou.go 中通过init()注册进全局插件注册表func init() { plugin.RegisterGlobalPlugin(NewSousouAsyncPlugin()) }插件名称为sousou优先级为 3普通质量数据源对应构造函数 NewSousouAsyncPluginfunc NewSousouAsyncPlugin() *SousouAsyncPlugin { return SousouAsyncPlugin{ BaseAsyncPlugin: plugin.NewBaseAsyncPlugin(sousou, 3), } }BaseAsyncPlugin由 plugin/plugin.go 提供封装了内存缓存、工作池、超时控制等异步插件基础设施。结构分析文档json结构分析.md正是为这类插件开发提供接口层依据它回答了三个核心问题——如何构造请求、如何解析响应、如何把响应字段映射为 PanSou 的统一数据模型。二、API 基本信息与请求参数2.1 基本信息数据源类型JSON APIGET 请求API URL 格式https://sousou.pro/api.php?actionsearchq{关键词}page{页码}per_size{每页数量}type{网盘类型}数据特点网盘资源聚合搜索支持多种网盘类型过滤源码 sousou.go 中将 API 端点与默认参数定义为常量const ( SousouAPI https://sousou.pro/api.php SousouWebURL https://www.panso.vip/search DefaultPerSize 30 DefaultMaxPages 3 )2.2 请求参数表参数名类型必填说明示例值actionstring是操作类型固定为searchsearchqstring是搜索关键词遮天pageint否页码从 1 开始1per_sizeint否每页返回数量10typestring否网盘类型过滤为空表示全部QUARK, BDY, ALY, XUNLEI, UC, 115从源码看插件实际发送请求时会对关键词做 URL 编码url.QueryEscape(keyword)并显式传入page、per_size30、type三个参数见 searchByTypeapiURL : fmt.Sprintf(%s?actionsearchq%spage%dper_size%dtype%s, SousouAPI, url.QueryEscape(keyword), pageNum, DefaultPerSize, diskType, )2.3 网盘类型参数说明参数值含义QUARK夸克网盘BDY百度网盘ALY阿里云盘XUNLEI迅雷网盘UCUC 网盘115115 网盘留空搜索所有网盘类型仓库中 supportedDiskTypes 常量保存了插件实际并发遍历的 6 种网盘类型与文档一致其余类型如TIANYI、CAIYUN、123PAN、PIKPAK虽然 API 标识与系统类型映射表中存在但不在默认并发类型列表中。三、API 响应结构解析3.1 顶层结构{ code: 200, // 状态码200表示成功 msg: 请求成功, // 响应消息 data: { total: 200, // 总记录数 per_size: 10, // 每页数量 took: 62, // 搜索耗时毫秒 list: [] // 数据列表数组 } }源码 SousouResponse 对该结构做了严格的 Go 结构体绑定type SousouResponse struct { Code int json:code Msg string json:msg Data struct { Total int json:total PerSize int json:per_size Took int json:took List []SousouItem json:list } json:data }插件在解析响应后还会校验Code是否为 200非 200 会被当作错误写入错误通道见 searchByType。3.2data.list数组中的数据项结构{ disk_id: bd8623b72cbb, // 网盘分享ID disk_name: 美漫之黑手遮天-西风啸月.txt, // 资源标题/文件名 disk_pass: , // 提取码可能为空 disk_type: QUARK, // 网盘类型标识 files: file:美漫之黑手遮天-西风啸月.txt, // 文件列表描述 doc_id: cmhfmcz0l2emoae7mf7amvbsi, // 文档ID share_user: 安心*海豹, // 分享用户脱敏 share_user_id: , // 分享用户ID可能为空 shared_time: 2025-10-27 21:38:59, // 分享时间 rel_movie: , // 相关电影可能为空 is_mine: true, // 是否我的分享 tags: null, // 标签数组可能为null或字符串数组 link: https://pan.quark.cn/s/bd8623b72cbb, // 分享链接 enabled: true, // 是否启用 weight: 1, // 权重 status: 0 // 状态 }对应的 Go 结构体为 SousouItem其中Tags字段类型被声明为interface{}正是因为该字段可能为 null 或字符串数组type SousouItem struct { DiskID string json:disk_id DiskName string json:disk_name DiskPass string json:disk_pass DiskType string json:disk_type Files string json:files DocID string json:doc_id ShareUser string json:share_user ShareUserID string json:share_user_id SharedTime string json:shared_time RelMovie string json:rel_movie IsMine bool json:is_mine Tags interface{} json:tags // 可能为null或字符串数组 Link string json:link Enabled bool json:enabled Weight int json:weight Status int json:status }3.3 字段说明核心字段disk_id网盘分享的唯一标识符用于去重和构建 UniqueIDdisk_name资源标题通常是文件名或文件夹名disk_pass提取码/访问密码可能为空字符串disk_type网盘类型标识符API 特定格式link完整的分享链接 URL扩展信息字段files文件列表描述格式多样单文件file:文件名.txt多文件file:1.mp4\nfile:2.mp4\nfolder:文件夹包含文件和文件夹的层级结构tags分类标签可能为null或字符串数组如[短剧, 电视剧, 国产剧]share_user分享用户昵称已脱敏处理shared_time分享时间格式为YYYY-MM-DD HH:MM:SS元数据字段doc_id系统内部文档 IDshare_user_id分享用户的系统 ID通常为空rel_movie关联的电影信息通常为空is_mine布尔值标识是否为当前用户的分享enabled布尔值标识资源是否启用weight整数资源权重status整数资源状态码四、字段映射从 API 数据到 PanSou 标准模型文档给出了完整的字段映射规则源字段目标字段说明disk_idUniqueID格式sousou-{disk_id}disk_nameTitle资源标题filesContent文件列表描述shared_timeDatetime解析为time.Time格式tagsTags标签数组需处理 null 情况linkdisk_passdisk_typeLinks转换为 Link 数组Channel插件搜索结果 Channel 为空字符串目标模型定义于 model/response.go其中SearchResult是 PanSou 所有数据源TG 频道与插件的统一结果载体type SearchResult struct { MessageID string json:message_id sonic:message_id UniqueID string json:unique_id sonic:unique_id // 全局唯一ID Channel string json:channel sonic:channel Datetime time.Time json:datetime sonic:datetime Title string json:title sonic:title Content string json:content sonic:content Links []Link json:links sonic:links Tags []string json:tags,omitempty sonic:tags,omitempty Images []string json:images,omitempty sonic:images,omitempty } type Link struct { Type string json:type sonic:type URL string json:url sonic:url Password string json:password sonic:password Datetime time.Time json:datetime,omitempty sonic:datetime,omitempty WorkTitle string json:work_title,omitempty sonic:work_title,omitempty }注意Channel字段插件搜索结果必须为空字符串以与 TG 频道来源Channel 为频道名区分这也是后续MergedLink.Sourcetg:频道名或plugin:插件名区分数据来源的基础。五、网盘类型映射API 标识到系统类型5.1 映射表API 标识 (disk_type)系统类型 (Link.Type)域名特征QUARKquarkpan.quark.cnBDYbaidupan.baidu.comALYaliyunalipan.com,aliyundrive.comXUNLEIxunleipan.xunlei.comUCucdrive.uc.cn115115115.com,115cdn.comTIANYItianyicloud.189.cnCAIYUNmobilecaiyun.139.com123PAN123123pan.com,123912.comPIKPAKpikpakmypikpak.com5.2 源码实现convertDiskType源码中的 convertDiskType 完整实现了这张映射表未命中类型统一返回othersfunc (p *SousouAsyncPlugin) convertDiskType(diskType string) string { switch diskType { case BDY: return baidu case ALY: return aliyun case QUARK: return quark case TIANYI: return tianyi case UC: return uc case CAIYUN: return mobile case 115: return 115 case XUNLEI: return xunlei case 123PAN: return 123 case PIKPAK: return pikpak default: return others } }5.3 域名特征与 GetLinkType 的对应关系映射表中的域名特征与 PanSou 工具层的链接类型判定逻辑一一对应。 util/regex_util.go 中的GetLinkType(url)通过域名关键字判断链接类型例如包含pan.quark.cn返回quark、包含pan.baidu.com返回baidu、包含alipan.com或aliyundrive.com返回aliyun并覆盖了 123 网盘的多个域名123684.com、123685.com、123865.com、123912.com、123pan.com、123pan.cn、123592.com。这意味着Sousou API 的disk_type字段用于主动标注类型而GetLinkType用于从分享 URL 被动推导类型两者互为校验保证最终入库的Link.Type与真实域名一致。六、数据特征与边界情况处理6.1 时间格式原始格式2025-10-27 21:38:59解析格式2006-01-02 15:04:05Gotime.Parse文档给出的解析函数func parseTime(timeStr string) time.Time { if timeStr { return time.Time{} // 零值 } // 格式2025-10-27 21:38:59 parsedTime, err : time.Parse(2006-01-02 15:04:05, timeStr) if err ! nil { return time.Time{} // 解析失败返回零值 } return parsedTime }源码 convertResults 中的内联解析逻辑与之一致仅当SharedTime非空且解析成功时才赋值失败或为空则保持time.Time{}零值。6.2 标签处理// 情况1: tags 为 null tags: null // 情况2: tags 为字符串数组 tags: [短剧, 电视剧, 国产剧]文档给出的processTags使用类型断言处理interface{}func processTags(tags interface{}) []string { if tags nil { return nil } // 类型断言为字符串数组 if tagArray, ok : tags.([]interface{}); ok { result : make([]string, 0, len(tagArray)) for _, tag : range tagArray { if tagStr, ok : tag.(string); ok { result append(result, tagStr) } } return result } return nil }源码版本 processTags 额外过滤了空字符串并且只有当结果非空时才返回数组其余情况统一返回nil进一步保证了Tags字段的干净性。6.3 文件列表格式单文件: file:美漫之黑手遮天-西风啸月.txt 多文件: .mp4 file:29.mp4 file:78.mp4 file:59.mp4 folder:14.弹指遮天 复杂结构: 58.mp4 file:90.mp4 file:40.mp4 folder:10.遮天武神该字段直接透传为SearchResult.Content作为结果摘要展示因此即便格式不统一也无碍——它本身承载的是文件列表描述语义。七、插件开发核心实现SearchResult 构建文档给出了完整的构建示例result : model.SearchResult{ UniqueID: fmt.Sprintf(sousou-%s, item.DiskID), Title: item.DiskName, Content: item.Files, Datetime: parseTime(item.SharedTime), Tags: processTags(item.Tags), Links: []model.Link{ { Type: convertDiskType(item.DiskType), URL: item.Link, Password: item.DiskPass, }, }, Channel: , // 插件搜索结果Channel为空 }源码 convertResults 在示例基础上增加了三道防御逻辑可作为开发模板参考跳过无链接结果item.Link 时直接continueUniqueID 后备方案disk_id为空时使用fmt.Sprintf(sousou-%d-%d, time.Now().Unix(), i)兜底时间解析失败容忍解析失败仅记录 debug 日志不影响整条结果的构建。link : model.Link{ URL: item.Link, Type: p.convertDiskType(item.DiskType), Password: item.DiskPass, } uniqueID : fmt.Sprintf(sousou-%s, item.DiskID) if item.DiskID { uniqueID fmt.Sprintf(sousou-%d-%d, time.Now().Unix(), i) } result : model.SearchResult{ UniqueID: uniqueID, Title: item.DiskName, Content: item.Files, Datetime: datetime, Tags: tags, Links: []model.Link{link}, Channel: , // 插件搜索结果必须为空字符串 }八、多页并发请求策略8.1 文档示例并发分页获取func (p *SousouAsyncPlugin) searchAPI(client *http.Client, keyword string) ([]SousouItem, error) { maxPages : 3 // 获取前3页 perSize : 30 // 每页30条 // 创建结果通道 resultChan : make(chan []SousouItem, maxPages) errChan : make(chan error, maxPages) var wg sync.WaitGroup // 并发请求每一页 for page : 1; page maxPages; page { wg.Add(1) go func(pageNum int) { defer wg.Done() url : fmt.Sprintf(https://sousou.pro/api.php?actionsearchq%spage%dper_size%dtype, url.QueryEscape(keyword), pageNum, perSize) items, err : p.fetchPage(client, url) if err ! nil { errChan - err return } resultChan - items }(page) } // 等待所有请求完成 go func() { wg.Wait() close(resultChan) close(errChan) }() // 收集结果 var allItems []SousouItem for items : range resultChan { allItems append(allItems, items...) } return allItems, nil }8.2 源码实现双层并发实际仓库中的并发模型是两层的外层doSearch对 6 种网盘类型并发发起搜索每种类型一个 goroutine见 doSearch内层searchByType对单个类型的 3 页DefaultMaxPages并发请求见 searchByType。即一个关键词最多会产生6 类型 × 3 页 18个并发 HTTP 请求。每个请求都使用context.WithTimeout(context.Background(), 30*time.Second)控制超时并设置浏览器风格请求头User-Agent、Accept: application/json, text/plain, */*、Referer: https://sousou.pro/以提升兼容性。8.3 去重处理deduplicateItemsdisk_id是去重依据但源码 deduplicateItems 做了更健壮的降级处理优先以DiskID为键为空则用Link两者皆空则用DiskName | DiskType冲突时保留信息更丰富的项采用计分制文件列表更长 1 分、新项有提取码 5 分、有时间 3 分、有标签 2 分。这一设计同时解决了多页重复与多类型结果交叉重复两个层面的问题。8.4 关键词过滤转换完成后doSearch还会调用plugin.FilterResultsByKeyword(results, keyword)实现见 plugin/plugin.go做最终过滤——要求关键词的每个分词都出现在标题或内容中保证返回结果与用户搜索意图强相关。九、与 hunhepan 插件的对比文档从开发视角对比了同为聚合搜索源的 sousou 与 hunhepan特性sousouhunhepan说明请求方式GETPOSTsousou 更简单API 数量1 个4 个sousou 单一 API链接格式标准标准都是简单的 URL时间字段有有格式相同标签处理可能为 null可能为 null需要类型检查去重依据disk_iddisk_id相同逻辑对比表中的事实可在仓库中得到印证 hunhepan.go 定义了 4 个 API 端点HunhepanAPI、QkpansoAPI、KuakeAPI、MisosoAPI且请求使用http.NewRequest(POST, apiURL, bytes.NewBuffer(jsonData))发送 POST 请求而 sousou 仅依赖SousouAPI一个端点、全程 GET。这种对比说明GET 单端点 查询参数拼 URL 的接口在插件接入上成本最低适合作为新数据源接入的优先选择。十、Web 搜索兜底路径源码补充json结构分析.md未提及的是仓库当前实现还内置了一条 Web 兜底路径。 doSearch 注释指出旧 API 现在会重定向到panso.vip并返回 403因此优先使用服务端渲染的搜索页https://www.panso.vip/search?q{关键词}页面中的文档链接更稳定// The old API now redirects to panso.vip and returns 403. Prefer the // server-rendered search pages, which expose stable document links. if results, err : p.searchWeb(client, keyword); err nil len(results) 0 { return plugin.FilterResultsByKeyword(results, keyword), nil }searchWeb通过 goquery 解析div.search-item列表再用并发度为 8 的信号量sem : make(chan struct{}, 8)逐个抓取详情页a.jump-link提取真实分享链接并用util.GetLinkType校验链接类型合法性others与空类型会被丢弃见 fetchPansoDocument。该路径的 UniqueID 由stableHash(docID)的 FNV-1a 哈希生成格式仍为sousou-{hash}。对应的单元测试覆盖了三条兜底路径的核心逻辑见 sousou_test.goTestCleanPansoTitleRemovesSEOSuffix标题清洗去掉- 网盘搜索-找网盘资源就上盘搜VIP等 SEO 后缀TestParsePansoPasswordFromMarkup从提取码a1b2文本中提取 4 位密码TestParsePansoDatetime解析2025-10-02 14:44:38格式时间。十一、注意事项与开发建议11.1 注意事项时间解析时间格式为YYYY-MM-DD HH:MM:SS需要正确的 Go 解析格式2006-01-02 15:04:05标签类型tags可能为null需要进行类型检查和处理链接验证确保link字段非空再创建 Link 对象网盘类型disk_type使用大写标识符需要转换为系统标准类型分页策略可以并发请求多页提高效率去重处理使用disk_id作为唯一标识进行去重空字段处理disk_pass、share_user_id、rel_movie等字段可能为空。11.2 开发建议优先级设置建议设置为等级 3普通质量数据源与源码NewBaseAsyncPlugin(sousou, 3)一致分页数量建议获取前 3 页数据平衡性能和数据量对应源码DefaultMaxPages 3每页大小建议设置为 30 条与其他插件保持一致对应源码DefaultPerSize 30超时控制使用 context 控制请求超时30 秒源码中context.WithTimeout(context.Background(), 30*time.Second)完全一致错误处理对每个可能失败的解析步骤都要有错误处理源码对 HTTP 状态码、JSON 解码、API code 都做了分支判断重试机制实现简单的重试机制提高稳定性当前仓库由异步插件基础设施的缓存合并与后台刷新机制间接承担了容错缓存策略文档建议缓存 TTL 为 2 小时。需要说明的是仓库 plugin/plugin.go 中BaseAsyncPlugin的默认 TTL 为1 * time.Hour实际取值由配置项AsyncCacheTTLHours决定接入时可根据数据新鲜度需求在配置层权衡。十二、API 响应示例12.1 成功响应{ code: 200, msg: 请求成功, data: { total: 200, per_size: 10, took: 62, list: [ { disk_id: bd8623b72cbb, disk_name: 美漫之黑手遮天-西风啸月.txt, disk_pass: , disk_type: QUARK, files: file:美漫之黑手遮天-西风啸月.txt, doc_id: cmhfmcz0l2emoae7mf7amvbsi, share_user: 安心*海豹, share_user_id: , shared_time: 2025-10-27 21:38:59, rel_movie: , is_mine: true, tags: null, link: https://pan.quark.cn/s/bd8623b72cbb, enabled: true, weight: 1, status: 0 } ] } }12.2 错误响应推测文档中标注该错误响应结构为推测接入时建议以实际接口返回为准但结构上符合code msg data的统一约定{ code: 400, msg: 请求失败: 参数错误, data: null }结语plugin/sousou/json结构分析.md是一份典型的数据源接入分析文档从请求参数到响应字段从类型映射到并发分页它完整回答了把一个 JSON API 接入 PanSou 插件体系所需的全部信息。仓库源码 sousou.go 与测试用例进一步验证了文档中的每一项结论并额外展示了去重计分、UniqueID 兜底、Web 兜底路径等生产级细节。对希望为 PanSou 新增网盘搜索数据源的开发者而言本文的字段映射表、convertDiskType与并发分页模板可以直接照搬到新插件中结合 插件开发指南 即可快速完成从接口分析到可运行插件的全流程。【免费下载链接】pansouPanSou是一款高性能的网盘资源搜索API服务支持TG频道和插件搜索。系统设计以性能和可扩展性为核心支持多频道多插件并发搜索、结果智能排序和网盘类型分类。docker集成前后端一键启动开箱即用。仅供学习研究请勿以各种形式用于盈利目的。 https://t.me/s/webhtv项目地址: https://gitcode.com/gh_mirrors/pan/pansou创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
