185 lines
7.4 KiB
Go
185 lines
7.4 KiB
Go
// sese-engine — Go rewrite
|
||
// Go 版 sese-engine:个人搜索引擎的主入口文件。
|
||
//
|
||
// 所有模块(爬虫、收获服务器、搜索服务器、反向链接计算)均作为 goroutine 在同一进程中启动。
|
||
// 主线程阻塞等待系统信号(Ctrl-C / SIGTERM),收到后优雅退出。
|
||
//
|
||
// 运行方式:
|
||
//
|
||
// cd golang && go run . [--storage ./savedata] [--entry https://zh.wikipedia.org/]
|
||
package main
|
||
|
||
import (
|
||
"flag" // 命令行参数解析
|
||
"fmt" // 格式化(搜索服务端口)
|
||
"log" // 日志输出
|
||
"os" // 操作系统信号
|
||
"os/signal" // 信号捕获
|
||
"path/filepath" // 路径处理
|
||
"syscall" // 系统调用(SIGTERM)
|
||
|
||
"sese-engine/analyzer" // 文本分析和关键词提取
|
||
"sese-engine/backlink" // 反向链接(繁荣值)计算
|
||
"sese-engine/config" // 全局配置
|
||
"sese-engine/crawler" // BFS 爬虫
|
||
"sese-engine/info" // info 服务(繁荣表、调整表、屏蔽词)
|
||
"sese-engine/search" // 搜索服务器(内嵌收获服务)
|
||
"sese-engine/storage" // 持久化存储
|
||
)
|
||
|
||
// initConfig 检查并初始化配置文件
|
||
// 如果 savedata/config.yml 不存在,则从模板生成
|
||
func initConfig() error {
|
||
configDir := "savedata"
|
||
configPath := filepath.Join(configDir, "config.yml")
|
||
|
||
// 检查配置文件是否已存在
|
||
if _, err := os.Stat(configPath); err == nil {
|
||
// 配置文件已存在,直接返回
|
||
return nil
|
||
}
|
||
|
||
// 确保 data 目录存在
|
||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||
return fmt.Errorf("failed to create config directory: %v", err)
|
||
}
|
||
|
||
// 从模板生成配置文件
|
||
defaultConfig := `# SESE Engine 配置文件
|
||
# 程序实际加载的配置文件
|
||
|
||
# 索引 / 存储相关限制
|
||
index:
|
||
max_urls_per_key: 11000 # 每个索引词最多保存的 URL 数量上限
|
||
max_same_domain_per_key: 20 # 同一域名在每个索引词下最多出现的次数
|
||
big_clean_threshold: 2000000 # 内存中累计多少条索引后触发一次刷盘清理
|
||
max_new_urls_per_key: 10000 # 每次刷盘时,每个索引词最多写入的新 URL 数量上限
|
||
min_urls_for_new_key: 3 # 新索引词如果 URL 数少于该值则丢弃,不写入磁盘
|
||
|
||
# 爬虫行为相关配置
|
||
crawler:
|
||
spider_name: "loli_spider" # HTTP 请求的 User-Agent 标识
|
||
cooldown: 3 # 同一主机相邻两次请求的最小间隔(秒),用于遵守 robots.txt 和避免被封
|
||
workers: 22 # 爬虫并发 goroutine 数量
|
||
crawl_focus: 0.7 # 域名集中度因子,越大越倾向在少量域名内深挖,越小越分散
|
||
max_keywords_per_page: 250 # 单个页面最多提取的关键词数量
|
||
max_epoch: 100 # BFS 爬取的最大轮次上限
|
||
expected_prosper_ratio: 0.6 # 队列中预期"繁荣"域名(高反向链接)的占比,用于调度决策
|
||
entry_url: "https://zh.wikipedia.org/" # BFS 爬取的起始入口 URL
|
||
|
||
# 搜索结果排序权重配置
|
||
search:
|
||
use_online_snippet: true # 是否在线抓取摘要(搜索时实时抓取页面补充摘要)
|
||
online_snippet_timeout: 3 # 在线抓取摘要的超时时间(秒)
|
||
weight_daily_decay: 0.996 # 页面年龄的时间衰减因子(每天乘以此系数)
|
||
language_weight: 0.5 # 语种匹配权重:与查询语种一致时加分
|
||
consecutive_key_weight: 1.3 # 连续关键词命中权重:多词连续出现时加分
|
||
backlink_weight: 1.0 # 反向链接权重:指向该 URL 的链接越多得分越高
|
||
server_port: 80 # 搜索服务和收获服务的统一 HTTP 监听端口
|
||
flush_interval_seconds: 300 # 定期刷盘间隔(秒):将内存索引批量写入磁盘
|
||
|
||
# 反向链接(PageRank 类)计算相关配置
|
||
backlink:
|
||
baseline: 200000 # 反向链接得分归一化的除数(用于将原始链接数映射到 [0,1] 区间)
|
||
|
||
# 存储根目录路径,相对于进程启动时的工作目录
|
||
storage:
|
||
path: "./savedata"
|
||
|
||
# 各模块 Prometheus 监控指标的 HTTP 端口
|
||
prometheus:
|
||
crawler_port: 14950 # 爬虫模块的 metrics 端口
|
||
backlink_port: 14952 # 反向链接计算模块的 metrics 端口
|
||
search_port: 14953 # 搜索服务(含收获功能)模块的 metrics 端口
|
||
`
|
||
|
||
if err := os.WriteFile(configPath, []byte(defaultConfig), 0644); err != nil {
|
||
return fmt.Errorf("failed to write config file: %v", err)
|
||
}
|
||
|
||
log.Printf("Generated default config file: %s", configPath)
|
||
return nil
|
||
}
|
||
|
||
// loadConfig 从 savedata/config.yml 加载配置
|
||
func loadConfig() error {
|
||
if err := config.LoadFromSavedata(); err != nil {
|
||
return fmt.Errorf("failed to load config: %v", err)
|
||
}
|
||
log.Printf("Config loaded successfully from savedata/config.yml")
|
||
return nil
|
||
}
|
||
|
||
func main() {
|
||
// ---- 0. 初始化配置文件 ----
|
||
if err := initConfig(); err != nil {
|
||
log.Fatalf("failed to init config: %v", err)
|
||
}
|
||
|
||
// ---- 0.5 加载配置文件 ----
|
||
if err := loadConfig(); err != nil {
|
||
log.Fatalf("failed to load config: %v", err)
|
||
}
|
||
|
||
// ---- 命令行参数 ----
|
||
// --storage:存储根目录路径,默认使用 config.StoragePath
|
||
storageDir := flag.String("storage", config.StoragePath, "path to savedata directory")
|
||
// --entry:BFS 爬取的起始 URL,默认使用 config.EntryURL()(维基百科中文首页)
|
||
entryURL := flag.String("entry", config.EntryURL(), "BFS crawl entry URL")
|
||
// --stopwords:屏蔽词 JSON 文件路径
|
||
stopWords := flag.String("stopwords", "/savedata/标点符号.json", "path to stop-words JSON")
|
||
flag.Parse()
|
||
|
||
// 设置日志格式:时间戳 + 短文件名
|
||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||
log.Printf("sese-engine starting storage=%s entry=%s", *storageDir, *entryURL)
|
||
|
||
// ---- 1. 存储层:打开 bbolt 数据库 ----
|
||
db, err := storage.Open(*storageDir)
|
||
if err != nil {
|
||
log.Fatalf("failed to open storage: %v", err)
|
||
}
|
||
defer db.Close()
|
||
db.StartWriteFlusher() // 启动异步写缓冲后台刷盘
|
||
|
||
// ---- 2. Info 服务:加载繁荣表、调整表和屏蔽词 ----
|
||
infoSvc := info.New(*storageDir)
|
||
|
||
// ---- 3. Analyzer:初始化分词器和语言检测器 ----
|
||
// modelPath 参数已废弃(lingua-go 使用内置模型,无需外部文件)
|
||
anal, err := analyzer.New("", *stopWords)
|
||
if err != nil {
|
||
log.Fatalf("failed to init analyzer: %v", err)
|
||
}
|
||
defer anal.Close()
|
||
|
||
// ---- 4. 搜索服务器(默认 :80):对外提供搜索 API,同时内嵌收获服务(统一端口)
|
||
searchSrv := search.New(db, infoSvc, anal)
|
||
go func() {
|
||
addr := fmt.Sprintf(":%d", config.SearchServerPort())
|
||
if err := searchSrv.ListenAndServe(addr); err != nil {
|
||
log.Fatalf("[search] fatal: %v", err)
|
||
}
|
||
}()
|
||
|
||
// ---- 6. 反向链接计算器:每 48 小时运行一次 ----
|
||
bl := backlink.New(db, *storageDir)
|
||
go bl.Run()
|
||
|
||
// ---- 7. 爬虫:从入口 URL 开始 BFS 爬取 ----
|
||
// 从 info 服务获取繁荣表快照,用于调度优先级决策
|
||
prosperMap := infoSvc.ProsperMap()
|
||
crawl := crawler.New(db, anal, prosperMap)
|
||
go crawl.Run(*entryURL, config.MaxEpoch())
|
||
|
||
log.Println("all modules started — press Ctrl-C to stop")
|
||
|
||
// ---- 优雅退出 ----
|
||
// 阻塞等待 SIGINT(Ctrl-C)或 SIGTERM 信号
|
||
quit := make(chan os.Signal, 1)
|
||
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
||
<-quit
|
||
log.Println("shutdown signal received, flushing index...")
|
||
searchSrv.Flush() // 退出前刷盘,不丢数据
|
||
}
|