// Package recruitment 的抓取适配配置。 // // 本文件实现 crawl_source.config 字段(VARCHAR(1000) JSON)的解析与默认值兜底, // 让「抓取行为」可按数据源定制,无需改代码。设计目标: // - 所有字段可选,未配置则用默认值,保证历史数据零迁移即可继续工作; // - JSON 解析失败不报错,回退全默认值,避免一个源的脏配置拖垮整个调度。 package recruitment import ( "encoding/json" "github.com/gogf/gf/v2/frame/g" ) // SourceConfig 是单个数据源的抓取适配配置(对应 crawl_source.config)。 // 所有字段均可省略,零值即「用默认」。 type SourceConfig struct { // IncrDays 增量窗口天数:仅抓取该天数内发布的公告。默认 30。 // force=true 时忽略此项。 IncrDays int `json:"incrDays"` // MaxItems 单次抓取详情页上限,默认 60;force 模式下放宽到 300。 MaxItems int `json:"maxItems"` // MaxPages 列表分页最大翻页数,默认 1(不翻页)。预留字段。 MaxPages int `json:"maxPages"` // DelayMs 详情页之间的抓取间隔(毫秒),默认 0(不限速)。 // 政府站对高频访问敏感,建议按源配置 300~1000。 DelayMs int `json:"delayMs"` // IncludeWords 自定义标准词(命中则加分)。为空则用内置默认词表。 IncludeWords []string `json:"includeWords"` // ExcludeWords 自定义排除词(命中则大幅减分)。为空则用内置默认词表。 ExcludeWords []string `json:"excludeWords"` // MinScore 链接入选阈值,默认 2。调高更严格,调低更宽松。 MinScore int `json:"minScore"` // DatePatterns 发布日期自定义正则(需含一个捕获组或在匹配结果内含日期)。 // 为空则走内置的分段抽取策略。 DatePatterns []string `json:"datePatterns"` } // 默认值常量(未配置或配置缺失时使用)。 const ( defaultIncrDays = 30 // 增量窗口默认 30 天 defaultMaxItems = 60 // 常规抓取详情上限 forceMaxItems = 300 // force 全量回溯上限 defaultMinScore = 2 // 链接入选评分阈值 ) // parseSourceConfig 解析数据源的 config 字段。 // 任何解析错误都静默回退为默认值,保证抓取流程不因配置问题中断。 func parseSourceConfig(raw string) SourceConfig { var cfg SourceConfig raw = trimSpace(raw) if raw == "" || raw == "{}" { return cfg } if err := json.Unmarshal([]byte(raw), &cfg); err != nil { // 配置格式错误不应阻断抓取,记一条日志后按默认行为继续。 g.Log().Warningf(nil, "解析数据源 config 失败,按默认配置抓取: %v", err) return SourceConfig{} } return cfg } // incrDays 返回增量窗口天数(含默认值兜底)。 func (c SourceConfig) incrDays() int { if c.IncrDays > 0 { return c.IncrDays } return defaultIncrDays } // maxItems 返回单次抓取详情页上限。 func (c SourceConfig) maxItems(force bool) int { if force { return forceMaxItems } if c.MaxItems > 0 { return c.MaxItems } return defaultMaxItems } // minScore 返回链接入选阈值(含默认值兜底)。 func (c SourceConfig) minScore() int { if c.MinScore > 0 { return c.MinScore } return defaultMinScore } // includeWordsOrDefault 返回标准词表:配置优先,否则用内置默认。 func (c SourceConfig) includeWordsOrDefault() []string { if len(c.IncludeWords) > 0 { return c.IncludeWords } return defaultIncludeWords } // excludeWordsOrDefault 返回排除词表:配置优先,否则用内置默认。 func (c SourceConfig) excludeWordsOrDefault() []string { if len(c.ExcludeWords) > 0 { return c.ExcludeWords } return defaultExcludeWords } // trimSpace 轻量去空白(避免为此引入 strings 依赖以外的耦合)。 func trimSpace(s string) string { start, end := 0, len(s) for start < end && (s[start] == ' ' || s[start] == '\t' || s[start] == '\n' || s[start] == '\r') { start++ } for end > start && (s[end-1] == ' ' || s[end-1] == '\t' || s[end-1] == '\n' || s[end-1] == '\r') { end-- } return s[start:end] }