// Package info loads and serves auxiliary data: backlink scores, adjustment // table, and blocked query words. // info 包负责加载和管理辅助数据:繁荣表(反向链接分数)、调整表(人工权重调整)和屏蔽词表。 package info import ( "encoding/json" // JSON 反序列化 "math" // 对数运算(Log2) "os" // 文件读取 "path/filepath" // 路径拼接 "strings" // 字符串操作 "sync" // 读写锁 ) // Service 管理繁荣表、调整表和屏蔽词表,并提供只读快照。 type Service struct { mu sync.RWMutex prosperMap map[string]float64 // 繁荣表:域名 → 归一化反向链接分数 adjustTable map[string]float64 // 调整表:主机名 → 人工权重倍数(默认 1.0) blockedWords map[string]bool // 屏蔽词集合:搜索时直接过滤 storagePath string // 存储根目录路径 } // New 创建并加载 info Service,从 storagePath 目录读取数据文件。 func New(storagePath string) *Service { s := &Service{storagePath: storagePath} s.Reload() return s } // Reload 从磁盘重新加载所有数据文件(支持热更新配置)。 func (s *Service) Reload() { s.mu.Lock() defer s.mu.Unlock() s.prosperMap = loadProsperMap(s.storagePath) s.adjustTable = loadAdjustTable() s.blockedWords = loadBlockedWords() } // Prosper 返回指定 URL 的繁荣分数(对其所有路径段累计计算)。 // 分数越高表示该域名越"有价值"(反向链接越多)。 func (s *Service) Prosper(rawURL string) float64 { s.mu.RLock() defer s.mu.RUnlock() return prosperFor(rawURL, s.prosperMap) } // ProsperMap 返回繁荣表的完整只读快照(深拷贝)。 // 供爬虫调度算法使用。 func (s *Service) ProsperMap() map[string]float64 { s.mu.RLock() defer s.mu.RUnlock() out := make(map[string]float64, len(s.prosperMap)) for k, v := range s.prosperMap { out[k] = v } return out } // Adjust 返回指定主机名的人工权重倍数(默认 1.0)。 // 允许管理员通过调整表提升或降低某些域名的爬取/搜索优先级。 func (s *Service) Adjust(host string) float64 { s.mu.RLock() defer s.mu.RUnlock() if v, ok := s.adjustTable[host]; ok { return v } return 1.0 } // IsBlocked 判断某词是否在屏蔽词列表中(搜索时不返回含该词的结果)。 func (s *Service) IsBlocked(word string) bool { s.mu.RLock() defer s.mu.RUnlock() return s.blockedWords[word] } // ---- 数据加载函数 ---- // backlinkBaseline 繁荣表归一化的基准值(用于将原始链接数映射到固定区间)。 const backlinkBaseline = 200000.0 // loadProsperMap 从 storage/prosper.json 加载繁荣表,并进行归一化和域名树传播。 func loadProsperMap(storagePath string) map[string]float64 { path := filepath.Join(storagePath, "prosper.json") f, err := os.Open(path) if err != nil { return map[string]float64{} } defer f.Close() var raw map[string]float64 if err := json.NewDecoder(f).Decode(&raw); err != nil { return map[string]float64{} } return normalise(raw) } // normalise 对繁荣表进行归一化,并执行域名树传播。 // 归一化:将所有顶级域名的分数总和缩放到 backlinkBaseline。 // 传播:子域名分数向上传播到父域名(父域名分数不低于任何子域名)。 func normalise(d map[string]float64) map[string]float64 { // 计算顶级域名(不含 "/")的分数总和 total := 0.0 for k, v := range d { if !strings.Contains(k, "/") { total += v } } if total == 0 { return d } // 按总和归一化 factor := backlinkBaseline / total out := make(map[string]float64, len(d)) for k, v := range d { out[k] = v * factor } // 域名树传播:子域名分数 ≥ 父域名分数 for k, v := range out { now := k for { idx := strings.Index(now, ".") if idx < 0 { break } now = now[idx+1:] // 上移一级 if cur, ok := out[now]; ok && cur < v { out[now] = v // 父域名分数不低于子域名 } else if !ok { break } } } return out } // loadAdjustTable 从 data/adjust.json 加载人工调整表(主机名 → 权重倍数)。 // 文件不存在时返回空 map(所有域名权重为默认 1.0)。 func loadAdjustTable() map[string]float64 { f, err := os.Open(filepath.Join("data", "adjust.json")) if err != nil { return map[string]float64{} } defer f.Close() var m map[string]float64 json.NewDecoder(f).Decode(&m) return m } // loadBlockedWords 从 data/blocked_words.json 加载屏蔽词列表。 // 文件不存在时返回空集合。 func loadBlockedWords() map[string]bool { f, err := os.Open(filepath.Join("data", "blocked_words.json")) if err != nil { return map[string]bool{} } defer f.Close() var words []string json.NewDecoder(f).Decode(&words) m := make(map[string]bool, len(words)) for _, w := range words { m[w] = true } return m } // prosperFor 对 URL 按路径段分解查询繁荣表,计算综合繁荣分数。 // 分数计算:对每段取 Log2 变换后累加,返回值范围约 [0.1, +∞)。 func prosperFor(rawURL string, pm map[string]float64) float64 { segments := decomposeURL(rawURL) s := 0.0 for _, seg := range segments { t, ok := pm[seg] if !ok { t = 0 } l := 0.0 if t > 0 { l = math.Log2(2+t*2) - 1 // Log2(2+2t)-1,t=0 时为 0,随 t 增大而增大 } if s == 0 { if l == 0 { return 0 } s = l } else { s = l + math.Log((s-l)/2+1) // 累加并衰减 } } if s > 0 { return 0.1 + s } return 0 } // decomposeURL 将 URL 分解为递增的路径段。 // 例如:"https://zh.wikipedia.org/wiki/Go" → ["zh.wikipedia.org", "zh.wikipedia.org/wiki", "zh.wikipedia.org/wiki/Go"]。 // 用于按从泛到精的顺序查繁荣表。 func decomposeURL(rawURL string) []string { u := strings.ToLower(rawURL) if strings.HasPrefix(u, "https://") { u = u[8:] } else if strings.HasPrefix(u, "http://") { u = u[7:] } else { return nil } u = strings.ReplaceAll(u, "?", "/") // 查询参数转路径 u = strings.ReplaceAll(u, "#", "/") // 锚点转路径 u = strings.TrimRight(u, "/") // 过滤无效格式 if u == "" || u[0] == '/' || u[0] == '%' || u[0] == ' ' { return nil } parts := strings.Split(u, "/") var out []string current := parts[0] out = append(out, current) // 第一段:顶级域名 for _, p := range parts[1:] { current = current + "/" + p // 逐步拼接路径段 out = append(out, current) } return out }