DIYSearchEngine
DIYSearchEngine copied to clipboard
🔍 Go 开发的开源互联网搜索引擎,附教程《自己动手开发互联网搜索引擎》
Go å¼åç弿ºäºèç½æç´¢å¼æ
DIYSearchEngine æ¯ä¸ä¸ªè½å¤é«éééæµ·éäºèç½æ°æ®ç弿ºæç´¢å¼æï¼éç¨ Go è¯è¨å¼åã
æ³è¿è¡æ¬é¡¹ç®è¯·æå°é¡¹ç®åºé¨ï¼ææç¨ã
ã两ä¸åæä½ èªå·±å¨æå¼åäºèç½æç´¢å¼æã
åå¨åé¢
æ¬ææ¯ä¸ç¯æä½ åâåæ³æç´¢å¼æâçæç« ï¼ä¸åé½ç¬¦åãç½ç»å®å ¨æ³ãå robots ä¸çè§èçè¦æ±ï¼å¦æä½ è¢«å ¬å¸è¦æ±ç¬ä¸äºä¸äºåææªæ½çç½ç«ï¼æä¸ªäººå»ºè®®ä½ 马ä¸ç¦»èï¼æå·²ç»ç¥éäºå¥½å èµ·å ¨å ¬å¸æ°ç¾äººè¢«ä¸é 端çäºä»¶ã
ãç¬ä¹å·±ã
æç´¢å¼æåçæ ¼å±ï¼æ¯åå«å¤ä¸åçï¼åªéç¨ä½ä¸çªæ¥èï¼ä¾¿è½è·åä¸ç¯åä¸ç¯å «è¡æï¼ç¯ç¯é½æ¯ç¬è«ãç´¢å¼ãæåºä¸æ¿æ§ã坿¯è¿ä¸æ¿æ§å°åºè¯¥æä¹ç¨ä»£ç ååºæ¥ï¼å´è¢«ä½è ä»¬æ æä¿ææ²é»ï¼å¤§æµå¯è½ç¡®å®æ¯ææ¥çç½¢ã
仿年æ¹äºåï¼ä¾¿å¼å§å¨æ°æµªäºè®¡ç®åºæ ä»»ä¸åä¼è®¡ï¼èæ¿åè¯æï¼æé¿ç¸è¿äºå¤©çï¼æ æ³åºå¯¹é£äºé¾ç¼ çäºè®¡ç®å®¢æ·ãè¿äºå®¢æ·æ¶å»é½è¦æ±æä»¬çæå¡å¨çº¿ï¼æ¯å½åºç°æ éï¼ä¸å°åç§éçµè¯å°±ä¼çº·è³æ²æ¥ï¼æ¯æä»¬ççæ§ç³»ç»è¿è¦è¿ æ·ãæä»¥è¿äºå å¤©ï¼ææå说æå¹²ä¸äºè¿äºã幸äºäºååºé£è¾¹è¦äººï¼æ é¡»è¾éï¼ä¾¿æ¹ä¸ºä¸ç®¡äºååºè¿è¥çä¸ç§æ èèå¡äºã
æä»æ¤ä¾¿æ´å¤©çåå¨çµè¯åé¢ï¼ä¸ç®¡æçèå¡ãè½ç¶åªéè¦æ¨éªéæï¼æå¤±ä¸äºå°ä¸¥ï¼ä½æ»è§å¾æäºæ èãæææ¯ä¸å¯å¶è¸åï¼ä¸»é¡¾ä¹æ²¡æå¥½å£°æ°ï¼æäººæ´»æ³¼ä¸å¾ï¼åªæå¨åé¥åï¼ä¼äººä¸èµ·æ£æ¥æ¶é²è°èµ·æç´¢å¼æï¼æè½æåå°å 许欢ç¬ï¼å æ¤è³ä»ä»æ·±å»éè®°å¨å¿ã
ç±äºè°·æè¢«æç§°ä¸ºâå¥âï¼æ¬éå± æ°å°±ä¸ºå½å°çæç´¢å¼æåäºä¸ä¸ªç»°å·ï¼å«ä½åº¦å¨ã
度å¨ä¸åºç°ï¼ææäººé½ç¬äºèµ·æ¥ï¼æçå«å°ï¼â度å¨ï¼ä½ æ¨å¤©åå æ³å¾æ³è§è¯äºï¼âä»ä¸åçï¼å¯¹åå°è¯´ï¼â温两个çæï¼è¦ä¸ç¢æåºè±âï¼è¯´ç便æåºä¹æå¹¿åãæä»¬åæ æçé«å£°å·éï¼âä½ ä¸å®åéªäºäººå®¶çé±äºï¼â度å¨ç大ç¼ç说ï¼âä½ æä¹è¿æ ·åç©ºæ±¡äººæ¸ ç½â¦â¦ââä»ä¹æ¸ ç½ï¼æå天亲ç¼è§ä½ åäºèç°ç³»å¹¿åï¼ç¬¬ä¸å±å ¨æ¯ãâ度å¨ä¾¿æ¶¨çº¢äºè¸ï¼é¢ä¸çéçæ¡æ¡ç»½åºï¼äºè¾©éï¼â广åä¸è½ç®å·â¦â¦æµéï¼â¦â¦äºèç½å¹¿åçäºï¼è½ç®å·ä¹ï¼âæ¥è¿ä¾¿æ¯é¾æçè¯ï¼ä»ä¹âå 费使ç¨âï¼ä»ä¹âCPMâä¹ç±»ï¼å¼å¾ä¼äººé½åç¬èµ·æ¥ï¼åºå å¤å 满äºå¿«æ´»ç空æ°ã
æ¬æç®æ
䏿¿æ§æç« éå°é½æ¯ï¼ä½æ¯ççèªå·±å¼ååºæ¥æç´¢å¼æç人å´å°ä¹åå°ï¼å ¶å®ï¼å¼åä¸ä¸ªæç´¢å¼ææ²¡é£ä¹é¾ï¼æ°æ®é乿²¡æä½ æ³è±¡çé£ä¹å¤§ï¼åæç´¢å¼ä¹æ²¡æåé¢ä¸ççé£ä¹ç«é ·ï¼BM25 ç®æ³ä¹æ²¡æå®ç表达å¼çèµ·æ¥é£ä¹å¤¸å¼ ï¼åªç»å 个人ç¨çè¯ä¹æ²¡å¤å°è®¡ç®ååã
çªç ´èªå·±å¿çµçæ·éï¼åªé èªå·±å°±å¯ä»¥å¼åä¸ä¸ªç§æçäºèç½æç´¢å¼æï¼
æ¬ææ¯ä¸ç¯âè·æåâæç« ï¼åªè¦ä½ 䏿¥ä¸æ¥è·çæåï¼æåå°±å¯ä»¥å¾å°ä¸ä¸ªå¯ä»¥è¿è¡çäºèç½æç´¢å¼æãæ¬æçå端è¯è¨éç¨ Golangï¼å åæ°æ®åºéç¨ Redisï¼åå ¸åå¨éç¨ MySQLï¼ä¸ç¨è´¹å°½å¿æå°ç ç©¶è¿ç¨é´éä¿¡ï¼ä¹ä¸ç¨ç»å°½èæ±å°è§£å³å¤çº¿ç¨å线ç¨å®å ¨é®é¢ï¼ä¹ä¸ç¨èªå·±å¨ç£ç䏿æ B+ æ è´å¯æåï¼ç«çå°±æé±æ£äºã
ç®å½
æå¤§è±¡è£ è¿å°ç®±ï¼åªéè¦ä¸æ¥ï¼
- ç¼å髿§è½ç¬è«ï¼ä»äºèç½ä¸ç¬åç½é¡µ
- 使ç¨åæç´¢å¼ææ¯ï¼å°ç½é¡µæåæåå ¸
- ä½¿ç¨ BM25 ç®æ³ï¼è¿åæç´¢ç»æ
ç¬¬ä¸æ¥ï¼ç¼å髿§è½ç¬è«ï¼ä»äºèç½ä¸ç¬åç½é¡µ
Golang çåç¨ä½¿å¾å®ç¹å«éåæ¿æ¥å¼å髿§è½ç¬è«ï¼åªè¦å©ç¨å¤é¨ Redis å好âåç¨é´éä¿¡âï¼ä½ æå¤å° CPU æ ¸å¿ go é½å¯ä»¥åå®ï¼èä¸ä»£ç åèµ·æ¥è¿ç¹å«ç®åï¼è¿ç¨å线ç¨é½ä¸éè¦èªå·±ç®¡çãå½ç¶ï¼åç¨åè½å¼ºå¤§ï¼ä»£ç ç®ç¥ï¼è¿å°±å¯¼è´å®ç debug ææ¬å¾é«ï¼æå¨ååç¨ä»£ç çæ¶åæè§èªå·±åå¨ç¼ä¸¹ï¼ä¿®æ¹ä¸ä¸ªå符就å¯ä»¥è®©ç¨åºä»é¾éæåå°åä¸åï¼ç®ç´æ¯ææ§ ChatGPT è¿ç¥å¥ã
å¨ç¼åç¬è«ä¹åï¼æä»¬éè¦ç¥éä»äºèç½ä¸ç¬åå
容éè¦éµçºªå®æ³ï¼å¹¶éµå®robots.txtï¼å¦åï¼å¯è½å°±è¦è¿å»ååè¾ä»¬åç£ç¬è«ææ¯äºãrobots.txt çå
·ä½è§è大家å¯ä»¥èªè¡æç´¢ï¼ä¸é¢è·çæå¼æã
æ°å»º go é¡¹ç®æå°±ä¸æ¼ç¤ºäºï¼ä¸ä¼çå¯ä»¥é®ä¸ä¸ ChatGPT~
ç¬è«å·¥ä½æµç¨
æä»¬å 设计ä¸ä¸ªå¯ä»¥è½å°çç¬è«å·¥ä½æµç¨ã
1. 设计ä¸ä¸ª UA
é¦å
æä»¬è¦ç»èªå·±çç¬è«è®¾å®ä¸ä¸ª UAï¼å°½ééç¨è¾æ°ç PC æµè§å¨ç UA å 以æ¹é ï¼å å
¥æä»¬èªå·±ç spider åç§°ï¼æç项ç®å«âEnterprise Search Engineâ ç®ç§° ESEï¼æä»¥æè®¾å®ç UA æ¯ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4280.67 Safari/537.36 ESESpider/1.0ï¼ä½ 们å¯ä»¥èªå·±è®¾å®ã
éè¦æ³¨æçæ¯ï¼é¨åç½ç«ä¼å±è½éå¤´é¨æç´¢å¼æçç¬è«ï¼è¿ä¸ªéè¦ä½ 们转å¨èªæçå°èè¢çèªå·±è§£å³å¦ã
2. éæ©ä¸ä¸ªç¬è«å·¥å ·åº
æéæ©çæ¯ PuerkitoBio/goqueryï¼å®æ¯æèªå®ä¹ UA ç¬åï¼å¹¶å¯ä»¥å¯¹ç¬å°ç HTML 页é¢è¿è¡è§£æï¼è¿èå¾å°å¯¹æä»¬çæç´¢å¼æååéè¦ç页颿 é¢ãè¶ é¾æ¥çã
3. è®¾è®¡æ°æ®åº
ç¬è«çæ°æ®åºåæ¯ç¹å«ç®åï¼ä¸ä¸ªè¡¨å³å¯ãè¿ä¸ªè¡¨éé¢åç页é¢ç URL åç¬æ¥çæ é¢ä»¥åç½é¡µæåå 容ã
CREATE TABLE `pages` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`url` varchar(768) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'ç½é¡µé¾æ¥',
`host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'åå',
`dic_done` tinyint DEFAULT '0' COMMENT 'å·²æåè¿è¯å
¸',
`craw_done` tinyint NOT NULL DEFAULT '0' COMMENT 'å·²ç¬',
`craw_time` timestamp NOT NULL DEFAULT '2001-01-01 00:00:00' COMMENT 'ç¬åæ¶å»',
`origin_title` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'ä¸çº§é¡µé¢è¶
龿¥æå',
`referrer_id` int NOT NULL DEFAULT '0' COMMENT 'ä¸çº§é¡µé¢ID',
`scheme` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'http/https',
`domain1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'ä¸çº§åååç¼',
`domain2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'äºçº§åååç¼',
`path` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'URL è·¯å¾',
`query` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'URL æ¥è¯¢åæ°',
`title` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '页颿 é¢',
`text` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '页颿å',
`created_at` timestamp NOT NULL DEFAULT '2001-01-01 08:00:00' COMMENT 'æå
¥æ¶é´',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
4. ç»ç·ç¬ï¼
ç¬è«æä¸ä¸ªæå¥½çç¹æ§ï¼èªæå¢æ®ãæ¯ä¸ä¸ªç½é¡µéï¼åºæ¬é½å¸¦æå ¶ä»ç½é¡µç龿¥ï¼è¿æ ·æä»¬å°±å¯ä»¥éçä¸ï¼ä¸çäºï¼äºçä¸ï¼ä¸çä¸ç©äºã
æ¤æ¶ï¼æä»¬åªéè¦æ¾ä¸ä¸ªå¯¼èªç½ç«ï¼æå¨æè¯¥ç½ç«ç龿¥æå ¥å°æ°æ®åºéï¼ç¬è«å°±å¯ä»¥å¼å§è¿ä½äºãåä½å¯ä»¥èªè¡æéå¯å£ç页é¢é¾æ¥æç¨ã
æä»¬æ£å¼è¿å ¥å®æé¶æ®µï¼ä»¥ä¸é½æ¯å¯ä»¥è¿è¡ç代ç çæ®µï¼ä»£ç é»è¾å¨æ³¨ééé¢è®²è§£ã
æéç¨joho/godotenvæ¥æä¾.envé
ç½®æä»¶è¯»åçè½åï¼ä½ éè¦æååå¤å¥½ä¸ä¸ª.envæä»¶ï¼å¹¶å¨éé¢å¡«å好å¯ä»¥ä½¿ç¨ç MySQL æ°æ®åºä¿¡æ¯ï¼å
·ä½å¯ä»¥åè项ç®ä¸ç.env.exampleæä»¶ã
func main() {
fmt.Println("My name id enterprise-search-engine!")
// å è½½ .env
initENV() // è¯¥å½æ°çå
·ä½å®ç°å¯ä»¥åè项ç®ä»£ç
// å¼å§ç¬
nextStep(time.Now())
// é»å¡ï¼ä¸è·ç¬è«æ¶ç¨äºé»å¡ä¸»çº¿ç¨
select {}
}
// 循ç¯ç¬
func nextStep(startTime time.Time) {
// åå§å gorm æ°æ®åº
dsn0 := os.Getenv("DB_USERNAME0") + ":" +
os.Getenv("DB_PASSWORD0") + "@(" +
os.Getenv("DB_HOST0") + ":" +
os.Getenv("DB_PORT0") + ")/" +
os.Getenv("DB_DATABASE0") + "?charset=utf8mb4&parseTime=True&loc=Local"
gormConfig := gorm.Config{}
db0, _ := gorm.Open(mysql.Open(dsn0), &gormConfig)
// 仿°æ®åºéååºæ¬è½®éè¦ç¬ç 100 æ¡ URL
var pagesArray []models.Page
db0.Table("pages").
Where("craw_done", 0).
Order("id").Limit(100).Find(&pagesArray)
tools.DD(pagesArray) // æå°ç»æ
// éäºç¯å¹
ï¼ä¸é¢ç¨æåæè¿°
1. 循ç¯å±å¼ pagesArray
2. é对æ¯ä¸ä¸ª pageï¼ä½¿ç¨ curl å·¥å
·ç±»è·åç½é¡µææ¬
3. è§£æç½é¡µææ¬ï¼æååºæ é¢å页é¢ä¸å«æçè¶
龿¥
4. å°æ é¢ãä¸çº§åååç¼ãURL è·¯å¾ãæå
¥æ¶é´çä¿¡æ¯è¡¥å
å®å
¨ï¼æ´æ°å°è¿ä¸è¡æ°æ®ä¸
5. å°é¡µé¢ä¸çè¶
龿¥æå
¥ pages è¡¨ï¼æä»¬çç½é¡µåºç¬¬ä¸æ¬¡æ©å
äºï¼
fmt.Println("è·å®ä¸è½®", time.Now().Unix()-startTime.Unix(), "ç§")
nextStep(time.Now()) // ç´§æ¥çè·ä¸ä¸æ¡
}

æå·²ç»äºå
å° hao123 ç龿¥æå
¥äº pages è¡¨ï¼æä»¥æè¿è¡go build -o ese *.go && ./eseå½ä»¤ä¹åï¼å¾å°äºå¦ä¸ä¿¡æ¯ï¼
My name id enterprise-search-engine!
å è½½.env : /root/enterprise-search-engine/.env
APP_ENV: local
[[{1 0 https://www.hao123.com 0 0 2001-01-01 00:00:00 +0800 CST 2001-01-01 08:00:00 +0800 CST 0001-01-01 00:00:00 +0000 UTC}]]

ä¸é¢ç代ç ä¸ï¼æä»¬ç¬¬ä¸æ¬¡ç¨å°äº~~éé¾~~éå½ï¼èªå·±è°ç¨èªå·±ã
5. åæ³åè§ï¼éµå® robots.txt è§è
æéæ©ç¨temoto/robotstxtè¿ä¸ªåºæ¥æ¢æ¥æä»¬çç¬è«æ¯å¦è¢«å
许ç¬åæä¸ª URLï¼ä½¿ç¨ä¸å¼ åç¬ç表æ¥å卿¯ä¸ªååç robots è§åï¼å¹¶å¨ Redis ä¸å»ºç«ç¼åï¼æ¯æ¬¡ç¬å URL ä¹åï¼å
è¿è¡ä¸æ¬¡å¹é
ï¼å¹é
æåååç¬ï¼ä¿è¯åæ³åè§ã
å¶é çæ£çç产级ç¬è«

æäºåé¢è¿ä¸ªç论ä¸å¯ä»¥è¿è¡çç®åç¬è«ï¼ä¸é¢æä»¬å°±è¦ç»è¿å¹é©¬è¡¥å 亿ç¹ç»èäºï¼ç产ç¯å¢ä¸ï¼ç¬è«æ§è½ä¼åæ¯æéè¦çå·¥ä½ã
仿ç§ç¨åº¦ä¸æ¥è¯´ï¼æç´¢å¼æçä¼å£å¹¶ä¸åå³äºæç´¢ç®æ³çä¼å£ï¼å ä¸ºç®æ³ä½ä¸ºä¸ç§âç¹å®é®é¢çç®ä¾¿ç®æ³âï¼ä¸å®¶åä¸å ¬å¸æ¯å«å®¶å¼ºçç¨åº¦å¾æéï¼æç´¢å¼æççæ£ä¼å£å¨äºåªå®¶è½å¤ä»¥æå¿«çé度索å¼å°äºèç½ä¸å±åºä¸ç©·çæ°é¡µé¢åå·²ç»æ´æ°è¿å å®¹çæ§é¡µé¢ï¼å¨äºåªå®¶è½å¤è¯å«åªä¸ªç½é¡µæ¯ä»·å¼æé«çç½é¡µã
è¯å«ç½é¡µä»·å¼æ¹é¢ï¼æå½¦å®èµ·å®¶çæç´¢ä¸å©ï¼ä»¥åè°·æå¤§åé¼é¼ç PageRank 齿¥æå¼æ²åå·¥ä¹å¦ã使¬æçéç¹ä¸å¨è¿ä¸ªé¢åï¼èå¨äºææ¯å®ç°ã让æä»¬åå°ç¬è«æ§è½ä¼åï¼ä¸ºä»ä¹æ§è½ä¼å妿¤éè¦å¢ï¼æä»¬æå»ºçæ¯äºèç½æç´¢å¼æï¼éè¦ç¬æµ·éçæ°æ®ï¼å æ¤æä»¬çç¬è«éè¦è¶³å¤é«æï¼ä¸æäºèç½æ 400 ä¸ä¸ªç½ç«ï¼3500 亿个ç½é¡µï¼åªæåªç¬ååä¹ä¸ï¼3.5 亿个ç½é¡µä¹ä¸æ¯å¼ç©ç¬çï¼å¦æåªæ¯å线ç¨é»å¡å°ç¬ï¼æ¶èçæ¶é´ææè¦ä»¥å¹´ä¸ºåä½äºã
ç¬è«æ§è½ä¼åï¼æä»¬é¦å éè¦è§åä¸ä¸ç¡¬ä»¶ã
ç¡¬ä»¶è¦æ±
é¦å
计ç®ç£ç空é´ï¼å设ä¸ä¸ªé¡µé¢ 20KBï¼å¨ä¸è¿è¡å缩çæ
åµä¸ï¼ä¸äº¿ä¸ªé¡µé¢å°±éè¦ 20 * 100000000 / 1024 / 1024 / 1024 = 1.86TB çç£ç空é´ï¼èæä»¬æç®ä½¿ç¨ MySQL æ¥åå¨é¡µé¢ææ¬ï¼éè¦ç空é´ä¼æ´å¤§ä¸ç¹ã
æçç¬è«è±è´¹äº 2 个æçæ¶é´ï¼ç¬å°äºå¤§çº¦ 1 亿个 URLï¼å ¶ä¸ 3600 ä¸ä¸ªç¬å°äºé¡µé¢ç HTML ææ¬åå¨äºæ°æ®åºéï¼å ±æ¶èäºè¶ è¿ 600GB çç£ç空é´ã
é¤äºç¡¬æ§çç£ç空é´ï¼å©ä¸çå°±æ¯å°½éå¤ç CPU æ ¸å¿æ°åå åäºï¼CPU æ¿æ¥å¹¶åç¬ç½é¡µï¼å åæ¿æ¥æ¯ææµ·éåç¨çæ¶èï¼å¤å ç¨ Redis 为ç¬è«æéãç¬è«é¶æ®µå¯¹å åçè¦æ±è¿ä¸å¤§ï¼ä½å¨åé¢ç¬¬äºæ¥æååå ¸çæ¶åï¼å¤§å å容éç Redis å°æä¸ºæéå©å¨ã
æä»¥ï¼æä»¬å¯¹ç¡¬ä»¶çéæ±æ¯è¿æ ·çï¼ä¸å°æ ¸å¿æ°å°½éå¤çç©çæºæ¿æ¥è·æä»¬ç ese äºè¿å¶ç¨åºï¼å¤å 髿§è½æ°æ®åºï¼ä¾å¦16æ ¸64GBå åï¼NVMEç£çï¼ï¼ä½ è½æå°å¤å°å°æ°æ®åºå°±åå¤å¤å°å°ï¼å°±ç®ä½ æå°äº 65536 å°æ°æ®åºï¼ä¹è½è·æ»¡ï¼çè®ºä¸æä»¬å¯ä»¥æ éååºå表ãè½è¿ä¹ææ¯å 为ç½é¡µæ°æ®å ·æç¦»æ£æ§ï¼ç¸äºä¹é´çå ³ç³»å¨ç¬è«ååå ¸é¶æ®µè¿ä¸åå¨ï¼å¨æ¥è¯¢é¶æ®µææ¯è¾éè¦ã
顺çè¿ä¸ªæè·¯ï¼æäººå¯è½å°±ä¼æ³ï¼æç¨ KV æ°æ®åºä¾å¦ MongoDB æ¥åæä¹æ ·å¢ï¼å½ç¶æ¯å¾å¥½çï¼ä½æ¯MongoDB ä¸éåå¹²çäºæ å®å¨æ¯å¤ªå¤å¦ï¼æä»¥ä½ ä¾ç¶éè¦ Redis å MySQL çæ¯æï¼å¦æä½ éè¦ç¬åæ´å¤§è§æ¨¡çç½é¡µï¼å¯ä»¥æ MongoDB ç¨èµ·æ¥ï¼å©ç¨è¿ä¸æ¥æ¨é«ç³»ç»å¤æåº¦çæ¹å¼è·å¾ä¸ä¸ªæ¾èçæ§è½æåã
ä¸é¢æä»¬å¼å§è¿è¡è½¯ä»¶ä¼åï¼æåªè®²è¿°å ³é®æ¥éª¤ï¼å使ä»ä¹ä¸æç½çå°æ¹å¯ä»¥åè项ç®ä»£ç ã
éç¨ HTTP 客æ·ç«¯ä»¥é²æ¢å åæ³é²
è¿ä¸ªç¹çèµ·æ¥å¾å°ï¼ä½å½ä½ ç¬é´å¹¶åæ°åä¸åç¨çæ¶åï¼æ¯ä¸ªåç¨ 1MB çå åæµªè´¹ç´¯ç§¯èµ·æ¥é½æ¯å·¨å¤§çï¼å¾å®¹æé æ OOMã
æä»¬å¨ tools æä»¶å¤¹ä¸å建curl.goå·¥å
·ç±»ï¼ä¸é¨ç¨æ¥åæ¾å
¨å± client å curl å·¥å
·å½æ°ï¼
package tools
import ... //çç¥ï¼å
·ä½å¯ä»¥åè项ç®ä»£ç
// å
¨å±éç¨ client 对象ï¼4 ç§è¶
æ¶ï¼ä¸è·é 301 302 跳转
var client = req.C().SetTimeout(time.Second * 4).SetRedirectPolicy(req.NoRedirectPolicy())
// è¿å document 对象åç¶æç
func Curl(page models.Page, ch chan int) (*goquery.Document, int) {
... //çç¥ï¼å
·ä½å¯ä»¥åè项ç®ä»£ç
}
åºç¡ç¥è¯å¨å¤ï¼goroutine åç¨
æé»è®¤ä½ å·²ç»äºè§£ go åç¨æ¯ä»ä¹äºï¼å®å°±æ¯ä¸ä¸ªçèµ·æ¥åéæ³çä¸è¥¿ãå¨è¿éææä¾ä¸ä¸ªçè§£åç¨çå°è¯çªï¼æ¯ä¸ªåç¨å¨è¿å ¥ç£çãç½ç»çâåªéè¦åå°çå¾ âçä»»å¡ä¹åï¼ä¼æå½å CPU æ ¸å¿(å¯ä»¥çè§£æä¸ä¸ªå¾çµæº)çæä»¤æé goto å°ä¸ä¸ä¸ªåç¨çèµ·å§ã
éè¦æ³¨æçæ¯ï¼åç¨æ¯ä¸ç§ç¹æ®çå¹¶åå½¢å¼ï¼ä½ å¨å¹¶å彿°å è°ç¨ç彿°å¿ 须齿¯æå¹¶åè°ç¨ï¼ç±»ä¼¼äºä¼ ç»çâ线ç¨å®å ¨âï¼å¦æä½ ä¸ä¸å°å¿åäºä¸å®å ¨ç代ç ï¼è½»åå¡é¡¿ï¼éå crashã
䏿¬¡ååºä¸æ¹éè¦ç¬ç URLï¼ä½¿ç¨åç¨å¹¶åç¬
åç¨ä»£ç 宿æ¥å¦ï¼
// tools.DD(pagesArray) // æå°ç»æ
// å建 channel æ°ç»
chs := make([]chan int, len(pagesArray))
// å±å¼ pagesArray æ°ç»
for k, v := range pagesArray {
// åå¨ channel æé
chs[k] = make(chan int)
// é¿ç¦è¾¾å大çï¼ï¼
go craw(v, chs[k], k)
}
// 注æï¼ä¸é¢ç代ç ä¸å¯çç¥ï¼å¦åä½ ä¸é¢ go åºæ¥çé£äºåç¨ä¼ç¬é´éåº
var results = make(map[int]int)
for _, ch := range chs {
// ç¥ä¹ä¸æï¼æ¶éæ¥èªåç¨çè¿åæ°æ®ï¼å¹¶ hold 主线ç¨ä¸ç¬é´éåº
r := <-ch
_, prs := results[r]
if prs {
results[r] += 1
} else {
results[r] = 1
}
}
// å½ä»£ç æ§è¡å°è¿éçæ¶åï¼è¯´æææçåç¨é½å·²ç»è¿åæ°æ®äº
fmt.Println("è·å®ä¸è½®", time.Now().Unix()-startTime.Unix(), "ç§")
craw彿°åç¨åï¼
// ççç¬ï¼å卿 é¢ï¼å
容ï¼ä»¥åå龿¥
func craw(status models.Page, ch chan int, index int) {
// è°ç¨ CURL å·¥å
·ç±»ç¬å°ç½é¡µ
doc, chVal := tools.Curl(status, ch)
// 对 doc çå¤çå¨è¿éçç¥
// æéè¦ç䏿¥ï¼å chennel åé int å¼ï¼è¯¥å¨ä½æ¯åç¨ç»æçæ å¿
ch <- chVal
return
}
åç¨ä¼ååå®äºï¼CPU è¢«åæ»¡äºï¼æ¥ä¸æ¥æ°æ®åºè¦æä¸ºç¶é¢äºã
MySQL æ§è½ä¼å
åå°è¿éï¼å¨åæ®éä¸å¡é»è¾çæ¶åé常快ç MySQL å·²ç»æ¯æ´ä¸ªç³»ç»ä¸ææ ¢çä¸ç¯äºï¼pages 表ä¸å¤©å°±è¦å¢å å ç¾ä¸è¡ï¼MySQL ä¼ä»¥èç¼å¯è§çéåº¦æ ¢ä¸æ¥ãæä»¬è¦å¯¹ MySQL åæ§è½ä¼åã
ä½ä»¥è§£å¿§ï¼å¯æç´¢å¼
é¦å ï¼æ¶çæå¤§çè¯å®æ¯å ç´¢å¼ï¼è¿å¥è¯éç¨äº 99% çåºæ¯ã
å¨ä½ ç£ç容éå¤ç¨çæ åµä¸ï¼å ç´¢å¼é常å¯ä»¥è·å¾æ°ç¾åå°æ°ä¸åçæ§è½æåãæä»¬å ç» url å 个索å¼ï¼å 为æä»¬æ¯ç¬å°ä¸ä¸ª URL é½è¦æ¥ä¸ä¸å®æ¯å¦å·²ç»å¨è¡¨éé¢åå¨äºï¼è¿ä¸ªå¨ä½çé¢çæ¯é常é«çï¼å¦ææä»¬æç»ç¬å°äºä¸äº¿ä¸ªé¡µé¢ï¼é£è¿ä¸ªå¯¹æ¯å¨ä½è³å°ä¼åç¾äº¿æ¬¡ã
é¨ååºæ¯ä¸å¾å¥½ç¨çååºå表
é常幸è¿ï¼ç¬è«åºæ¯åååºå表é常å¥åï¼åªè¦æä»¬è½æ ¹æ® URL å°æ°æ®ååå°åæ£å¼ï¼ä¸åç URL ä¹é´æ¯æ²¡æå¤å°å ³ç³»çãé£æä»¬è¯¥æä¹å°æ°æ®åæ£å¼å¢ï¼ä½¿ç¨æ£åå¼ï¼
æ¯ä¸ä¸ª URL å¨ MD5 ä¹åï¼é½ä¼å¾å°ä¸ä¸ªå½¢å¦698d51a19d8a121ce581499d7b701668ç 32 ä½é¿åº¦ç 16 è¿å¶æ°ãèè¿äºæ°å卿¦ç䏿¯åççï¼æä»¥çè®ºä¸æä»¬å¯ä»¥å°æ°äº¿ä¸ª URL åååå¸å¨å¤ä¸ªåºçå¤ä¸ªè¡¨éãä¸é¢é®é¢æ¥äºï¼è¯¥æä¹åå¢ï¼
åªæä¸å°æ°æ®åºï¼åºè¯¥å表åï¼
å¦æä½ çè¿æçãé«å¹¶åçå²å¦åçï¼å «ï¼-- å° InnoDB å¥çä¸ä¸ä¸æï¼B+ æ ä¸ Buffer Pool ãçè¯ï¼å°±ä¼æç½ï¼åªè¦ä½ è½æ¥åå表çé»è¾ä»£ä»·ï¼é£å¨ä»»ä½å¤§æ°æ®éåºæ¯ä¸åè¡¨é½æ¯æææ¾æ¶ççï¼å 为éç表容éçå¢å ï¼é£æ£µ 16KB 页åç»æç B+ æ çå¤æåº¦å¢å æ¯è¶ 线æ§çï¼ç¨çé¼çè¯è¯´å°±æ¯ï¼äºé¶å¯¼æ°æç»å¤§äº 0ãæ¤å¤ï¼ç¼åä¹ä¼å¤±æï¼ä½ ç MySQL è¿è¡é度ä¼éä½å°ä¸ä¸ªä»¤äººåæçæ°´å¹³ã
æä»¥ï¼å³ä¾¿ä½ åªæä¸å°æ°æ®åºï¼é£ä¹åºè¯¥å表ãå¦æä½ çç£çæ¯ NVMEï¼æè§å¾åæºæ¿åº MD5 çå䏤使°åï¼ååºæ¥ 16 x 16 = 256 ä¸ªè¡¨æ¯æ¯è¾ä¸éçã
å½ç¶ï¼å¦æä½ è½æå° 16 å°æ°æ®åºæå¡å¨ï¼é£æ¿åºç¬¬ä¸ä½ 16 è¿å¶æ°åéå®ç©çæå¡å¨ï¼åç¨äºä¸ä½æ°åç»æ¯å°æºå¨å 256 ä¸ªè¡¨ä¹æ¯æå¥½çã
æççå®ç¡¬ä»¶åå表é»è¾
ç±äºæå¸æ¯è¾èä¿~~è´«ç©·~~ï¼æºæ¿çæå¡å¨é½æ¯äºæçï¼å®å¨æ¯æ¿ä¸åºé«æ§è½ç NVME æå¡å¨ï¼äºæ¯ææ¾ IT åäºä¸¤å° ThinkBook 14 寸ç¬è®°æ¬è£ ä¸äº CentOS Stream 9ï¼
- æå åæ©å 尿大ï¼å½¢æäº 8GB æ¿è½½ + 32GB å åæ¡ä¸å ± 40GB çå¥è©é ç½®
- CPU æ¯ AMD Ryzen 5 5600Uï¼è½ç¶æ¯ä½åçç CPUï¼åªæå æ ¸åäºçº¿ç¨ï¼ä½æ¯ä¹æ¯ Intel çæ¸£æ¸£ CPU å¿«å¤äºï¼Intelï¼çèççæ¤å®äºï¼ä¸æ»´é½æ²¡æäºï¼
- ç£çå°±ç¨èªå¸¦ç 500GB NVMEï¼å®æµè¯»åé度è½è·å° 3GB/2GBï¼ååå¤ç¨
ç±äºåå°æºå¨åªæ 6 æ ¸ï¼æå°±åç»ä»ä»¬åäº 128 个表ï¼å¨æ¯æ¬¡è¦æ§è¡ SQL ä¹åï¼æä¼å ç¨ URL ä½ä¸ºåæ°è·åä¸ä¸å®å¯¹åºçæ°æ®åºæå¡å¨å表åã表åè·åé»è¾å¦ä¸ï¼
- è®¡ç®æ¤ URL ç MD5 æ£åå¼
- åå两ä½åå è¿å¶æ°å
- æ¼æ¥æç±»ä¼¼
pages_0fæ ·åç表å
tableName := table + "_" + tools.GetMD5Hash(url)[0:2]
ç¬è«æ°æ®æµåæ¶æä¼å
ä¸é¢æä»¬å·²ç»ä½¿ç¨åç¨æ CPU å ¨é¨å©ç¨èµ·æ¥äºï¼å使ç¨ååºåè¡¨ææ¯ææ°æ®åºç¡¬ä»¶å ¨é¨å©ç¨èµ·æ¥äºï¼ä½æ¯å¦æä½ è¿ä¸ªæ¶åç´æ¥ç¨ä¸é¢ç代ç å¼å§è·ï¼ä¼åç°éåº¦è¿æ¯ä¸å¤å¿«ï¼å 为æäºå·¥ä½ MySQL è¿æ¯ä¸æ é¿åã
æ¤æ¶ï¼æä»¬å°±éè¦å¯¹æ°æ®æµåæ¶æååºä¼åäºã
æåä»åºè¡¨åç¶æè¡¨
åå§ç pages 表æ 16 ä¸ªåæ®µï¼å¨æä»¬ç¬çè¿ç¨ä¸ï¼åªç¨å¾å°äºä¸ªï¼id url host craw_done craw_timeãèçè¿æä¸é¢ç InnoDB æç« çå°ä¼ä¼´è¿ç¥éï¼å¨é¡µé¢ HTML 被填å
è¿textåæ®µä¹åï¼pages 表ç 16KB 页åä¼åºç°é¢ç¹çè°æ´åæéçä¹±é£ï¼å¯¹ InnoDB çâå±é¨æ§âæ§è½æ¶¡è½®çæ½å±é常ä¸å©ï¼ä¼é æ buffer pool çé¢ç¹å¤±æã
æä»¥ï¼ä¸ºäºç¬çæ´å¿«ï¼ä¸º pages 表æé ä¸ä¸ªæ§è½æ´å¼ºçâå½±åâå°±ååéè¦ãäºæ¯ï¼æä¸ºpages_0f表æé äºåªå
å«ä¸é¢äºä¸ªå段çstatus_0få
å¼è¡¨ï¼æ°æ®ä» pages 表éé¢å¤å¶èæ¥ï¼æ¿æ
ä¸äºé¢ç¹è¯»åä»»å¡ï¼
- æ£æ¥ URL æ¯å¦å·²ç»å¨åºï¼å³å¦æä»¥åå«ç页é¢ä¸å·²ç»åºç°äºè¿ä¸ª URL äºï¼æ¬æ¬¡å°±ä¸éè¦åå ¥åºäº
- æ¾åºä¸ä¸æ¹éè¦ç¬ç页é¢ï¼å³
craw_done=0ç URL - craw_time æ¿æ æ¥å¿çä½ç¨ï¼ç¨äºç»è®¡è¿å»ä¸æ®µæ¶é´çç¬è«æç
é¤äºè¿äºé«é¢æä½ï¼åå¨é¡µé¢ HTML åæ é¢çä¿¡æ¯çä½é¢æä½æ¯å¯ä»¥ç´æ¥å
¥paqes_0fä»åºè¡¨çã
宿¶è¯»å URL æ¹ä¸ºåå°å®æ¶è¯»å
éçåè¡¨æ°æ®éç鿏æåï¼æ¯ä¸è½®å¼å§æ¶ä»æ°æ®åºé颿¹é读åºéè¦ç¬ç URL æäºä¸ä¸ªç¸å¯¹èæ¶çæä½ï¼å³ä¾¿æ¯å¼ 表åªéè¦ 500msï¼é£è½®è¯¢ 256 å¼ è¡¨æ»èæ¶ä¹è¾¾å°äº 128 ç§ä¹å¤ï¼è¿æ¯æ æ³æ¥åçï¼æä»¥è¿ä¸ªæµç¨ä¹éè¦å¼æ¥åãä½ é®ä¸ºä»ä¹ä¸å¼æ¥åæ¶è¯»å 256 å¼ è¡¨ï¼å 为 MySQL æå®è´µçå°±æ¯è¿æ¥æ°ï¼è¿æ ·ä¼è®©è¿æ¥æ°ç´æ¥çæï¼å¤§å®¶é½å«ç©äºï¼å ³äºè¿æ¥æ°æä»¬ä¸é¢è¿ä¼ææåã
æä»¬ææµç¨è°æ´ä¸ä¸ï¼æ¯ 20 ç§ä» status è¡¨ä¸æç½ä¸æ¹éè¦ç¬ç URL æ¾è¿ Redis ä¸ç§¯ç´¯èµ·æ¥ï¼ç¬çæ¶åç´æ¥ä» Redis ä¸è¯»ä¸æ¹ãè¿ä¹åæ¯ä¸ºäºææ¯ä¸ç§çæ¶é´é½å©ç¨èµ·æ¥ï¼å°½å填满åç¨ç¬è«çèå£ã
// å¨ main() 䏿³¨å宿¶ä»»å¡
c := cron.New(cron.WithSeconds())
// æ¯ 20 ç§æ§è¡ä¸æ¬¡ prepareStatusesBackground 彿°
c.AddFunc("*/20 * * * * *", prepareStatusesBackground)
go c.Start()
// prepareStatusesBackground 彿°ä¸ï¼ä½¿ç¨ LPush åæåºå表ç头鍿å
¥ URL
for _, v := range _statusArray {
taskBytes, _ := json.Marshal(v)
db.Rdb.LPush(db.Ctx, "need_craw_list", taskBytes)
}
// æ¯ä¸è½®é½ä½¿ç¨ RPop 仿åºå表çå°¾é¨è¯»åéè¦ç¬ç URL
var statusArr []models.Status
maxNumber := 1 // æ¾å¤§åæ°ï¼æ§å¶æ¯ä¸æ¹ç URL æ°é
for i := 0; i < 256*maxNumber; i++ {
jsonString := db.Rdb.RPop(db.Ctx, "need_craw_list").Val()
var _status models.Status
err := json.Unmarshal([]byte(jsonString), &_status)
if err != nil {
continue
}
statusArr = append(statusArr, _status)
}
ååéè¦çç¬è«åå管æ§
è¿å»åå¹´ï¼ä¸å½äºèç½æ¯æ¬¡ææç´¢å¼ææ°ç§å´èµ·ï¼æé½è¦è¢«æ°ç¬è« DDOS ä¸éï¼æ³æ³å°±æ°ãè¿å¸®å¤§åçèé¸ç¨åºåï¼ä»¥ä¸ºé便ä¸ä¸ªç½ç«é½è½æ¿åä½ 2000 QPSï¼å®é ä¸äºèç½ä¸ 99.9% ç½ç«çæé QPS å°ä¸äº 100ï¼è¶ è¿ 10 é½å¤åã对äºï¼å¦ææ YisouSpider ç人çå°æ¬æï¼è¯·å廿¨å¨ä¸ä¸ä½ 们çç¬è«ä¼åï¼è½ç¶ä½ 们çç¬è«ä¸ä¼æç»é«éç¬åï¼ä½æ¯ä½ 们卿¯åéç第ä¸ç§å¹¶å 10 个请æ±çæ¹æ³æ´åæ¯ DDOSï¼å¯¹ç³»ç»çå±å®³æ´å¤§...
æä»¬è¦åè°·æé£æ ·ï¼åä¸ä¸ªååååçææç¬è«ï¼è¿å°±éè¦æä»¬ææ¯ä¸ä¸ªååçç¬è«é¢çé½è®°å½ä¸æ¥ï¼å¹¶å®æ¶è¿è¡è°æ´ãæåºäº Redis åæ¯ä¸ª URL ç host åäºä¸ä¸ªè®¡æ°å¨ï¼å¨æ¯æ¬¡ççè¦ç¬æä¸ª URL ä¹åï¼è°ç¨ä¸æ¬¡æ£æµå½æ°ï¼çæ¯å¦å¯¹å个ååçç¬è«ååè¿å¤§ã
æ¤å¤ï¼ç±äºæä»¬ç craw 彿°æ¯åç¨è°ç¨çï¼æ¤æ¶ Redis å°±æ¾å¾æ´ä¸ºéè¦äºï¼å®è½æä¾å®è´µçâ线ç¨å®å
¨æ°æ®è¯»åâåè½ï¼å¦æä½ 乿¯sync.Mapçå害è
ï¼æç¸ä¿¡ä½ ä¸å®ææð
æè®¤ä¸ºï¼å线ç¨ç Redis æ¯ go åç¨æä½³çä¼ä¼´ï¼å°±å PHP å MySQL 飿 ·ã
å ·ä½ä»£ç æå°±ä¸æ¾äºï¼æéè¦çåå¦å¯ä»¥èªå·±å»ç项ç®ä»£ç å¦ã
ç¯çä½¿ç¨ Redis å éé¢ç¹éå¤çæ°æ®åºè°ç¨
æä»¬ä½¿ç¨åç¨é«éç¬å°æ°æ®äºï¼ä¸ä¸æ¥å°±æ¯åå¨è¿äºæ°æ®ãè¿ä¸ªæä½çèµ·æ¥å¾ç®åï¼æ´æ°ä¸ä¸åæ¥é£ä¸è¡ï¼åæå ¥ N è¡æ°æ°æ®ä¸å°±è¡äºåï¼å ¶å®ä¸è¡ï¼è¿æä¸ä¸ªå ³é®æ¥éª¤éè¦ä½¿ç¨ Redis æ¥å éï¼æ°ç¬å°ç URL æ¯å¦å·²ç»å¨æ°æ®åºéåå¨äºãè¿ä¸ªæä½çèµ·æ¥ç®åï¼ä½å¨æä»¬è§£å³äºä¸é¢è¿äºæ§è½é®é¢ä»¥åï¼åºå¤§çæ°éå°±æäºè¿ä¸æ¥æå¤§çé®é¢ï¼æ¯ä¸æ¬¡æ¥è¯¢ä¼è¶æ¥è¶æ ¢ï¼æ¥è¯¢åæ°è¿ç¹å«å¤ï¼è¿è°é¡¶å¾ä½ã
妿æä»¬æ¿ Redis æ¥å URLï¼å²ä¸æ¯éè¦æææ URL é½åå
¥ Redis åï¼è¿å
åéæ±ä¹å¤ªå¤§äºãè¿ä¸ªæ¶åï¼æä»¬çèæåï¼å±é¨æ§ååºç°äºï¼ç±äºæä»¬çç¬è«æ¯æç
§é¡ºåºç¬çï¼é£âæåçæå乿¯æåâçæ¦çæ¯å¾å¤§çï¼æä»¥æä»¬åªè¦å¨ Redis éè®°å½ä¸ä¸ææ¡ URL æ¯å¦åå¨ï¼é£ä¹å䏿®µæ¶é´ï¼è¿ä¸ªä¿¡æ¯è¢«æ¥å°çæ¦çä¹å¾å¤§ï¼
// æä»¬ä½¿ç¨ä¸ä¸ª Hash æ¥åå¨ URL æ¯å¦åå¨çç¶æ
statusHashMapKey := "ese_spider_status_exist"
statusExist := db.Rdb.HExists(db.Ctx, statusHashMapKey, _url).Val()
// è¥ HashMap ä¸ä¸åå¨ï¼åæ¥è¯¢ææå
¥æ°æ®åº
if !statusExist {
··· 代ç çç¥ï¼ä¸åå¨åå建è¿è¡ pageï¼åå¨åæ´æ°ä¿¡æ¯ ···
// æ 论æ¯å¦æ°æå
¥äºæ°æ®ï¼é½å° _url å
¥ HashMap
db.Rdb.HSet(db.Ctx, statusHashMapKey, _url, 1).Err()
}
è¿æ®µä»£ç çä¼¼ç®åï¼å®æµé常好ç¨ï¼å¯ä¸çé®é¢å°±æ¯ä¸è½è¿è¡å¤ªé¿æ¶é´ï¼é䏿®µæ¶é´å¾æ¸ ç©ºä¸æ¬¡ï¼å 为éçæ¶é´çæµéï¼å±é¨æ§ä¼è¶æ¥è¶å·®ã
ç»å¿çå°ä¼ä¼´å¯è½å·²ç»åç°äºï¼æ¢ç¶ç¬åç¶æå·²ç»ç¨ Redis æ¥æ¿è½½äºï¼é£è¿éè¦åºå pages å status 表åï¼éè¦ï¼å 为 Redis ä¹ä¸æ¯å ¨è½çï¼å®çåºç¡æ°æ®ä¾ç¶æ¯æ¥èª MySQL çãç®åè¿ä¸ªæ¶æç±»ä¼¼äºå¤æçä¸çº§ç«ç®ï¼çèµ·æ¥æåæ²¡é£ä¹å¤§ï¼ä½è¿å°å°çæéå¯è½å°±è½è®©ä½ ç¬ä¸äº¿ä¸ªç½é¡µçæ¶é´ä» 3 个æç¼©åå° 1 个æï¼æ¯é常å¼çã
å¦å¤ï¼å¦æéè¿æ«æ 256 å¼ è¡¨ä¸ craw_time åæ®µçæ¹å¼æ¥ç»è®¡âè¿å» N åéç¬äºå¤å°ä¸ª URLãææé¡µé¢å¤å°ä¸ªãå 为ç¬è«ååèç¥è¿ç页é¢å¤å°ä¸ªãç½ç»é误çå¤å°ä¸ªã夿¬¡ç½ç»é误åä¸åéå¤ç¬åçå¤å°ä¸ªâçæ°æ®ï¼è¿æ¯å¤ªæ ¢äºï¼ä¹å¤ªæ¶èèµæºäºï¼è¿äºç»è®¡ä¿¡æ¯ä¹éè¦ä½¿ç¨ Redis æ¥è®°å½ï¼
// è¿å»ä¸åéç¬å°äºå¤å°ä¸ªé¡µé¢ç HTML
allStatusKey := "ese_spider_all_status_in_minute_" + strconv.Itoa(int(time.Now().Unix())/60)
// 计æ°å¨å 1
db.Rdb.IncrBy(db.Ctx, allStatusKey, 1).Err()
// ç»å½ 1 å°æ¶
db.Rdb.Expire(db.Ctx, allStatusKey, time.Hour).Err()
// è¿å»ä¸åé仿°ç¬å°ç HTML é颿ååºäºå¤å°ä¸ªæ°çå¾
ç¬ URL
newStatusKey := "ese_spider_new_status_in_minute_" + strconv.Itoa(int(time.Now().Unix())/60)
// 计æ°å¨å 1
db.Rdb.IncrBy(db.Ctx, newStatusKey, 1).Err()
// ç»å½ 1 å°æ¶
db.Rdb.Expire(db.Ctx, newStatusKey, time.Hour).Err()
ç产ç¬è«éå°çå ¶ä»é®é¢
卿们䏿æé«ç¬è«é度çè¿ç¨ä¸ï¼ç¬è«çå¤æåº¦ä¹å¨æç»ä¸åï¼æä»¬ä¼éå°ç©å ·ç¬è«éä¸å°çå¾å¤é®é¢ï¼æ¥ä¸æ¥æå享ä¸ä¸æçå¤çç»éªã
æå¶æ´å¢çæ°æ®åºè¿æ¥æ°
å¨åç¨è¿ä¸ªå¤§æå¨çåå©ä¹ä¸ï¼æä»¬å¯ä»¥è½»æååºè¶ é«å¹¶è¡ç代ç ï¼æ CPU å ¨é¨åå®ï¼ä½æ¯ï¼å¹¶è¡çåç¨å¤äºä»¥åï¼æ°æ®åºçè¿æ¥æ°ååä¹å¼å§æ´å¢ãMySQL é»è®¤çæå¤§è¿æ¥æ°åªæ 151ï¼æ ¹æ®æçå®é ä½éªï¼åªææ¯ä¸ä¸ªåç¨ä¸ä¸ªè¿æ¥ï¼æä»¬è¿ä¸ªç¬è«ä¹å¯ä»¥è½»ææè¿æ¥æ°å¹²å°æ°ä¸ï¼è¿ä¸ªæ°å太大äºï¼å³ä¾¿æ¯ææ°ç CPU å ä¸ DDR5 å åï¼åå¶äº MySQL ç®æ³çéå¶ï¼å¨è¿æ¥æ°è¾¾å°è¿ä¸ªçº§å«ä»¥åï¼å¤çæµ·éè¿æ¥æ°æéè¦çæ¶é´ä¹è¶æ¥è¶å¤ãè¿ä¸ªæ åµåãé«å¹¶åçå²å¦åçï¼äºï¼-- Apache çæ§è½ç¶é¢ä¸ Nginx çæ§è½ä¼å¿ãä¸æä¸æè¿°ç Apache ç prefork æ¨¡å¼æ¯è¾åãå¥½æ¶æ¯æ¯ï¼æè¿çæ¬ç MySQL 8 éå¯¹è¿æ¥æ°å¹é ç®æ³åäºä¼åï¼å¤§å¹ æåäºå¤§éè¿æ¥æ°ä¸çæ§è½ã
é¤äºåç¨ä¹å¤ï¼ååºåè¡¨å¯¹è¿æ¥æ°ççæ´å¢ä¹è´æä¸å¯æ¨å¸ç责任ãä¸ºäºæååæ¡ SQL çæ§è½ï¼æä»¬ç»åå°æ°æ®åºæå¡å¨åäº 256 å¼ è¡¨ï¼è¿ç§æ
åµä¸ï¼ä»¥åçä¸ä¸ªè¿æ¥+䏿¡ SQL çç¶æä¼çªç¶å¢å å° 256 ä¸ªè¿æ¥å 256 æ¡ SQLï¼å¦ææä»¬ä¸å 以éå¶çè¯ï¼å¯ä»¥è¯´åç¨+å表ä¸å¯å¨ï¼ä½ å°±ä¸å®ä¼æ¶å°æµ·éçToo many connectionsæ¥éãæçè§£å³æ¹æ³æ¯ï¼å¨ gorm åå§åçæ¶åï¼ç»ä»è®¾å®ä¸ä¸ªâåçº¿ç¨æå¤§è¿æ¥æ°âï¼
dbdb0, _ := _db0.DB()
dbdb0.SetMaxIdleConns(1)
dbdb0.SetMaxOpenConns(100)
dbdb0.SetConnMaxLifetime(time.Hour)
æ ¹æ®æçç»éªï¼100 个å¤ç¨äºï¼å大çè¯ï¼ä½ ç TCP 端å£å°±è¦ä¸å¤ç¨äºã
ååé»åå
æä»¬é½ç¥éï¼å 容ååºæ¯ä¸ç§ä¸é¨é»æç´¢å¼æç©ºåçåå¾å 容ç产è ï¼ç¬è«å¾é¾å¤æåªäºç½ç«æ¯å 容ååºï¼ä½æ¯äººä¸ç¹è¿å»å°±è½å¤æåºæ¥ãèè¿äºååçå é¨é¾æ¥åçåç¹å«å¥½ï¼è¿å°±å¯¼è´æä»¬éè¦æå¨ç»ä¸äºæ¶å¿çå 容ååºååå é»ååãæä»¬æç¬å°çæ¯ä¸ªååä¸ç URL æ°éç»è®¡ä¸ä¸ï¼æä¸ä¸ªå¨æçæåï¼å°±è½å¾å®¹æåç°å¤´é¨çå 容ååºååäºã
夿ç失败å¤ççç¥
ç产代ç åæå¦ä»£ç æå¤§çåºå«å°±æ¯æå¨çé误å¤çï¼ââ John·Luiï¼ä½è èªå·±ï¼
å¦æä½ ççè¦æä¸ä¸ªæ¶µçæ°äº¿é¡µé¢çå¯ä»¥ç¨çæç´¢å¼æï¼ä½ ä¼ç¢°å°åç§åæ ·çå¥è©å¤±è´¥ï¼è¿äºå¤±è´¥é½éè¦æ¿åºç¹å«çå¤ççç¥ï¼ä¸é¢æå享ä¸ä¸æéå°è¿çé®é¢åæçå¤ççç¥ã
- å页é¢è¶ æ¶é常éè¦ï¼å¦æä½ æ³å°½å¯è½å°å¨ä¸æ®µæ¶é´å ç¬å°å°½éå¤ç页é¢çè¯ï¼ç¼©çä½ curl çè¶ æ¶æ¶é´é常éè¦ï¼ç»è¿æ¸ç´¢ï¼ææè¿ä¸ªæ¶é´è®¾å®å°äº 4 ç§ï¼æ¢è½ç¬å°ç»å¤§å¤æ°ç½é¡µï¼ä¹ä¸ä¼æµªè´¹æ¶é´å¨ä¸äºæ ¹æ¬å°±æ æ³ååºç URL ä¸ã
- å个 URL é误达å°ä¸å®æ°é以åï¼éè¦ç´æ¥æé»ï¼ä¸ç¶ä¸æ®µæ¶é´åï¼ä½ çç¬è«æ´å¤©å°±åªç¬é£äºè¢«æ æ°æ¬¡ç¬å失败ç URL ä¸å¦ï¼ä¸ä¸ªæ°é¡µé¢ä¹ç¬ä¸å°ãè¿ä¸ªæ¬¡æ°æè®¾å®çæ¯ 3 次ã
- 妿æä¸ª URL è¿åç HTML æ æ³è¢«è§£æï¼æææ¾å¼ï¼æ²¡å¿ è¦è±è´¹é¢å¤èµæºéæ°ç¬ã
- ç±äºæä»¬çæ°æ®æµå·²ç»æ¯ä¸çº§ç«ç®å½¢æï¼æä»¥å¨åç§å°æ¹å ä¸â卿éâå°±å¾å¿
è¦ï¼å 为å¾å¤æ¶åæä»¬éè¦æå¨è®©å
¶ä»çº§ç«ç®å卿ºæåè¿è¡ï¼æå¨æ£ä¿®æä¸çº§å卿ºãæä¸è¬æ¿ MySQL æ¥åè¿ä»¶äºï¼å建ä¸ä¸ªå为
kvstoresç表ï¼åªæ key value ä¸¤ä¸ªåæ®µï¼éè¦çæ¶åæä¼æå¨ä¿®æ¹ç¹å® key 对åºç value å¼ï¼è®©æä¸çº§å卿ºæåä¸ä¸ã - ç±äº curl çç»æå ·æä¸ç¡®å®æ§ï¼å¡å¿ éè¦ä¿è¯ä»»ä½æ åµä¸ï¼é½è¦ç» channel è¿åä¿¡å·éï¼ä¸ç¶ä½ çæ´ä¸ªåºç¨ä¼ç´æ¥å¡æ»ã
- ä¸ä¸ªé¡µé¢å ç»å¸¸ä¼æåä¸ä¸ªè¶ 龿¥éå¤åºç°ï¼å¨å åéä¿åå·²ç»è§è¿ç URL å¹¶è·³è¿éå¤å¼å¯ä»¥æ¾èè约æ¶é´ã
- æå»ºäºä¸ä¸ª MySQL 表æ¥å卿æå¨æå ¥çé»ååååï¼è¿ä¸ªé常好ç¨ï¼å¯ä»¥å¨ç¬è«æç»è¿è¡çæ¶åéæ¶âæ¢æâï¼åæ¢å¯¹é»ååååçæ°å¢ç¬åã
è³æ¤ï¼æä»¬çç¬è«ç»äºæå»ºå®æäºã
ç¬è«è¿è¡æ¶æå¾
ç°å¨æä»¬çç¬è«è¿è¡æ¶æå¾åºè¯¥æ¯ä¸é¢è¿æ ·çï¼

ç¬è«æå®äºï¼è®©æä»¬è¿å ¥ç¬¬äºå¤§é¨åã
ç¬¬äºæ¥ï¼ä½¿ç¨åæç´¢å¼çæåå ¸
é£ä¸ª~~ç·äºº~~ä¸å¬å°±å¾çé¼çè¯åºç°äºï¼åæç´¢å¼ã
å¯¹äºæ²¡æè¿åæç´¢å¼ç人æ¥è¯´ï¼è¿ä¸ªè¯å¬èµ·æ¥åâçæååâ䏿 ·çé¼ï¼å ¶å®å®é常ç®åï¼ç®åç¨åº¦å ªæ¯ HTTP åè®®ã
åæç´¢å¼å°åºæ¯ä»ä¹
ä¸é¢è¿ä¸ªä¾åå¯ä»¥è§£éåæç´¢å¼æ¯ä¸ªä»ä¹ä¸è¥¿ï¼
- æä»¬æä¸ä¸ªè¡¨ titlesï¼å«æä¸¤ä¸ªå段ï¼ID å textï¼å设è¿ä¸ªè¡¨æ 100 è¡æ°æ®ï¼å ¶ä¸ç¬¬ä¸è¡ text 为âç¬è«å·¥ä½æµç¨âï¼ç¬¬äºè¡ä¸ºâå¶é çæ£çç产级ç¬è«â
- æä»¬å¯¹è¿ä¸¤è¡ææ¬è¿è¡åè¯ï¼ç¬¬ä¸è¡å¯ä»¥å¾å°âç¬è«âãâå·¥ä½âãâæµç¨âä¸ä¸ªè¯ï¼ç¬¬äºè¡å¯ä»¥å¾å°âå¶é âãâçæ£çâãâç产级âãâç¬è«âå个è¯
- æä»¬æé¡ºåºé¢ åè¿æ¥ï¼ä»¥è¯ä¸º keyï¼ä»¥â
titles.idâ¡,â¢è¿ä¸ªè¯å¨ text ä¸çä½ç½®è¿ä¸ä¸ªå ç´ æ¼æ¥å¨ä¸èµ·ä¸ºä¸ä¸ªå¼ï¼ä¸å text çæçå¼ä¹é´ä»¥ - ä½ä¸ºé´éï¼å¯¹æ°æ®è¿è¡âååç´¢å¼âï¼å¯ä»¥å¾å°ï¼- ç¬è«: 1,0-2,8
- å·¥ä½ï¼1,2
- æµç¨ï¼1,4
- å¶é ï¼2,0
- çæ£çï¼2,2
- ç产级ï¼2,5
åæç´¢å¼å®æäºï¼å°±æ¯è¿ä¹ç®åã说ç½äºï¼å°±æ¯æææå 容é½åè¯åºæ¥ï¼åååç»æ¯ä¸ªè¯æ è®°åºâä»åºç°å¨åªä¸ªææ¬çåªä¸ªä½ç½®âï¼æ²¡äºï¼å°±æ¯è¿ä¹ç®åãä¸é¢æ¯æçæçåå ¸ä¸ï¼âè¾°çºâè¿ä¸ªè¯çåå ¸å¼ï¼
110,85,1,195653,7101-66,111,1,195653,7101-
ä½ é®ä¸ºä»ä¹æä¸æ¾ä¸ªå¸¸è§çè¯ï¼å 为é便ä¸ä¸ªå¸¸è§çè¯ï¼å®çåå ¸é¿åº¦é½æ¯ä»¥ MB 为åä½çï¼æ ¹æ¬æ²¡æ³æ¾åºæ¥...
è¿æä¸ä¸ªçé¼çè¯ï¼æå°å®ç¾åå¸ï¼å¯ä»¥ç¨æ¥æå¸åå ¸æ°æ®ï¼å å¿«æç´¢éåº¦ï¼æå ´è¶£çåå¦å¯ä»¥èªè¡å¦ä¹
çæåæç´¢å¼æ°æ®
çè§£äºåæç´¢å¼æ¯ä»ä¹ä»¥åï¼æä»¬å°±å¯ä»¥çæææä»¬ç¬å°ç HTML å¤çæåæç´¢å¼äºã
æä½¿ç¨yanyiwu/gojiebaè¿ä¸ªåºæ¥è°ç¨ç»å·´åè¯ï¼æç
§ä»¥ä¸æ¥éª¤å¯¹æç¬å°çæ¯ä¸ä¸ª HTML ææ¬è¿è¡åè¯å¹¶å½ç±»ï¼
- åè¯ï¼ç¶å循ç¯å¤çè¿äºè¯ï¼
- ç»è®¡è¯é¢ï¼è¿ä¸ªè¯å¨è¯¥ HTML ä¸åºç°ç次æ°
- è®°å½ä¸è¿ä¸ªè¯å¨è¯¥ HTML 䏿¯ä¸æ¬¡åºç°çä½ç½®ï¼ä» 0 å¼å§ç®
- 计ç®è¯¥ HTML çæ»é¿åº¦ï¼æç´¢ç®æ³éè¦
- æç §ä¸å®æ ¼å¼ï¼ç»è£ æåæç´¢å¼å¼ï¼å½¢å¼å¦ä¸ï¼
// å表ç顺åºï¼ä¾å¦ 0f 转为åè¿å¶ä¸º 15
strconv.Itoa(i) + "," +
// pages.id 该 URL çä¸»é® ID
strconv.Itoa(int(pages.ID)) + "," +
// è¯é¢ï¼è¿ä¸ªè¯å¨è¯¥ HTML ä¸åºç°ç次æ°
strconv.Itoa(v.count) + "," +
// 该 HTML çæ»é¿åº¦ï¼BM25 ç®æ³éè¦
strconv.Itoa(textLength) + "," +
// è¿ä¸ªè¯åºç°çæ¯ä¸ä¸ªä½ç½®ï¼ç¨éå·éå¼ï¼å¯è½æå¤ä¸ª
strings.Join(v.positions, ",") +
// ä¸å page ä¹é´çé´é符
"-"
æä»¬æç §è¿ä¸ªè§åï¼æææç HTML è¿è¡åæç´¢å¼ï¼å¹¶ä¸æçæçç´¢å¼å¼æ¼æ¥å¨ä¸èµ·ï¼åå ¥ MySQL å³å¯ã
使ç¨åç¨ + Redis å¤§å¹ æåè¯å ¸çæé度
ä¸ç¥é大家æåå°äºæ²¡æï¼è¯å ¸ççææ¯ä¸ä¸ªæ¯ç¬è«é«å 个æ°é级ç CPU æ¶è大æ·ï¼ä¸ä¸ª HTML å¨è¾å å个è¯ï¼å¦æä½ è¦å¯¹æ°äº¿ä¸ª HTML è¿è¡åæç´¢å¼ï¼éè¦ç计ç®éæ¯é常æäººçãæç¬å°äº 3600 ä¸ä¸ªé¡µé¢ï¼ä½æ¯åªå¤çäºä¸å° 800 ä¸ä¸ªé¡µé¢çåæç´¢å¼ï¼å 为æç计ç®èµæºä¹æé...
å¹¶ä¸ï¼æè¯å ¸çå 容åå° MySQL éé¾åº¦ä¹å¾å¤§ï¼å 为ä¸äºå¸¸è§è¯çåæç´¢å¼ä¼å·¨é¿ï¼ä¾å¦â没æâè¿ä¸ªè¯ï¼ççæ¯å°å¤é½æå®ãé£è¯¥æä¹åæ§è½ä¼åå¢ï¼è¿æ¯æä»¬çèæåï¼åç¨å Redisã
åç¨åè¯
两个 HTML çåè¯å·¥ä½ä¹é´å®å ¨æ²¡æäº¤éï¼é常éåæ¿åç¨æ¥è·ã
使¯ï¼MySQL 举æäºï¼æé¡¶ä¸ä½ãæä»¥åç¨ç好æå Redis 乿¥äºã
ä½¿ç¨ Redis å为è¯å ¸æ°æ®çä¸è½¬ç«
æä»¬å¨ Redis ä¸é对æ¯ä¸ä¸ªè¯çæä¸ä¸ª Listï¼æåæåºæ¥çç´¢å¼æå ¥å°å°¾é¨ï¼
db.Rdb10.RPush(db.Ctx, word, appendSrting)
使ç¨åç¨ä» Redis æ¬è¿æ°æ®å° MySQL ä¸
ä½ æ²¡çéï¼è¿ä¸ªå°æ¹ä¹éè¦ä½¿ç¨åç¨ï¼å ä¸ºæ°æ®éå®å¨æ¯å¤ªå¤§äºï¼ä¸ä¸ªçº¿ç¨å¾ªç¯è·ä¼éå¸¸æ ¢ãç»è¿æç䏿å°è¯ï¼æåç°æ¯æ¬¡è½¬ç§» 2000 个è¯ï¼å¯¹ Redis çè´è½½æ¯è¾è½å¤æ¥åï¼E5-V4 ç CPU åæ ¸è½å¤è·æ»¡ï¼å¸¦å®½å¤§æ¦ 400Mbpsã
ä» Redis å° MySQL ç髿§è½æ¬è¿æ¹æ³å¦ä¸ï¼
- éæºè·åä¸ä¸ª key
- å¤æè¯¥ key çé¿åº¦ï¼åªæå¤§äºçäº 2 çè¿å ¥ä¸ä¸æ¥
- ææåä¸ä¸ªç´¢å¼å¼çä¸ï¼åé¢çå
ç´ ä¸ä¸ªä¸ä¸ª
LPopï¼å¼¹åºå¤´é¨ï¼åºæ¥ï¼æ¼æ¥å¨ä¸èµ· - æ±é䏿¹ 2000 ä¸ªéæºè¯çç»æï¼append å°æ°æ®åºè¯¥è¯ç°æç´¢å¼å¼çåé¢
æäºåç¨å Redis çåå©ï¼åè¯å åæç´¢å¼çé度快äºèµ·æ¥ï¼ä½æ¯å¦æä½ éæ©ä¸ä¸ªè¯ä¸ä¸ªè¯å° append å¼ï¼ä½ ä¼åç° MySQL åååååçè¶ æ ¢ï¼åè¦ä¼å MySQL äºï¼ð
äºå¡çå¦ç¨ï¼MySQL é«éæ¹éæå ¥
ç±äºéè¦å¾ç£çéåä¸è¥¿ï¼æä»¥åªè¦æ¯ä¸ä¸ªä¸ä¸ª updateï¼æä¹ä¼åé½ä¼å¾æ ¢ï¼é£ææ²¡æä¸æ¬¡æ§ update å¤è¡æ°æ®çæ¹æ³å¢ï¼æï¼é£å°±æ¯äºå¡ï¼
tx.Exec(`START TRANSACTION`)
// éè¦æ¹éæ§è¡ç update è¯å¥
for w, s := range needUpdate {
tx.Exec(`UPDATE word_dics SET positions = concat(ifnull(positions,''), ?) where name = ?`, s, w)
}
tx.Exec(`COMMIT`)
è¿ä¹æä½ï¼åå ¸åå ¥é度ä¸ä¸åèµ·æ¥äºã使¯ï¼æ¯æ¬¡æ§è¡ 2000 æ¡ update è¯å¥å¯¹ç£ççè¦æ±é常é«ï¼æè¿è¡è¿ä¸ªæä½çæ¶åï¼å¯ä»¥æç£çåå ¥é度ç¬é´æåå° 1.5GB/Sï¼å¦æä½ çæ°æ®åºåå¨ä¸å¤å¿«ï¼å¯ä»¥åå°è¯å¥æ°éã
ä¸ççåå·®ï¼æ æä¹çè¯
è¿ä¸ªä¸çä¸çä¸è¥¿å¹¶ä¸é½æ¯æç¨çï¼ä¸ä¸ª HTML ä¸çåç¬¦ä¹æ¯å¦æ¤ã
é¦å ï¼ä¸è¬ä¸å»ºè®®ç´¢å¼æ´ä¸ª HTMLï¼èæ¯æä»ç¨ DOM åºå¤çä¸ä¸ï¼æååºææ¬å 容ï¼åè¿è¡ç´¢å¼ã
å
¶æ¬¡ï¼å³ä¾¿æ¯ä½ å·²ç»è¿æ»¤æäºææç html æ ç¾ãcssãjs 代ç çï¼è¿æ¯æäºè¯é¢ç¹å°åºç°ï¼å®ä»¬åºç°çé¢ç妿¤çé«ï¼ä»¥è³äºåè失å»äºä½ä¸ºæç´¢è¯çä»·å¼ãè¿ä¸ªæ¶åï¼æä»¬å°±éè¦æä»ä»¬ç ç å°æé»ï¼ä¸å¤çä»ä»¬çåæç´¢å¼ãæä½¿ç¨çé»ååè¯å¦ä¸ï¼è¡¨å为word_black_listï¼åªæä¸¤ä¸ªå段 idãwordï¼éè¦çèªåï¼
INSERT INTO `word_black_list` (`id`, `word`)
VALUES
(1, 'px'),
(2, '20'),
(3, '('),
(4, ')'),
(5, ','),
(6, '.'),
(7, '-'),
(8, '/'),
(9, ':'),
(10, 'var'),
(11, 'ç'),
(12, 'com'),
(13, ';'),
(14, '['),
(15, ']'),
(16, '{'),
(17, '}'),
(18, '\''),
(19, '\"'),
(20, '_'),
(21, '?'),
(22, 'function'),
(23, 'document'),
(24, '|'),
(25, '='),
(26, 'html'),
(27, 'å
容'),
(28, '0'),
(29, '1'),
(30, '3'),
(31, 'https'),
(32, 'http'),
(33, '2'),
(34, '!'),
(35, 'window'),
(36, 'if'),
(37, 'â'),
(38, 'â'),
(39, 'ã'),
(40, 'src'),
(41, 'ä¸'),
(42, 'äº'),
(43, '6'),
(44, '。'),
(45, '<'),
(46, '>'),
(47, 'èç³»'),
(48, 'å·'),
(49, 'getElementsByTagName'),
(50, '5'),
(51, '、'),
(52, 'script'),
(53, 'js');
è³æ¤ï¼åå ¸çå¤çå䏿®µè½ï¼ä¸é¢è®©æä»¬ä¸èµ· Just æ it!
ç¬¬ä¸æ¥ï¼ä½¿ç¨ BM25 ç®æ³ç»åºæç´¢ç»æ
ç½ä¸å ³äº BM25 ç®æ³çæç« æ¯ä¸æ¯çèµ·æ¥é½æç¹æµï¼å«æ å¿ï¼çå®ä¸é¢è¿æ®µæåï¼æä¿è¯ä½ è½èªå·±ååºæ¥è¿ä¸ªç®æ³çå ·ä½å®ç°ï¼è¿ç§æå ·ä½ææ¡£ç工使¯æå¥½åçäºï¼æ¯åé¢çæ§è½ä¼åç®åå¤äºã
ç®åä»ç»ä¸ä¸ BM25 ç®æ³
BM25 ç®æ³æ¯ç°ä»£æç´¢å¼æçåºç¡ï¼å®å¯ä»¥å¾å¥½å°åæ ä¸ä¸ªè¯åä¸å ææ¬çç¸å ³æ§ã宿¥æä¸å°ç¬ç¹çè®¾è®¡ææ³ï¼æä»¬ä¸é¢ä¼è¯¦ç»è§£éã
è¿ä¸ªç®æ³ç¬¬ä¸æ¬¡è¢«ç产系ç»ä½¿ç¨æ¯å¨ 1980 年代ç伦æ¦åå¸å¤§å¦ï¼å¨ä¸ä¸ªå为 Okapi çä¿¡æ¯æ£ç´¢ç³»ç»ä¸è¢«å®ç°åºæ¥ï¼èååç®æ³æ¥èª 1970 年代 Stephen E. RobertsonãKaren Spärck Jones åä»ä»¬çåä¼´å¼åçæ¦çæ£ç´¢æ¡æ¶ãæä»¥è¿ä¸ªç®æ³ä¹å« Okapi BM25ï¼è¿éç BM ä»£è¡¨çæ¯best matchingï¼æä½³å¹é
ï¼ï¼é常å®å¨ï¼åæ¯äºè¿ªçâç¾æ¢¦æçâæç䏿¼ï¼Build Your Dreamsï¼ð
详ç»è®²è§£ BM25 ç®æ³æ°å¦è¡¨è¾¾å¼çå«ä¹

æç®åæè¿°ä¸ä¸è¿ä¸ªç®æ³çå«ä¹ã
é¦å ï¼å设æä»¬æ 100 个页é¢ï¼å¹¶ä¸å·²ç»å¯¹ä»ä»¬åè¯ï¼å¹¶å ¨é¨çæäºåæç´¢å¼ãæ¤æ¶ï¼æä»¬éè¦æç´¢è¿å¥è¯âBM25 ç®æ³çæ°å¦æè¿°âï¼æä»¬å°±éè¦æç §ä»¥ä¸æ¥éª¤æ¥è®¡ç®ï¼
- 对âBM25 ç®æ³çæ°å¦æè¿°âè¿è¡åè¯ï¼å¾å°âBM25âãâç®æ³âãâçâãâæ°å¦âãâæè¿°âäºä¸ªè¯
- æ¿åºè¿äºä¸ªè¯çå ¨é¨åå ¸ä¿¡æ¯ï¼å设å å«è¿äºä¸ªè¯ç页é¢ä¸å ±æ 50 个
- é个计ç®è¿äºä¸ªè¯åè¿ 50 个页é¢ç
ç¸å ³æ§æéåç¸å ³æ§å¾åçä¹ç§¯ï¼å½ç¶ï¼ä¸æ¯æ¯ä¸ªè¯é½åºç°å¨äºè¿ 50 个ç½é¡µä¸ï¼æå¤å°ç®å¤å°ï¼ - æè¿ 50 页é¢çåæ°å嫿±åï¼åååºæåï¼å³å¯ä»¥è·å¾âBM25 ç®æ³çæ°å¦æè¿°âè¿å¥è¯å¨è¿ 100 个页é¢ä¸çæç´¢ç»æ
ç¸å
³æ§æéåç¸å
³æ§å¾åååç¸ä¼¼ï¼å«ææ··äºï¼å®ä»¬çå
·ä½å®ä¹å¦ä¸ï¼
æä¸ªè¯åå å«å®çæä¸ªé¡µé¢çâç¸å ³æ§æéâ

ä¸å¾ä¸çWiæä»£çå°±æ¯ç¸å
³æ§æéï¼æå¸¸ç¨çæ¯TF-IDFç®æ³ä¸çIDFæéè®¡ç®æ³ï¼

è¿éç N æçæ¯é¡µé¢æ»æ°ï¼å°±æ¯ä½ å·²ç»å å
¥åå
¸ç页颿°éï¼éè¦å¨ææ«æ MySQL åå
¸ï¼å¯¹ææ¥è¯´å°±æ¯ 784 ä¸ãèn(Qi)å°±æ¯è¿ä¸ªè¯çåå
¸é¿åº¦ï¼å°±æ¯å«æè¿ä¸ªè¯ç页颿å¤å°ä¸ªï¼å°±æ¯æä»¬åå
¸å¼ä¸-åºç°ç次æ°ã
è¿ä¸ªåæ°çç°å®æä¹æ¯ï¼å¦æä¸ä¸ªè¯å¨å¾å¤é¡µé¢éé¢é½åºç°äºï¼é£è¯´æè¿ä¸ªè¯ä¸éè¦ï¼ä¾å¦ç¾åç¾ç©ºææ¥ç½åçâçâåï¼åªä¸ªé¡µé¢é½æï¼è¯´æè¿ä¸ªè¯ä¸åç¡®ï¼è¿èå®å°±ä¸éè¦ã
è¯ä»¥ç¨ä¸ºè´µã
æç代ç å®ç°å¦ä¸ï¼
// 页颿»æ°
db.DbInstance0.Raw("select count(*) from pages_0f where dic_done = 1").Scan(&N)
N *= 256
// åå
¸çå¼ä¸`-`åºç°ç次æ°
NQi := len(partsArr)
// å¾åºç¸å
³æ§æé
IDF := math.Log10((float64(N-NQi) + 0.5) / (float64(NQi) + 0.5))
æä¸ªè¯åå å«å®çæä¸ªé¡µé¢çâç¸å ³æ§å¾åâ

è¿ä¸ªè¡¨è¾¾å¼çèµ·æ¥æ¯ä¸æ¯å¾å¤æï¼ä½æ¯å®çå¤æåº¦æ¯ä¸ºäºå¤çæ¥è¯¢è¯å¥é颿ä¸ä¸ªå ³é®è¯åºç°äºå¤æ¬¡çæ åµï¼ä¾å¦âå «ç¾æ å µå¥åå¡ï¼ç®å µå¹¶æåè¾¹è·ãç®å µæææ å µç¢°ï¼æ å µæç¢°ç®å µç®ãâï¼âç®å µâè¿ä¸ªè¯åºç°äº 3 次ã为äºè½å¿«éå®ç°ä¸ä¸ªè½ç¨çæç´¢å¼æï¼æä»¬æ¾å¼æ¯æè¿ç§æ åµï¼ç¶åè¿ä¸ªçèµ·æ¥å°±åºæ¿ç表达å¼å°±å¯ä»¥ç®åæä¸é¢è¿ç§å½¢å¼ï¼

éè¦æ³¨æçæ¯ï¼è¿éé¢ç大åç K ä¾ç¶æ¯ä¸é¢é£ä¸ªç¥å¾®å¤æçæ ·å¼ãæä»¬å k1 为 2ï¼b 为 0.75ï¼é¡µé¢ï¼ææ¡£ï¼å¹³åé¿åº¦æèªå·±è·äºä¸ä¸ªï¼13214ï¼ä½ 们å¯ä»¥ç¨æè¿ä¸ªæ°ï¼ä¹å¯ä»¥èªå·±è·ä¸ä¸ªç¨ã
æç代ç å®ç°å¦ä¸ï¼
// ä½¿ç¨ - åååçå¼ï¼ä¸ºæ¤é¡µé¢çåå
¸å¼ï¼å½¢å¼ä¸ºï¼
// 110,85,1,195653,7101
ints := strings.Split(p, ",")
// è¿ä¸ªè¯å¨è¿ä¸ªé¡µé¢ä¸åºç°æ»æ¬¡æ°
Fi, err := strconv.Atoi(ints[2])
// è¿ä¸ªé¡µé¢çé¿åº¦
Dj, _ := strconv.Atoi(ints[3])
k1 := 2.0
b := 0.75
// 页é¢å¹³åé¿åº¦
avgDocLength := 13214.0
// å¾å°ç¸å
³æ§å¾å
RQiDj := (float64(Fi) * (k1 + 1)) / (float64(Fi) + k1*(1-b+b*(float64(Dj)/avgDocLength)))
æä¹æ ·ï¼æ¯ä¸æ¯æ¯ä½ æ³è±¡çç®åï¼
æ£éªæç´¢ç»æ
æå¨ææçâç¿°å¥æç´¢â页é¢ä¸æäºä¸ä¸âBM25 ç®æ³çæ°å¦æè¿°âï¼ç»æå¦ä¸ï¼

ææç´¢âä½èå¿âï¼ç»æå¦ä¸ï¼

第ä¸ä¸ªå°±æ¯æä»¬å®ç½ï¼å¯ä»¥è¯´ç¸å½ç²¾åäºã
çèµ·æ¥ææè¿ä¸éï¼è¦ç¥éè¿åªæ¯å¨ 784 ä¸çç½é¡µä¸æç´¢çç»æå¦ï¼å¦æä½ æè¶³å¤çæå¡å¨èµæºï¼è½æå®ä¸äº¿ä¸ªé¡µé¢çç¬åãç´¢å¼åæ¥è¯¢çè¯ï¼ææè¯å®æ´å ç好ã
å¦ä½ç»§ç»æåæç´¢åç¡®æ§ï¼
ç®åæä»¬çç®åç BM25 ç®æ³çæç´¢ç»æå·²ç»è¾¾å°è½ç¨çæ°´å¹³äºï¼è¿è½ç»§ç»æåæç´¢åç¡®æ§åï¼è¿å¯ä»¥ï¼
- æ¬æå ¨é¨æ¯åºäºåè¯åçåå ¸ï¼ä½ å¯ä»¥ååä¸ä»½åºäºååçï¼ç¶åæååçæç´¢ç»ææåºååè¯çæç´¢ç»æè¿è¡ç»åï¼æç´¢ç»æå¯ä»¥æ´åã
- ç¸ä¼¼çåçï¼æé æ´å åçãæ´å 丰å¯çåè¯æ¹å¼ï¼æé ä¸åå¾åçè¯å ¸ï¼å¯ä»¥æåç¹å®é¢åçæç´¢ç»æè¡¨ç°ï¼ä¾å¦å»å¦é¢åã代ç é¢åçã
- æé ä½ èªå·±ç PageRank ææ¯ï¼ä» URL ä¹é´å ³ç³»çè§åº¦ï¼ç»å个 URL çä»·å¼è¿è¡æåï¼å¹¶å°è¿ä¸ªä»·å¼åæ°æ¾è¿æç´¢ç»æçæåºåæ°ä¹ä¸ã
- å¼å ¥ proximity ç¸ä¼¼æ§è®¡ç®ï¼ä¸ä» èè精确å¹é çå ³é®è¯ï¼è¿è¦èèå°å«ä¹ç¸è¿çå ³é®è¯çæç´¢ç»æã
- ç¹æ®æ¥è¯¢çå¤çï¼ä¿®æ£ç¨æ·å¯è½çè¾å ¥é误ï¼å¤ç䏿ç¬ç¹çâæ¼é³å¹é âéæ±çã
åèèµæ
- ãNLPãéçç£ææ¬å¹é ç®æ³ââBM25 https://zhuanlan.zhihu.com/p/499906089
- ãèªå¶æç´¢å¼æãââ [æ¥]å±±ç°æµ©ä¹ãæ«æ°¸å¡
æç« ç»æäºï¼ä½ å¦åºäºåï¼æ¬¢è¿å°ä¸åä½ç½®çä¸ä½ çè¯è®ºï¼
- Githubï¼https://github.com/johnlui/DIY-Search-Engine
- å客ï¼https://pphc.lvwenhan.com/tech-epic/2023/diy-search-engine ãå ¨æå®ã
æ¬é¡¹ç®è¿è¡æ¹æ³
é¦å ï¼ç»èªå·±åå¤ä¸æ¯åå¡ã
- ææ¬é¡¹ç®ä¸è½½å°æ¬å°
- ç¼è¯ï¼
go build -o ese *.go - ä¿®æ¹é
ç½®æä»¶ï¼
cp .env.example .envï¼ç¶åæéé¢çæ°æ®åºå Redis é ç½®æ¹æä½ ç - æ§è¡
./ese art initåå»ºæ°æ®åº - æå¨æå ¥ä¸ä¸ªçå®ç URL å° pages_00 表ä¸ï¼åªéè¦å¡«å url å host ä¸¤ä¸ªåæ®µ
- æ§è¡
./eseï¼éå¾ å¥½äºåç âï¸
è¿ä¸æ®µæ¶é´ï¼çåå
¸æ°æ®è¡¨word_dicséé¢å¡«å
äºæ°æ®ä¹åï¼æå¼http://127.0.0.1:10086ï¼å°è¯æä¸ä¸å§ï¼ð
æ´å¤é¡¹ç®è¿è¡ä¿¡æ¯ï¼è¯·è§ wiki
ç½é¡µç´æ¥é 读ï¼https://pphc.lvwenhan.com/tech-epic/2023/diy-search-engine
ä½è ä¿¡æ¯ï¼
- å§åï¼åæç¿°
- GitHubï¼johnlui
- èä½ï¼ä½èå¿ CTO

æç« çæå£°æ
æ¬ææå½å±äºåæç¿°ï¼éç¨ CC BY-NC-ND 4.0 åè®®å¼æºï¼ä¾ GitHub å¹³å°ç¨æ·å è´¹é 读ã
代ç çæ
æ¬é¡¹ç®ä»£ç éç¨ MIT åè®®å¼æºã
