├── .gitignore ├── LICENSE ├── README.md ├── apis ├── chapters.go └── novels.go ├── common └── utils.go ├── config └── config.go ├── main.go └── vendor └── vendor.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | tags 15 | 16 | # Dir 17 | vendor/* 18 | release/* 19 | !vendor/vendor.json 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 howie.hu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## owllook小说接口 2 | 3 | [![GitHub release](https://img.shields.io/github/release/howie6879/owllook_api.svg)](https://github.com/howie6879/owllook_api/releases) [![Github All Releases](https://img.shields.io/github/downloads/howie6879/owllook_api/total.svg)](https://github.com/howie6879/owllook_api/releases) [![license](https://img.shields.io/github/license/howie6879/owllook_api.svg)](https://github.com/howie6879/owllook_api/blob/master/LICENSE) 4 | 5 | 本项目提供优雅的小说接口,其他衍生项目如下: 6 | 7 | - 公众号:[**粮草小说**](http://oe7yjec8x.bkt.clouddn.com/howie/2018-03-13-%E7%B2%AE%E8%8D%89%E5%B0%8F%E8%AF%B4.jpg-blog.howie),有兴趣的话可以关注下 8 | - 官网:[https://www.owllook.net](https://www.owllook.net) 9 | - 监控工具:[owllook_gui](https://github.com/howie6879/owllook_gui) 10 | 11 | ### Overview 12 | 13 | [owllook](https://github.com/howie6879/owllook)是一个基于其他搜索引擎构建的垂直小说搜索引擎,owllook目的是让阅读更简单、优雅,让每位读者都有舒适的阅读体验,有朋友有兴趣开发owllook的app端,于是此项目便诞生了,本项目提供小说的一系列接口,如检索、目录、章节内容、检查更新 14 | 15 | #### Installation 16 | 17 | `owllook_api`基于[gin](https://github.com/gin-gonic/gin),提供了一系列基本的小说接口,使用: 18 | 19 | ``` shell 20 | git clone https://github.com/howie6879/owllook_api 21 | cd owllook_api 22 | 23 | go get -u github.com/kardianos/govendor 24 | govendor sync 25 | go run main.go 26 | 27 | # 编译 28 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./release/owllook_api.mac 29 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./release/owllook_api.linux 30 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./release/owllook_api.exe 31 | ``` 32 | 33 | #### API 34 | 35 | 小说资源说明: 36 | 37 | 本项目利用了互联网上的一些小说资源作为检索目标进行资源提取,定义如下,100以后的命名定位第三方检索: 38 | 39 | | 名称 | 编号 |搜索类型 | 40 | | :--- | -------- | ----------- | 41 | | 10 | 笔趣阁 | 站内 | 42 | | 100 | 新笔趣阁 | 百度第三方 | 43 | | 110 | 笔下文学 | 百度第三方 | 44 | | 120 | 顶点小说 | 百度第三方 | 45 | 46 | **搜索小说:** 47 | 48 | 格式:/v1/novels/:name/:source 49 | 50 | 请求: 51 | 52 | ``` shell 53 | # 请求资源为10下小说牧神记的检索结果 54 | curl http://0.0.0.0:8080/v1/novels/牧神记/10 55 | ``` 56 | 57 | 响应: 58 | 59 | ``` json 60 | { 61 | "info": [ 62 | { 63 | "novel_abstract": "大墟的祖训说,天黑,别出门。大墟残老村的老弱病残们从江边捡到了一个婴儿,取名秦牧,含辛茹苦将他养大。这一天夜幕降临,……", 64 | "novel_author": "作者:宅猪", 65 | "novel_cover": "https://www.bqg99.cc/bookimages/2640967.jpg", 66 | "novel_latest_chapter_name": "第七百九十二章 道一(月底求月票)", 67 | "novel_latest_chapter_url": "https://www.bqg99.cc/book/2639610/595030666.html", 68 | "novel_name": "牧神记", 69 | "novel_type": "分类:玄幻", 70 | "novel_url": "https://www.bqg99.cc/book/2639610/", 71 | "source_name": "笔趣阁01", 72 | "source_url": "https://www.bqg99.cc/" 73 | } 74 | ], 75 | "status": 1 76 | } 77 | ``` 78 | 79 | **搜索作者:** 80 | 81 | 格式:/v1/authors/:name/:source 82 | 83 | 请求: 84 | 85 | ``` shell 86 | curl http://0.0.0.0:8080/v1/authors/猫腻/10 87 | ``` 88 | 89 | 响应: 90 | 91 | ``` json 92 | { 93 | "info": [ 94 | { 95 | "novel_abstract": "千里杀一人,十步不愿行。", 96 | "novel_author": "作者:猫腻", 97 | "novel_cover": "https://www.bqg99.cc/bookimages/1849310.jpg", 98 | "novel_latest_chapter_name": "今天没有更新", 99 | "novel_latest_chapter_url": "https://www.bqg99.cc/book/1847953/594800464.html", 100 | "novel_name": "大道朝天", 101 | "novel_type": "分类:玄幻", 102 | "novel_url": "https://www.bqg99.cc/book/1847953/", 103 | "source_name": "笔趣阁01", 104 | "source_url": "https://www.bqg99.cc/" 105 | }, 106 | { 107 | "novel_abstract": "命里有时终须有,命里无时要强求。  这是一个长生果的故事。……", 108 | "novel_author": "作者:猫腻", 109 | "novel_cover": "https://www.bqg99.cc/bookimages/1008998084.jpg", 110 | "novel_latest_chapter_name": "后记", 111 | "novel_latest_chapter_url": "https://www.bqg99.cc/book/1008996727/644149409.html", 112 | "novel_name": "择天记", 113 | "novel_type": "分类:玄幻", 114 | "novel_url": "https://www.bqg99.cc/book/1008996727/", 115 | "source_name": "笔趣阁01", 116 | "source_url": "https://www.bqg99.cc/" 117 | }, 118 | { 119 | "novel_abstract": "与天斗,其乐无穷。  ……", 120 | "novel_author": "作者:猫腻", 121 | "novel_cover": "https://www.bqg99.cc/bookimages/1010262420.jpg", 122 | "novel_latest_chapter_name": "第六卷忽然之间 第一百三十章 结尾", 123 | "novel_latest_chapter_url": "https://www.bqg99.cc/book/1010261063/959174919.html", 124 | "novel_name": "将夜", 125 | "novel_type": "分类:玄幻", 126 | "novel_url": "https://www.bqg99.cc/book/1010261063/", 127 | "source_name": "笔趣阁01", 128 | "source_url": "https://www.bqg99.cc/" 129 | }, 130 | { 131 | "novel_abstract": "世界上有两件东西能够深深地震撼人们的心灵,一件是我们心中崇高的道德准则,另一件是我们头顶上灿烂的星空——康德  当许……", 132 | "novel_author": "作者:猫腻", 133 | "novel_cover": "https://www.bqg99.cc/bookimages/1011122532.jpg", 134 | "novel_latest_chapter_name": "最后的单章:间客关门八件事", 135 | "novel_latest_chapter_url": "https://www.bqg99.cc/book/1011121175/979602210.html", 136 | "novel_name": "间客", 137 | "novel_type": "分类:玄幻", 138 | "novel_url": "https://www.bqg99.cc/book/1011121175/", 139 | "source_name": "笔趣阁01", 140 | "source_url": "https://www.bqg99.cc/" 141 | }, 142 | { 143 | "novel_abstract": "积善之家,必有余庆,留余庆,留余庆,忽遇恩人;幸娘亲,幸娘亲,积得阴功。劝人生,济困扶穷……而谁可知,人生于世,……", 144 | "novel_author": "作者:猫腻", 145 | "novel_cover": "https://www.bqg99.cc/bookimages/1012231120.jpg", 146 | "novel_latest_chapter_name": "第七卷朝天子 末章 后来", 147 | "novel_latest_chapter_url": "https://www.bqg99.cc/book/1012229763/989671183.html", 148 | "novel_name": "庆余年", 149 | "novel_type": "分类:历史", 150 | "novel_url": "https://www.bqg99.cc/book/1012229763/", 151 | "source_name": "笔趣阁01", 152 | "source_url": "https://www.bqg99.cc/" 153 | } 154 | ], 155 | "status": 1 156 | } 157 | ``` 158 | 159 | -------------------------------------------------------------------------------- /apis/chapters.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // SearchChapters returns all chapters that you serached 10 | func SearchChapters(c *gin.Context) { 11 | c.JSON(http.StatusOK, gin.H{"title": "chapters demo"}) 12 | } 13 | -------------------------------------------------------------------------------- /apis/novels.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/howie6879/owllook_api/common" 9 | "github.com/howie6879/owllook_api/config" 10 | ) 11 | 12 | var ( 13 | NovelsRulesMap = config.NovelsRulesMap 14 | ) 15 | 16 | // SearchAuthors returns all novels resource that you serached 17 | func SearchAuthors(c *gin.Context) { 18 | var currentRule config.NovelRule 19 | var ok bool 20 | novelName := c.Param("name") 21 | novelSource := c.Param("source") 22 | if novelName != "" { 23 | currentRule, ok = NovelsRulesMap[novelSource+"_1"] 24 | log.Println(ok) 25 | if ok == false { 26 | currentRule, ok = NovelsRulesMap[novelSource] 27 | } 28 | log.Println(ok) 29 | if ok { 30 | resultData, err := common.FetchHtml(novelName, currentRule) 31 | if err != nil { 32 | log.Println("Request URL error", err) 33 | c.JSON(http.StatusOK, gin.H{"statue": 0, "msg": "Request error"}) 34 | } else { 35 | c.JSON(http.StatusOK, gin.H{"status": 1, "info": resultData}) 36 | } 37 | } else { 38 | c.JSON(http.StatusOK, gin.H{"statue": 0, "msg": "Parameter error"}) 39 | } 40 | } else { 41 | c.JSON(http.StatusOK, gin.H{"statue": 0, "msg": "Parameter name can't be empty"}) 42 | } 43 | } 44 | 45 | // SearchNovels returns all novels resource that you serached 46 | func SearchNovels(c *gin.Context) { 47 | novelName := c.Param("name") 48 | novelSource := c.Param("source") 49 | if novelName != "" { 50 | currentRule, ok := NovelsRulesMap[novelSource] 51 | if ok { 52 | resultData, err := common.FetchHtml(novelName, currentRule) 53 | if err != nil { 54 | log.Println("Request URL error", err) 55 | c.JSON(http.StatusOK, gin.H{"statue": 0, "msg": "Request error"}) 56 | } else { 57 | c.JSON(http.StatusOK, gin.H{"status": 1, "info": resultData}) 58 | } 59 | } else { 60 | c.JSON(http.StatusOK, gin.H{"statue": 0, "msg": "Parameter error"}) 61 | } 62 | } else { 63 | c.JSON(http.StatusOK, gin.H{"statue": 0, "msg": "Parameter name can't be empty"}) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /common/utils.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "log" 5 | "net/url" 6 | "strings" 7 | 8 | "github.com/PuerkitoBio/goquery" 9 | "github.com/axgle/mahonia" 10 | "github.com/howie6879/owllook_api/config" 11 | "github.com/levigross/grequests" 12 | "github.com/saintfish/chardet" 13 | ) 14 | 15 | // DetectBody gbk convert to utf-8 16 | func DetectBody(body []byte) string { 17 | var bodyString string 18 | detector := chardet.NewTextDetector() 19 | result, err := detector.DetectBest(body) 20 | if err != nil { 21 | return string(body) 22 | } 23 | if strings.Contains(strings.ToLower(result.Charset), "utf") { 24 | bodyString = string(body) 25 | } else { 26 | bodyString = mahonia.NewDecoder("gbk").ConvertString(string(body)) 27 | } 28 | return bodyString 29 | } 30 | 31 | // MakeAbsolute returns a absolute url 32 | func MakeAbsolute(homeUrl string, currentUrl string) string { 33 | urlParse, _ := url.Parse(currentUrl) 34 | urlHost := urlParse.Host 35 | homeUrlParse, _ := url.Parse(homeUrl) 36 | if urlHost == "" { 37 | absoluteUrl := homeUrlParse.ResolveReference(urlParse) 38 | return absoluteUrl.String() 39 | } 40 | return urlParse.String() 41 | } 42 | 43 | // FetchHtml returns a raw html 44 | func FetchHtml(name string, rule config.NovelRule) ([]map[string]string, error) { 45 | var resultData []map[string]string 46 | var searchUrl string 47 | if rule.KeywordEncoding == "" { 48 | searchUrl = rule.SearchUrl + url.QueryEscape(name) 49 | } else { 50 | keyword := mahonia.NewEncoder(rule.KeywordEncoding).ConvertString(name) 51 | quote_keyword := url.QueryEscape(keyword) 52 | searchUrl = rule.SearchUrl + quote_keyword 53 | } 54 | log.Println(searchUrl) 55 | response, err := RequestURL(searchUrl) 56 | if err != nil { 57 | log.Println("Request URL error", err) 58 | return resultData, err 59 | } 60 | if response.StatusCode == 200 { 61 | raw_html := DetectBody(response.Bytes()) 62 | doc, _ := goquery.NewDocumentFromReader(strings.NewReader(raw_html)) 63 | doc.Find(rule.TargetItem).Each(func(i int, s *goquery.Selection) { 64 | var absoluteNovelUrl string 65 | novelName := s.Find(rule.ItemRule.NovelName).Text() 66 | novelUrl, _ := s.Find(rule.ItemRule.NovelUrl).Attr("href") 67 | if novelUrl != "" { 68 | absoluteNovelUrl = MakeAbsolute(rule.HomeUrl, novelUrl) 69 | } else { 70 | absoluteNovelUrl = novelUrl 71 | } 72 | novelType := s.Find(rule.ItemRule.NovelType).Text() 73 | novelCover, _ := s.Find(rule.ItemRule.NovelCover).Attr("src") 74 | absoluteNovelCover := MakeAbsolute(rule.HomeUrl, novelCover) 75 | novelAuthor := s.Find(rule.ItemRule.NovelAuthor).Text() 76 | novelAbstract := s.Find(rule.ItemRule.NovelAbstract).Text() 77 | novelLatestChapterName := s.Find(rule.ItemRule.NovelLatestChapterUrl).Text() 78 | novelLatestChapterUrl, _ := s.Find(rule.ItemRule.NovelLatestChapterUrl).Attr("href") 79 | absoluteNovelLatestChapterUrl := MakeAbsolute(rule.HomeUrl, novelLatestChapterUrl) 80 | currentItem := make(map[string]string) 81 | currentItem["source_name"] = rule.Name 82 | currentItem["source_url"] = rule.HomeUrl 83 | currentItem["novel_name"] = strings.TrimSpace(novelName) 84 | currentItem["novel_url"] = absoluteNovelUrl 85 | currentItem["novel_type"] = strings.TrimSpace(novelType) 86 | currentItem["novel_cover"] = absoluteNovelCover 87 | currentItem["novel_author"] = strings.TrimSpace(novelAuthor) 88 | currentItem["novel_abstract"] = strings.TrimSpace(novelAbstract) 89 | currentItem["novel_latest_chapter_name"] = novelLatestChapterName 90 | currentItem["novel_latest_chapter_url"] = absoluteNovelLatestChapterUrl 91 | resultData = append(resultData, currentItem) 92 | }) 93 | } 94 | return resultData, nil 95 | } 96 | 97 | // RequestURL returns a search result 98 | func RequestURL(url string) (*grequests.Response, error) { 99 | ro := &grequests.RequestOptions{ 100 | Headers: map[string]string{"User-Agent": config.GetUserAgent()}, 101 | } 102 | resp, err := grequests.Get(url, ro) 103 | if err != nil { 104 | log.Println("Unable to make request: ", err) 105 | } 106 | 107 | // log.Println(resp.String()) 108 | return resp, err 109 | } 110 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | // ItemRuleConfig contains information about novel's rules 9 | type ItemRuleConfig struct { 10 | NovelName string 11 | NovelUrl string 12 | NovelType string 13 | NovelAuthor string 14 | NovelCover string 15 | NovelAbstract string 16 | NovelLatestChapterName string 17 | NovelLatestChapterUrl string 18 | } 19 | 20 | // NovelRule contains information about novle's source 21 | type NovelRule struct { 22 | Name string 23 | HomeUrl string 24 | SearchUrl string 25 | Method string 26 | Params map[string]string 27 | KeywordEncoding string 28 | TargetItem string 29 | ItemRule ItemRuleConfig 30 | } 31 | 32 | var ( 33 | UserAgents = []string{ 34 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36", 35 | "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; Media Center PC 6.0; InfoPath.3; MS-RTC LM 8; Zune 4.7)", 36 | "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 5.1; Trident/5.0)", 37 | "Mozilla/5.0 (X11; Linux x86_64; rv:2.2a1pre) Gecko/20100101 Firefox/4.2a1pre", 38 | "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110131 Firefox/4.0b11pre", 39 | "Mozilla/5.0 (X11; U; Linux i686; ru-RU; rv:1.9.2a1pre) Gecko/20090405 Ubuntu/9.04 (jaunty) Firefox/3.6a1pre", 40 | "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100723 SUSE/3.6.8-0.1.1 Firefox/3.6.8", 41 | "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-PT; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6", 42 | "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)", 43 | "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 (.NET CLR 3.5.30729)", 44 | "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4", 45 | "Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.1", 46 | } 47 | // Common rules 48 | // 关于笔趣阁的规则 49 | ItemRule01 = ItemRuleConfig{ 50 | NovelName: "div.bookinfo h4.bookname a", 51 | NovelUrl: "div.bookinfo h4.bookname a", 52 | NovelType: "div.bookinfo div.cat", 53 | NovelAuthor: "div.bookinfo div.author", 54 | NovelCover: "div.bookimg a img", 55 | NovelAbstract: "div.bookinfo p", 56 | NovelLatestChapterName: "div.bookinfo div.update a", 57 | NovelLatestChapterUrl: "div.bookinfo div.update a", 58 | } 59 | // 百度第三方网站内容检索的规则 60 | ItemRule02 = ItemRuleConfig{ 61 | NovelName: "div.result-game-item-detail a.result-game-item-title-link", 62 | NovelUrl: "div.result-game-item-detail a.result-game-item-title-link", 63 | NovelType: "div.result-game-item-info p:nth-child(2)", 64 | NovelAuthor: "div.result-game-item-info p:nth-child(1)", 65 | NovelCover: "img.result-game-item-pic-link-img", 66 | NovelAbstract: "p.result-game-item-desc", 67 | NovelLatestChapterName: "p.result-game-item-info-tag a.result-game-item-info-tag-item", 68 | NovelLatestChapterUrl: "p.result-game-item-info-tag a.result-game-item-info-tag-item", 69 | } 70 | // 全本小说网的小说检索规则 71 | ItemRule03 = ItemRuleConfig{ 72 | NovelName: "h1.f20h", 73 | NovelUrl: "div.option span.btopt a", 74 | NovelType: "div.box_intro > div.box_info > table > tbody > tr:nth-child(1) > td > div:nth-child(1) > p:nth-child(3) > a", 75 | NovelAuthor: "div.box_info > table > tbody > tr:nth-child(1) > td > div:nth-child(1) > p:nth-child(2)", 76 | NovelCover: "div.box_intro div.pic img", 77 | NovelAbstract: "div.intro", 78 | NovelLatestChapterName: "div.box_info > table > tbody > tr:nth-child(1) > td > div:nth-child(1) > p:nth-child(2) a", 79 | NovelLatestChapterUrl: "div.box_info > table > tbody > tr:nth-child(1) > td > div:nth-child(1) > p:nth-child(2) a", 80 | } 81 | // 全本小说网的作者信息检索规则 这里自定义为key_1 其中NovelUrl 值获取的是下载地址 客户端可根据最新章节地址提取出path就是目录地址 82 | ItemRule04 = ItemRuleConfig{ 83 | NovelName: "ul.info>h2>b>a>font", 84 | NovelUrl: "", 85 | NovelType: "ul.info > li:nth-child(4) > font:nth-child(2)", 86 | NovelAuthor: "ul.info > li:nth-child(4) > font:nth-child(1) > b", 87 | NovelCover: "ul.info a img", 88 | NovelAbstract: "ul.info > li:nth-child(3)", 89 | NovelLatestChapterName: "ul.info > h2 > a > font > b", 90 | NovelLatestChapterUrl: "ul.info > h2 > a", 91 | } 92 | // 新笔趣阁的解析规则 93 | ItemRule05 = ItemRuleConfig{ 94 | NovelName: "h3.result-item-title a span", 95 | NovelUrl: "h3.result-item-title a", 96 | NovelType: "div.result-game-item-info > p:nth-child(2) > span:nth-child(2)", 97 | NovelAuthor: "div.result-game-item-info > p:nth-child(1) > span:nth-child(2)", 98 | NovelCover: "img.result-game-item-pic-link-img", 99 | NovelAbstract: "p.result-game-item-desc", 100 | NovelLatestChapterName: "div.result-game-item-info p.result-game-item-info-tag a.result-game-item-info-tag-item", 101 | NovelLatestChapterUrl: "div.result-game-item-info p.result-game-item-info-tag a.result-game-item-info-tag-item", 102 | } 103 | NovelsRulesMap = map[string]NovelRule{ 104 | "10": NovelRule{ 105 | Name: "笔趣阁01", 106 | HomeUrl: "https://www.bqg99.cc/", 107 | SearchUrl: "https://www.bqg99.cc/s.php?q=", 108 | Method: "Get", 109 | Params: make(map[string](string)), 110 | KeywordEncoding: "", 111 | TargetItem: ".bookbox", 112 | ItemRule: ItemRule01, 113 | }, 114 | "11": NovelRule{ 115 | Name: "笔趣阁02", 116 | HomeUrl: "http://www.cdzdgw.com/", 117 | SearchUrl: "http://www.cdzdgw.com/s.php?q=", 118 | Method: "Get", 119 | Params: make(map[string](string)), 120 | KeywordEncoding: "", 121 | TargetItem: ".bookbox", 122 | ItemRule: ItemRule01, 123 | }, 124 | "12": NovelRule{ 125 | Name: "笔趣阁03", 126 | HomeUrl: "http://www.biqugex.com/", 127 | SearchUrl: "http://www.biqugex.com/s.php?q=", 128 | Method: "Get", 129 | Params: make(map[string](string)), 130 | KeywordEncoding: "", 131 | TargetItem: ".bookbox", 132 | ItemRule: ItemRule01, 133 | }, 134 | "20": NovelRule{ 135 | Name: "全本小说网01", 136 | HomeUrl: "https://www.ybdu.com/", 137 | SearchUrl: "https://www.ybdu.com/modules/article/search.php?searchtype=keywords&entry=1&searchkey=", 138 | Method: "Get", 139 | Params: make(map[string](string)), 140 | KeywordEncoding: "gbk", 141 | TargetItem: "#detail-box", 142 | ItemRule: ItemRule03, 143 | }, 144 | "20_1": NovelRule{ 145 | Name: "全本小说网01", 146 | HomeUrl: "https://www.ybdu.com/", 147 | SearchUrl: "https://www.ybdu.com/modules/article/search.php?searchtype=keywords&entry=1&searchkey=", 148 | Method: "Get", 149 | Params: make(map[string](string)), 150 | KeywordEncoding: "gbk", 151 | TargetItem: "ul.info", 152 | ItemRule: ItemRule04, 153 | }, 154 | "30": NovelRule{ 155 | Name: "新笔趣阁01", 156 | HomeUrl: "https://www.xxbiquge.com/", 157 | SearchUrl: "https://www.xxbiquge.com/search.php?keyword=", 158 | Method: "Get", 159 | Params: make(map[string](string)), 160 | KeywordEncoding: "", 161 | TargetItem: "div.result-game-item", 162 | ItemRule: ItemRule05, 163 | }, 164 | "100": NovelRule{ 165 | Name: "新笔趣阁_百度01", 166 | HomeUrl: "http://www.biqugetv.com/", 167 | SearchUrl: "http://zhannei.baidu.com/cse/search?click=1&s=16765504158186272814&q=", 168 | Method: "Get", 169 | Params: make(map[string](string)), 170 | KeywordEncoding: "", 171 | TargetItem: "div.result-list div.result-item", 172 | ItemRule: ItemRule02, 173 | }, 174 | "110": NovelRule{ 175 | Name: "笔下文学_百度01", 176 | HomeUrl: "http://www.xbxwx.net/", 177 | SearchUrl: "http://so.xbxwx.net/cse/search?click=1&entry=1&s=10874778206555383279&q=", 178 | Method: "Get", 179 | Params: make(map[string](string)), 180 | KeywordEncoding: "", 181 | TargetItem: "div.result-list div.result-item", 182 | ItemRule: ItemRule02, 183 | }, 184 | "120": NovelRule{ 185 | Name: "顶点小说_百度01", 186 | HomeUrl: "http://www.23wx.cc/du/99/99646/", 187 | SearchUrl: "http://zhannei.baidu.com/cse/search?s=17788970894453164958&q=", 188 | Method: "Get", 189 | Params: make(map[string](string)), 190 | KeywordEncoding: "", 191 | TargetItem: "div.result-list div.result-item", 192 | ItemRule: ItemRule02, 193 | }, 194 | } 195 | ) 196 | 197 | // GetUserAgent returns a random user agent 198 | func GetUserAgent() string { 199 | rand.Seed(time.Now().Unix()) 200 | n := rand.Int() % len(UserAgents) 201 | return UserAgents[n] 202 | } 203 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/howie6879/owllook_api/apis" 8 | ) 9 | 10 | // Index returns a home page 11 | func Index(c *gin.Context) { 12 | c.JSON(http.StatusOK, gin.H{"projetc_name": "owllook api dmeo", "version": "v1", "url": "/v1"}) 13 | } 14 | 15 | func main() { 16 | // gin.SetMode(gin.ReleaseMode) 17 | router := gin.Default() 18 | router.GET("/", Index) 19 | v1 := router.Group("v1") 20 | v1.GET("/novels/:name/:source", apis.SearchNovels) 21 | v1.GET("/authors/:name/:source", apis.SearchAuthors) 22 | router.Run() 23 | } 24 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "l1dgFJfzGLG/2AEPyt6ADsnVLPQ=", 7 | "path": "github.com/PuerkitoBio/goquery", 8 | "revision": "dc2ec5c7ca4d9aae063b79b9f581dd3ea6afd2b2", 9 | "revisionTime": "2018-06-07T15:06:10Z" 10 | }, 11 | { 12 | "checksumSHA1": "Q/2QpI7E35SsNYfaxLsWHFry9k4=", 13 | "path": "github.com/andybalholm/cascadia", 14 | "revision": "901648c87902174f774fac311d7f176f8647bdaa", 15 | "revisionTime": "2018-02-20T18:43:36Z" 16 | }, 17 | { 18 | "checksumSHA1": "kNtoxGIa3rApgMx/PREQeKqjKw8=", 19 | "path": "github.com/axgle/mahonia", 20 | "revision": "3358181d7394e26beccfae0ffde05193ef3be33a", 21 | "revisionTime": "2018-02-08T00:28:26Z" 22 | }, 23 | { 24 | "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", 25 | "path": "github.com/gin-contrib/sse", 26 | "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", 27 | "revisionTime": "2017-01-09T09:34:21Z" 28 | }, 29 | { 30 | "checksumSHA1": "cpELhz30Sco7IBwvI6FyuShnjnQ=", 31 | "path": "github.com/gin-gonic/gin", 32 | "revision": "d459835d2b077e44f7c9b453505ee29881d5d12d", 33 | "revisionTime": "2017-07-02T09:28:26Z", 34 | "version": "v1.2", 35 | "versionExact": "v1.2" 36 | }, 37 | { 38 | "checksumSHA1": "YVt5lqmGq95m5tFdiz5pr2BF1Jo=", 39 | "path": "github.com/gin-gonic/gin/binding", 40 | "revision": "d459835d2b077e44f7c9b453505ee29881d5d12d", 41 | "revisionTime": "2017-07-02T09:28:26Z", 42 | "version": "v1.2", 43 | "versionExact": "v1.2" 44 | }, 45 | { 46 | "checksumSHA1": "M3FwiqpVbXwo9t2FyI5/gKxCDKY=", 47 | "path": "github.com/gin-gonic/gin/render", 48 | "revision": "d459835d2b077e44f7c9b453505ee29881d5d12d", 49 | "revisionTime": "2017-07-02T09:28:26Z", 50 | "version": "v1.2", 51 | "versionExact": "v1.2" 52 | }, 53 | { 54 | "checksumSHA1": "JZV+pLo8Z/kIYExrZr1Zu9KSKyU=", 55 | "path": "github.com/golang/protobuf/proto", 56 | "revision": "5831880292e721c76b58a16ecc60adc27d8e6355", 57 | "revisionTime": "2018-06-12T18:59:29Z" 58 | }, 59 | { 60 | "checksumSHA1": "p3IB18uJRs4dL2K5yx24MrLYE9A=", 61 | "path": "github.com/google/go-querystring/query", 62 | "revision": "53e6ce116135b80d037921a7fdd5138cf32d7a8a", 63 | "revisionTime": "2017-01-11T10:11:55Z" 64 | }, 65 | { 66 | "checksumSHA1": "e3krTtxeGeDTyWRkZC7BOYfexCw=", 67 | "path": "github.com/levigross/grequests", 68 | "revision": "bf9788368aa04abf510962fd0fb0d8e4b74396cc", 69 | "revisionTime": "2017-10-09T01:03:47Z" 70 | }, 71 | { 72 | "checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=", 73 | "path": "github.com/mattn/go-isatty", 74 | "revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c", 75 | "revisionTime": "2017-11-07T05:05:31Z" 76 | }, 77 | { 78 | "checksumSHA1": "EEXppdradk5G/UaQUQ95BQ9n7uw=", 79 | "path": "github.com/saintfish/chardet", 80 | "revision": "3af4cd4741ca4f3eb0c407c034571a6fb0ea529c", 81 | "revisionTime": "2012-08-16T06:12:21Z" 82 | }, 83 | { 84 | "checksumSHA1": "kdas0zQNtQqfUMEMFGkUF1OcgPc=", 85 | "path": "github.com/ugorji/go/codec", 86 | "revision": "f3cacc17c85ecb7f1b6a9e373ee85d1480919868", 87 | "revisionTime": "2018-04-07T10:30:00Z" 88 | }, 89 | { 90 | "path": "golang.org/x/net/html", 91 | "revision": "" 92 | }, 93 | { 94 | "path": "golang.org/x/net/publicsuffix", 95 | "revision": "" 96 | }, 97 | { 98 | "path": "golang.org/x/sys/unix", 99 | "revision": "" 100 | }, 101 | { 102 | "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", 103 | "path": "gopkg.in/go-playground/validator.v8", 104 | "revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf", 105 | "revisionTime": "2017-07-30T05:02:35Z" 106 | }, 107 | { 108 | "checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=", 109 | "path": "gopkg.in/yaml.v2", 110 | "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", 111 | "revisionTime": "2018-03-28T19:50:20Z" 112 | } 113 | ], 114 | "rootPath": "github.com/howie6879/owllook_api" 115 | } 116 | --------------------------------------------------------------------------------