├── .gitignore ├── LICENSE ├── README.md ├── RS ├── .gitignore ├── RS.ipynb ├── lib │ ├── .gitignore │ ├── __init__.py │ ├── config.py │ ├── model.py │ └── utils.py └── seg_corpus │ └── .gitignore ├── crawler ├── .gitignore ├── corpus │ └── .gitignore ├── download_check.py ├── info_crawler.py ├── lib │ ├── .gitignore │ ├── __init__.py │ ├── config.py │ ├── model.py │ └── utils.py └── txt_downloader.py ├── requirements.txt ├── screenshot.png └── web_demo ├── .babelrc ├── .editorconfig ├── .gitignore ├── README.md ├── build ├── build.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── dist ├── index.html └── static │ ├── bootstrap.min.css │ ├── jquery.min.js │ └── js │ ├── app.d2d173414305722a1a2d.js │ ├── app.d2d173414305722a1a2d.js.map │ ├── manifest.b6282e62578cc7cefbdf.js │ ├── manifest.b6282e62578cc7cefbdf.js.map │ ├── vendor.0ad8be03111db2f40424.js │ └── vendor.0ad8be03111db2f40424.js.map ├── index.html ├── lib ├── .gitignore ├── __init__.py ├── config.py ├── model.py └── utils.py ├── main.py ├── package.json ├── src ├── App.vue ├── assets │ └── logo.png ├── components │ ├── NavBar.vue │ ├── NovelList.vue │ └── Search.vue ├── main.js └── vuex │ ├── actions.js │ ├── getters.js │ └── store.js └── static ├── .gitkeep ├── bootstrap.min.css └── jquery.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Kalen Blue 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 | # novelRS 2 | 一个简单的网络小说推荐系统。 3 | 4 | ## 开发环境 5 | python3.6 + mongodb 6 | 7 | ## 代码说明 8 | ### 运行小说爬虫 9 | #### 下载小说列表: 10 | ```bash 11 | cd crawler & python3 info_crawler.py 12 | ``` 13 | #### 下载小说的txt文件: 14 | ```bash 15 | cd crawler & python3 txt_downloader.py 16 | ``` 17 | #### 小说过滤(只考虑大于500KB的小说): 18 | ```bash 19 | cd crawler & python3 download_check.py 20 | ``` 21 | 22 | ### 运行推荐算法 23 | 通过ipython notebook打开RS.ipynb 24 | ```bash 25 | cd RS & ipython3 notebook 26 | ``` 27 | 然后逐步完成notebook中的以下步骤: 28 | - 1、分词 29 | - 2、词表分析 30 | - 3、TF-IDF构建 31 | - 4、KD-Tree最近邻查询 32 | - 5、相似度更新 33 | 34 | ### 运行网页Demo 35 | #### 导入数据 36 | 数据下载链接:https://pan.baidu.com/s/1PFjFBtaKaBeS90CL5-hIKA 密码:f16j 37 | ``` sh 38 | mongoimport -d novelRS -c novels --file=novels.json 39 | ``` 40 | 41 | #### 前端说明 42 | 基于vue1.0编写,可以不用care。 43 | ``` sh 44 | cd web_demo 45 | npm install # 安装依赖库 46 | npm run dev # 调试模式 47 | npm run build # 导出dist 48 | 49 | ``` 50 | 51 | #### 运行后台 52 | ```bash 53 | cd web_demo & python3 main.py 54 | ``` 55 | 56 | #### 测试效果 57 | 运行后,打开[http://localhost:38438](http://localhost:38438)。 58 | ![](screenshot.png) 59 | 60 | ## LICENSE 61 | MIT 62 | -------------------------------------------------------------------------------- /RS/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | 1.txt 4 | *.dat 5 | *.pickle 6 | .ipynb_checkpoints/ -------------------------------------------------------------------------------- /RS/lib/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | __pycache__ -------------------------------------------------------------------------------- /RS/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nladuo/novelRS/6bbb687e385f1137f387547d201f0dbbee1538c0/RS/lib/__init__.py -------------------------------------------------------------------------------- /RS/lib/config.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | config = { 4 | 'timeout': 10, 5 | 'db_user': '', # mongodb的用户名 6 | 'db_pass': '', # mongodb的密码 7 | 'db_host': 'localhost', 8 | 'db_port': 27017, 9 | 'db_name': 'novelRS', 10 | 'cpu_num': 4 # 开几个进程计算 11 | } 12 | -------------------------------------------------------------------------------- /RS/lib/model.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | class Novel: 5 | """ 小说结构 """ 6 | def __init__(self, name, url, author, category, abstract, download_url): 7 | self.name = name 8 | self.url = url 9 | self.author = author 10 | self.category = category 11 | self.abstract = abstract 12 | self.download_url = download_url 13 | 14 | def dict(self): 15 | return { 16 | 'name': self.name, 17 | 'url': self.url, 18 | 'author': self.author, 19 | 'category': self.category, 20 | 'abstract': self.abstract, 21 | 'download_url': self.download_url, 22 | 'is_downloaded': False, # 是否下载 23 | 'success': True, # 下载是否成功, 24 | 'is_segment': False, # 是否分词 25 | } 26 | 27 | 28 | class FailedUrl: 29 | """ 失败的链接 """ 30 | def __init__(self, url): 31 | self.url = url 32 | 33 | def dict(self): 34 | return {'url': self.url} 35 | 36 | 37 | class Similarity: 38 | """ 保存两个小说之间相似度 """ 39 | def __init__(self, novel_id, similarity): 40 | self.novel_id = novel_id 41 | self.similarity = similarity 42 | 43 | def dict(self): 44 | return { 45 | 'novel_id': self.novel_id, 46 | 'similarity': self.similarity, 47 | } 48 | -------------------------------------------------------------------------------- /RS/lib/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pymongo 3 | import requests 4 | from .model import FailedUrl 5 | from .config import * 6 | 7 | 8 | def init_client(): 9 | """ 初始化mongo客户端 """ 10 | client = pymongo.MongoClient(config['db_host'], config['db_port']) 11 | if len(config['db_user']) != 0: 12 | admin = client['admin'] 13 | admin.authenticate(config['db_user'], config['db_pass']) 14 | return client 15 | 16 | 17 | def get_body(url): 18 | """ 发送http请求 """ 19 | retry_times = 0 20 | while retry_times < 3: 21 | try: 22 | content = requests.get(url, timeout=config['timeout']).content 23 | return content 24 | except KeyboardInterrupt: 25 | print("KeyboardInterrupt, now_url:", url) 26 | raise 27 | except: 28 | retry_times += 1 29 | return '' 30 | 31 | 32 | def add_failed_url(db, url): 33 | """ 把失败的url添加到数据库 """ 34 | collection = db.failed_urls 35 | if collection.find({'url': url}).count() == 0: 36 | collection.insert(FailedUrl(url).dict()) 37 | 38 | def read_novel(path): 39 | with open(path, "r") as f: 40 | return f.read().decode("gb2312", 'ignore') 41 | -------------------------------------------------------------------------------- /RS/seg_corpus/.gitignore: -------------------------------------------------------------------------------- 1 | *.txt -------------------------------------------------------------------------------- /crawler/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | test.py 4 | test.txt -------------------------------------------------------------------------------- /crawler/corpus/.gitignore: -------------------------------------------------------------------------------- 1 | *.txt -------------------------------------------------------------------------------- /crawler/download_check.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 标记下载大小小于400K的小说 """ 3 | from __future__ import print_function 4 | from lib.utils import * 5 | from lib.config import * 6 | import os.path 7 | 8 | 9 | def check_download(novel): 10 | path = os.path.join('corpus', str(novel["_id"]) + ".txt") 11 | try: 12 | filesize = os.path.getsize(path) 13 | success = filesize >= 500 * 1024 # 保留大于500KB的小说 14 | print(novel['_id'], novel['name'], "filesize:", filesize, "success:", success) 15 | 16 | return success 17 | except: 18 | return False 19 | 20 | 21 | class DownloadChecker: 22 | """ 爬取小说的章节,存到数据库中 """ 23 | def __init__(self): 24 | self.client = init_client() 25 | self.db = self.client[config['db_name']] 26 | self.novels = self.db.novels.find({}) 27 | 28 | def run(self): 29 | novels = [] 30 | # 先把数据都读到内存里 31 | for novel in self.novels: 32 | novels.append(novel) 33 | 34 | for novel in novels: 35 | success = check_download(novel) 36 | self.__update_novel(novel, success) 37 | 38 | self.__close() 39 | 40 | def __update_novel(self, novel, success): 41 | """ 把小说设置为已经爬去取过 """ 42 | self.db.novels.update({'_id': novel['_id']}, { 43 | '$set': {'is_saved': success}, 44 | }) 45 | 46 | def __close(self): 47 | """ 关闭数据库 """ 48 | self.client.close() 49 | 50 | 51 | if __name__ == '__main__': 52 | checker = DownloadChecker() 53 | checker.run() 54 | print("DownloadChecker has been finished.") 55 | 56 | -------------------------------------------------------------------------------- /crawler/info_crawler.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import print_function 3 | from bs4 import BeautifulSoup 4 | from lib.utils import * 5 | from lib.model import * 6 | from lib.config import * 7 | import traceback 8 | 9 | 10 | def get_page_num(html): 11 | soup = BeautifulSoup(html, "html.parser") 12 | a = soup.find("div", {"class": "tspage"}).find_all("a")[1] 13 | page_str = a.attrs["href"].split("/")[-1].replace("index_", "").replace( ".html", "") 14 | return int(page_str) 15 | 16 | 17 | class InfoCrawler: 18 | """ 爬取小说基本信息 """ 19 | def __init__(self): 20 | self.client = init_client() 21 | self.db = self.client[config['db_name']] 22 | self.collection = self.db.novels 23 | self.collection.ensure_index('url', unique=True) 24 | 25 | def run(self): 26 | # 只爬取玄幻奇幻和武侠仙侠两个类别 27 | start_urls = [ 28 | "https://www.qisuu.la/soft/sort01/", 29 | "https://www.qisuu.la/soft/sort02/" 30 | ] 31 | 32 | # For中断重新爬取 33 | start_index = { 34 | "https://www.qisuu.la/soft/sort01/": 1, 35 | "https://www.qisuu.la/soft/sort02/": 1 36 | } 37 | 38 | # 开始爬取 39 | for start_url in start_urls: 40 | html = get_body(start_url) 41 | if html == "": 42 | raise Exception("Error download init url: %s" % start_url ) 43 | page_num = get_page_num(html) 44 | print(start_url, "page_num:", page_num) 45 | for page in range(start_index[start_url], page_num + 1): 46 | url = start_url + "index_%d.html" % page 47 | if page == 1: 48 | url = start_url 49 | print("正在爬取:", url) 50 | html = get_body(url) 51 | if html == "": 52 | add_failed_url(self.db, url);continue 53 | novels = self.__parse(html) 54 | self.__add_novels(novels) 55 | self.__close() 56 | 57 | def __add_novels(self, novels): 58 | for novel in novels: 59 | try: 60 | if self.collection.find({"url": novel.url}).count() == 0: 61 | self.collection.insert(novel.dict()) 62 | except Exception as ex: 63 | traceback.print_exc() 64 | 65 | def __close(self): 66 | """ 关闭数据库 """ 67 | self.client.close() 68 | 69 | @staticmethod 70 | def __parse(html): 71 | """ 解析小说 """ 72 | # print(html) 73 | novels = [] 74 | soup = BeautifulSoup(html, "html.parser") 75 | lis = soup.find("div", {"class": "listBox"}).find_all("li") 76 | for li in lis: 77 | for i, child in enumerate(li.children): 78 | if i == 3: 79 | url = "https://www.qisuu.la" + child.attrs["href"] 80 | # 下载详情页面 81 | html2 = get_body(url) 82 | soup2 = BeautifulSoup(html2, "html.parser") 83 | abstract = soup2.find("div", {"class": "showInfo"}).get_text() 84 | author = soup2.find("div", {"class": "detail_right"}).find_all("li")[5].\ 85 | get_text().replace("书籍作者:", "") 86 | name = soup2.find("div", {"class": "showDown"}).script.get_text().split("'")[5] 87 | txt_url = soup2.find("div", {"class": "showDown"}).script.get_text().split("','")[1] 88 | category = soup2.find("div", {"class": "wrap position"}).span.find_all("a")[-2].get_text() 89 | print("《"+name+"》", "作者:", author, "类别:", category, txt_url) 90 | novels.append(Novel(name, url, author, category, abstract, txt_url)) 91 | return novels 92 | 93 | 94 | if __name__ == '__main__': 95 | crawler = InfoCrawler() 96 | crawler.run() 97 | print("info_crawler has been finished.") 98 | -------------------------------------------------------------------------------- /crawler/lib/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log -------------------------------------------------------------------------------- /crawler/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nladuo/novelRS/6bbb687e385f1137f387547d201f0dbbee1538c0/crawler/lib/__init__.py -------------------------------------------------------------------------------- /crawler/lib/config.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | config = { 4 | 'timeout': 10, 5 | 'db_user': '', # mongodb的用户名 6 | 'db_pass': '', # mongodb的密码 7 | 'db_host': 'localhost', 8 | 'db_port': 27017, 9 | 'db_name': 'novelRS', 10 | 'cpu_num': 4 # 开几个进程计算 11 | } 12 | -------------------------------------------------------------------------------- /crawler/lib/model.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | class Novel: 5 | """ 小说结构 """ 6 | def __init__(self, name, url, author, category, abstract, download_url): 7 | self.name = name 8 | self.url = url 9 | self.author = author 10 | self.category = category 11 | self.abstract = abstract 12 | self.download_url = download_url 13 | 14 | def dict(self): 15 | return { 16 | 'name': self.name, 17 | 'url': self.url, 18 | 'author': self.author, 19 | 'category': self.category, 20 | 'abstract': self.abstract, 21 | 'download_url': self.download_url, 22 | 'is_downloaded': False, # 是否下载 23 | 'success': True, # 下载是否成功, 24 | 'is_segment': False, # 是否分词 25 | } 26 | 27 | 28 | class FailedUrl: 29 | """ 失败的链接 """ 30 | def __init__(self, url): 31 | self.url = url 32 | 33 | def dict(self): 34 | return {'url': self.url} 35 | 36 | 37 | class Similarity: 38 | """ 保存两个小说之间相似度 """ 39 | def __init__(self, novel_id, similarity): 40 | self.novel_id = novel_id 41 | self.similarity = similarity 42 | 43 | def dict(self): 44 | return { 45 | 'novel_id': self.novel_id, 46 | 'similarity': self.similarity, 47 | } 48 | -------------------------------------------------------------------------------- /crawler/lib/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pymongo 3 | import requests 4 | from .model import FailedUrl 5 | from .config import * 6 | 7 | 8 | def init_client(): 9 | """ 初始化mongo客户端 """ 10 | client = pymongo.MongoClient(config['db_host'], config['db_port']) 11 | if len(config['db_user']) != 0: 12 | admin = client['admin'] 13 | admin.authenticate(config['db_user'], config['db_pass']) 14 | return client 15 | 16 | 17 | def get_body(url): 18 | """ 发送http请求 """ 19 | retry_times = 0 20 | while retry_times < 3: 21 | try: 22 | content = requests.get(url, timeout=config['timeout']).content 23 | return content 24 | except KeyboardInterrupt: 25 | print("KeyboardInterrupt, now_url:", url) 26 | raise 27 | except: 28 | retry_times += 1 29 | return '' 30 | 31 | 32 | def add_failed_url(db, url): 33 | """ 把失败的url添加到数据库 """ 34 | collection = db.failed_urls 35 | if collection.find({'url': url}).count() == 0: 36 | collection.insert(FailedUrl(url).dict()) 37 | 38 | 39 | def read_novel(path): 40 | with open(path, "r") as f: 41 | return f.read().decode("gb2312", 'ignore') 42 | -------------------------------------------------------------------------------- /crawler/txt_downloader.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import print_function 3 | import time 4 | from lib.utils import * 5 | from lib.config import * 6 | from urllib.parse import quote 7 | from urllib.request import urlretrieve 8 | import os.path 9 | import socket 10 | import sys 11 | socket.setdefaulttimeout(20) 12 | 13 | 14 | def reporthook(count, block_size, total_size): 15 | global start_time 16 | if count == 0: 17 | start_time = time.time() 18 | return 19 | duration = time.time() - start_time 20 | if duration == 0: 21 | duration = 1 22 | progress_size = int(count * block_size) 23 | speed = int(progress_size / (1024 * duration)) 24 | percent = min(int(count * block_size * 100 / total_size), 100) 25 | sys.stdout.write("\r.....%d%%, %d KB, %d KB/s, %d seconds passed....." % 26 | (percent, progress_size / 1024, speed, duration)) 27 | sys.stdout.flush() 28 | 29 | 30 | class TxtDownloader: 31 | """ 爬取小说的章节,存到数据库中 """ 32 | def __init__(self): 33 | self.client = init_client() 34 | self.db = self.client[config['db_name']] 35 | self.novels = self.db.novels.find({'is_downloaded': False}) 36 | 37 | def run(self): 38 | novels = [] 39 | # 先把数据都读到内存里 40 | for novel in self.novels: 41 | novels.append(novel) 42 | 43 | for novel in novels: 44 | download_url = quote(str(novel['download_url'])).replace("https%3A", "https:") 45 | print("downloading", novel['_id'], novel['name'], novel['author'], novel["category"], 46 | download_url) 47 | 48 | filename = os.path.join('corpus', str(novel["_id"]) + ".txt") 49 | success = False 50 | while not success: 51 | try: 52 | urlretrieve(download_url, filename, reporthook) 53 | success = True 54 | except IOError as ex: 55 | if "HTTP Error 404: Not Found" in str(ex): 56 | break 57 | print("timeout error") 58 | time.sleep(1) 59 | 60 | # 判断爬取是否正确 61 | if success: 62 | print("\nSaved in", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))) 63 | else: 64 | print("HTTP Error 404: Not Found") 65 | self.__update_failed_novel(novel) # 把novel的success设为false 66 | self.__update_novel(novel) # 把novel的is_downloaded设为true 67 | print("\n") 68 | time.sleep(1) 69 | 70 | self.__close() 71 | 72 | def __update_novel(self, novel): 73 | """ 把小说设置为已经爬去取过 """ 74 | self.db.novels.update({'_id': novel['_id']}, { 75 | '$set': {'is_downloaded': True}, 76 | }) 77 | 78 | def __update_failed_novel(self, novel): 79 | """ 把小说设置为已经爬去取过 """ 80 | self.db.novels.update({'_id': novel['_id']}, { 81 | '$set': {'success': False}, 82 | }) 83 | 84 | def __close(self): 85 | """ 关闭数据库 """ 86 | self.client.close() 87 | 88 | 89 | if __name__ == '__main__': 90 | crawler = TxtDownloader() 91 | crawler.run() 92 | print("txt_downloader has been finished.") 93 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pymongo 2 | requests 3 | lxml 4 | jieba 5 | bs4 6 | gevent 7 | numpy 8 | scikit-learn 9 | flask 10 | scipy -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nladuo/novelRS/6bbb687e385f1137f387547d201f0dbbee1538c0/screenshot.png -------------------------------------------------------------------------------- /web_demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /web_demo/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.js] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.vue] 12 | charset = utf-8 13 | indent_style = space 14 | indent_size = 2 15 | end_of_line = lf 16 | insert_final_newline = true 17 | trim_trailing_whitespace = true 18 | -------------------------------------------------------------------------------- /web_demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | .idea/ 5 | package-lock.json -------------------------------------------------------------------------------- /web_demo/README.md: -------------------------------------------------------------------------------- 1 | # web_demo 2 | 3 | > Novel Recommend System WebSite Demo. 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 18 | -------------------------------------------------------------------------------- /web_demo/build/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | require('shelljs/global') 3 | env.NODE_ENV = 'production' 4 | 5 | var path = require('path') 6 | var config = require('../config') 7 | var ora = require('ora') 8 | var webpack = require('webpack') 9 | var webpackConfig = require('./webpack.prod.conf') 10 | 11 | console.log( 12 | ' Tip:\n' + 13 | ' Built files are meant to be served over an HTTP server.\n' + 14 | ' Opening index.html over file:// won\'t work.\n' 15 | ) 16 | 17 | var spinner = ora('building for production...') 18 | spinner.start() 19 | 20 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) 21 | rm('-rf', assetsPath) 22 | mkdir('-p', assetsPath) 23 | cp('-R', 'static/', assetsPath) 24 | 25 | webpack(webpackConfig, function (err, stats) { 26 | spinner.stop() 27 | if (err) throw err 28 | process.stdout.write(stats.toString({ 29 | colors: true, 30 | modules: false, 31 | children: false, 32 | chunks: false, 33 | chunkModules: false 34 | }) + '\n') 35 | }) 36 | -------------------------------------------------------------------------------- /web_demo/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /web_demo/build/dev-server.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var express = require('express') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var proxyMiddleware = require('http-proxy-middleware') 6 | var webpackConfig = process.env.NODE_ENV === 'testing' 7 | ? require('./webpack.prod.conf') 8 | : require('./webpack.dev.conf') 9 | 10 | // default port where dev server listens for incoming traffic 11 | var port = process.env.PORT || config.dev.port 12 | // Define HTTP proxies to your custom API backend 13 | // https://github.com/chimurai/http-proxy-middleware 14 | var proxyTable = config.dev.proxyTable 15 | 16 | var app = express() 17 | var compiler = webpack(webpackConfig) 18 | 19 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 20 | publicPath: webpackConfig.output.publicPath, 21 | stats: { 22 | colors: true, 23 | chunks: false 24 | } 25 | }) 26 | 27 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 28 | // force page reload when html-webpack-plugin template changes 29 | compiler.plugin('compilation', function (compilation) { 30 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 31 | hotMiddleware.publish({ action: 'reload' }) 32 | cb() 33 | }) 34 | }) 35 | 36 | // proxy api requests 37 | Object.keys(proxyTable).forEach(function (context) { 38 | var options = proxyTable[context] 39 | if (typeof options === 'string') { 40 | options = { target: options } 41 | } 42 | app.use(proxyMiddleware(context, options)) 43 | }) 44 | 45 | // handle fallback for HTML5 history API 46 | app.use(require('connect-history-api-fallback')()) 47 | 48 | // serve webpack bundle output 49 | app.use(devMiddleware) 50 | 51 | // enable hot-reload and state-preserving 52 | // compilation error display 53 | app.use(hotMiddleware) 54 | 55 | // serve pure static assets 56 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 57 | app.use(staticPath, express.static('./static')) 58 | 59 | module.exports = app.listen(port, function (err) { 60 | if (err) { 61 | console.log(err) 62 | return 63 | } 64 | console.log('Listening at http://localhost:' + port + '\n') 65 | }) 66 | -------------------------------------------------------------------------------- /web_demo/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | // generate loader string to be used with extract text plugin 15 | function generateLoaders (loaders) { 16 | var sourceLoader = loaders.map(function (loader) { 17 | var extraParamChar 18 | if (/\?/.test(loader)) { 19 | loader = loader.replace(/\?/, '-loader?') 20 | extraParamChar = '&' 21 | } else { 22 | loader = loader + '-loader' 23 | extraParamChar = '?' 24 | } 25 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 26 | }).join('!') 27 | 28 | if (options.extract) { 29 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 30 | } else { 31 | return ['vue-style-loader', sourceLoader].join('!') 32 | } 33 | } 34 | 35 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 36 | return { 37 | css: generateLoaders(['css']), 38 | postcss: generateLoaders(['css']), 39 | less: generateLoaders(['css', 'less']), 40 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 41 | scss: generateLoaders(['css', 'sass']), 42 | stylus: generateLoaders(['css', 'stylus']), 43 | styl: generateLoaders(['css', 'stylus']) 44 | } 45 | } 46 | 47 | // Generate loaders for standalone style files (outside of .vue) 48 | exports.styleLoaders = function (options) { 49 | var output = [] 50 | var loaders = exports.cssLoaders(options) 51 | for (var extension in loaders) { 52 | var loader = loaders[extension] 53 | output.push({ 54 | test: new RegExp('\\.' + extension + '$'), 55 | loader: loader 56 | }) 57 | } 58 | return output 59 | } 60 | -------------------------------------------------------------------------------- /web_demo/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var projectRoot = path.resolve(__dirname, '../') 5 | 6 | module.exports = { 7 | entry: { 8 | app: './src/main.js' 9 | }, 10 | output: { 11 | path: config.build.assetsRoot, 12 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, 13 | filename: '[name].js' 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.vue'], 17 | fallback: [path.join(__dirname, '../node_modules')], 18 | alias: { 19 | 'src': path.resolve(__dirname, '../src'), 20 | 'assets': path.resolve(__dirname, '../src/assets'), 21 | 'components': path.resolve(__dirname, '../src/components') 22 | } 23 | }, 24 | resolveLoader: { 25 | fallback: [path.join(__dirname, '../node_modules')] 26 | }, 27 | module: { 28 | loaders: [ 29 | { 30 | test: /\.vue$/, 31 | loader: 'vue' 32 | }, 33 | { 34 | test: /\.js$/, 35 | loader: 'babel', 36 | include: projectRoot, 37 | exclude: /node_modules/ 38 | }, 39 | { 40 | test: /\.json$/, 41 | loader: 'json' 42 | }, 43 | { 44 | test: /\.html$/, 45 | loader: 'vue-html' 46 | }, 47 | { 48 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 49 | loader: 'url', 50 | query: { 51 | limit: 10000, 52 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 53 | } 54 | }, 55 | { 56 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 57 | loader: 'url', 58 | query: { 59 | limit: 10000, 60 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 61 | } 62 | } 63 | ] 64 | }, 65 | vue: { 66 | loaders: utils.cssLoaders() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /web_demo/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var config = require('../config') 2 | var webpack = require('webpack') 3 | var merge = require('webpack-merge') 4 | var utils = require('./utils') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | 8 | // add hot-reload related code to entry chunks 9 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 10 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 11 | }) 12 | 13 | module.exports = merge(baseWebpackConfig, { 14 | module: { 15 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 16 | }, 17 | // eval-source-map is faster for development 18 | devtool: '#eval-source-map', 19 | plugins: [ 20 | new webpack.DefinePlugin({ 21 | 'process.env': config.dev.env 22 | }), 23 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 24 | new webpack.optimize.OccurenceOrderPlugin(), 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }) 33 | ] 34 | }) 35 | -------------------------------------------------------------------------------- /web_demo/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var env = process.env.NODE_ENV === 'testing' 10 | ? require('../config/test.env') 11 | : config.build.env 12 | 13 | var webpackConfig = merge(baseWebpackConfig, { 14 | module: { 15 | loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) 16 | }, 17 | devtool: config.build.productionSourceMap ? '#source-map' : false, 18 | output: { 19 | path: config.build.assetsRoot, 20 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 21 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 22 | }, 23 | vue: { 24 | loaders: utils.cssLoaders({ 25 | sourceMap: config.build.productionSourceMap, 26 | extract: true 27 | }) 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | } 38 | }), 39 | new webpack.optimize.OccurenceOrderPlugin(), 40 | // extract css into its own file 41 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), 42 | // generate dist index.html with correct asset hash for caching. 43 | // you can customize output by editing /index.html 44 | // see https://github.com/ampedandwired/html-webpack-plugin 45 | new HtmlWebpackPlugin({ 46 | filename: process.env.NODE_ENV === 'testing' 47 | ? 'index.html' 48 | : config.build.index, 49 | template: 'index.html', 50 | inject: true, 51 | minify: { 52 | removeComments: true, 53 | collapseWhitespace: true, 54 | removeAttributeQuotes: true 55 | // more options: 56 | // https://github.com/kangax/html-minifier#options-quick-reference 57 | }, 58 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 59 | chunksSortMode: 'dependency' 60 | }), 61 | // split vendor js into its own file 62 | new webpack.optimize.CommonsChunkPlugin({ 63 | name: 'vendor', 64 | minChunks: function (module, count) { 65 | // any required modules inside node_modules are extracted to vendor 66 | return ( 67 | module.resource && 68 | /\.js$/.test(module.resource) && 69 | module.resource.indexOf( 70 | path.join(__dirname, '../node_modules') 71 | ) === 0 72 | ) 73 | } 74 | }), 75 | // extract webpack runtime and module manifest to its own file in order to 76 | // prevent vendor hash from being updated whenever app bundle is updated 77 | new webpack.optimize.CommonsChunkPlugin({ 78 | name: 'manifest', 79 | chunks: ['vendor'] 80 | }) 81 | ] 82 | }) 83 | 84 | if (config.build.productionGzip) { 85 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 86 | 87 | webpackConfig.plugins.push( 88 | new CompressionWebpackPlugin({ 89 | asset: '[path].gz[query]', 90 | algorithm: 'gzip', 91 | test: new RegExp( 92 | '\\.(' + 93 | config.build.productionGzipExtensions.join('|') + 94 | ')$' 95 | ), 96 | threshold: 10240, 97 | minRatio: 0.8 98 | }) 99 | ) 100 | } 101 | 102 | module.exports = webpackConfig 103 | -------------------------------------------------------------------------------- /web_demo/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /web_demo/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'] 18 | }, 19 | dev: { 20 | env: require('./dev.env'), 21 | port: 8080, 22 | assetsSubDirectory: 'static', 23 | assetsPublicPath: '/', 24 | proxyTable: {}, 25 | // CSS Sourcemaps off by default because relative paths are "buggy" 26 | // with this option, according to the CSS-Loader README 27 | // (https://github.com/webpack/css-loader#sourcemaps) 28 | // In our experience, they generally work as expected, 29 | // just be aware of this issue when enabling this option. 30 | cssSourceMap: false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web_demo/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /web_demo/config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /web_demo/dist/index.html: -------------------------------------------------------------------------------- 1 | 网络小说推荐系统 -------------------------------------------------------------------------------- /web_demo/dist/static/js/app.d2d173414305722a1a2d.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([1,0],[function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}var r=o(1),s=n(r),u=o(13),a=n(u);new s.default({el:"body",components:{App:a.default}})},,function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.searchNovels=function(e,t){var o=e.dispatch;o("SEARCH_NOVELS",t)}},function(e,t){"use strict";function o(e){return e.novels}Object.defineProperty(t,"__esModule",{value:!0}),t.getNovels=o},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=o(1),s=n(r),u=o(17),a=n(u);s.default.use(a.default);var c={novels:[]},l={SEARCH_NOVELS:function(e,t){var o="/api/search/"+t;$.ajax({type:"GET",url:o,dataType:"json",success:function(o){e.novels=o,0==o.length&&alert("未找到"+t)}})}};t.default=new a.default.Store({state:c,mutations:l})},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=o(15),s=n(r),u=o(16),a=n(u),c=o(14),l=n(c),d=o(4),p=n(d);t.default={components:{NavBar:l.default,Search:a.default,NovelList:s.default},store:p.default}},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={}},function(e,t,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=o(3);t.default={methods:{convert:function(e){return Math.floor(1e4*e)/100+"%"}},vuex:{getters:{novels:n.getNovels}}}},function(e,t,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=o(2);t.default={data:function(){return{novel_name:""}},vuex:{actions:{searchNovels:n.searchNovels}},methods:{search:function(){this.searchNovels(this.novel_name)}}}},function(e,t){e.exports="
"},function(e,t){e.exports="

网络小说推荐系统

"},function(e,t){e.exports='
书名 作者 分类 相似度
{{ novel.name }} {{ novel.author }} {{ novel.category }} {{ convert(novel.similarity) }}
'},function(e,t){e.exports='
'},function(e,t,o){var n,r,s={};n=o(5),r=o(9),e.exports=n||{},e.exports.__esModule&&(e.exports=e.exports.default);var u="function"==typeof e.exports?e.exports.options||(e.exports.options={}):e.exports;r&&(u.template=r),u.computed||(u.computed={}),Object.keys(s).forEach(function(e){var t=s[e];u.computed[e]=function(){return t}})},function(e,t,o){var n,r,s={};n=o(6),r=o(10),e.exports=n||{},e.exports.__esModule&&(e.exports=e.exports.default);var u="function"==typeof e.exports?e.exports.options||(e.exports.options={}):e.exports;r&&(u.template=r),u.computed||(u.computed={}),Object.keys(s).forEach(function(e){var t=s[e];u.computed[e]=function(){return t}})},function(e,t,o){var n,r,s={};n=o(7),r=o(11),e.exports=n||{},e.exports.__esModule&&(e.exports=e.exports.default);var u="function"==typeof e.exports?e.exports.options||(e.exports.options={}):e.exports;r&&(u.template=r),u.computed||(u.computed={}),Object.keys(s).forEach(function(e){var t=s[e];u.computed[e]=function(){return t}})},function(e,t,o){var n,r,s={};n=o(8),r=o(12),e.exports=n||{},e.exports.__esModule&&(e.exports=e.exports.default);var u="function"==typeof e.exports?e.exports.options||(e.exports.options={}):e.exports;r&&(u.template=r),u.computed||(u.computed={}),Object.keys(s).forEach(function(e){var t=s[e];u.computed[e]=function(){return t}})}]); 2 | //# sourceMappingURL=app.d2d173414305722a1a2d.js.map -------------------------------------------------------------------------------- /web_demo/dist/static/js/app.d2d173414305722a1a2d.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///static/js/app.d2d173414305722a1a2d.js","webpack:///./src/main.js","webpack:///./src/vuex/actions.js","webpack:///./src/vuex/getters.js","webpack:///./src/vuex/store.js","webpack:///App.vue","webpack:///NovelList.vue","webpack:///Search.vue","webpack:///./src/App.vue?d818","webpack:///./src/components/NavBar.vue?a699","webpack:///./src/components/NovelList.vue?23c4","webpack:///./src/components/Search.vue?699a","webpack:///./src/App.vue","webpack:///./src/components/NavBar.vue","webpack:///./src/components/NovelList.vue","webpack:///./src/components/Search.vue"],"names":["webpackJsonp","module","exports","__webpack_require__","_interopRequireDefault","obj","__esModule","default","_vue","_vue2","_App","_App2","Vue","el","components","App","Object","defineProperty","value","searchNovels","_ref","novel_name","dispatch","getNovels","state","novels","_vuex","_vuex2","use","Vuex","mutations","SEARCH_NOVELS","name","url","$","ajax","type","dataType","success","data","length","alert","Store","_NovelList","_NovelList2","_Search","_Search2","_NavBar","_NavBar2","_store","_store2","NavBar","Search","NovelList","store","_getters","methods","convert","similarity","Math","floor","vuex","getters","_actions","actions","search","this","__vue_script__","__vue_template__","__vue_styles__","__vue_options__","options","template","computed","keys","forEach","key"],"mappings":"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","file":"static/js/app.d2d173414305722a1a2d.js","sourcesContent":["webpackJsonp([1,0],[\n/* 0 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\tvar _vue = __webpack_require__(1);\n\t\n\tvar _vue2 = _interopRequireDefault(_vue);\n\t\n\tvar _App = __webpack_require__(13);\n\t\n\tvar _App2 = _interopRequireDefault(_App);\n\t\n\tfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\t\n\tnew _vue2.default({\n\t el: 'body',\n\t components: { App: _App2.default }\n\t});\n\n/***/ }),\n/* 1 */,\n/* 2 */\n/***/ (function(module, exports) {\n\n\t'use strict';\n\t\n\tObject.defineProperty(exports, \"__esModule\", {\n\t value: true\n\t});\n\tvar searchNovels = exports.searchNovels = function searchNovels(_ref, novel_name) {\n\t var dispatch = _ref.dispatch;\n\t\n\t dispatch('SEARCH_NOVELS', novel_name);\n\t};\n\n/***/ }),\n/* 3 */\n/***/ (function(module, exports) {\n\n\t\"use strict\";\n\t\n\tObject.defineProperty(exports, \"__esModule\", {\n\t value: true\n\t});\n\texports.getNovels = getNovels;\n\tfunction getNovels(state) {\n\t return state.novels;\n\t}\n\n/***/ }),\n/* 4 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\tObject.defineProperty(exports, \"__esModule\", {\n\t value: true\n\t});\n\t\n\tvar _vue = __webpack_require__(1);\n\t\n\tvar _vue2 = _interopRequireDefault(_vue);\n\t\n\tvar _vuex = __webpack_require__(17);\n\t\n\tvar _vuex2 = _interopRequireDefault(_vuex);\n\t\n\tfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\t\n\t_vue2.default.use(_vuex2.default);\n\t\n\tvar state = {\n\t novels: []\n\t};\n\t\n\tvar mutations = {\n\t SEARCH_NOVELS: function SEARCH_NOVELS(state, name) {\n\t var url = '/api/search/' + name;\n\t $.ajax({\n\t type: \"GET\",\n\t url: url,\n\t dataType: \"json\",\n\t success: function success(data) {\n\t state.novels = data;\n\t if (data.length == 0) {\n\t alert(\"未找到\" + name);\n\t }\n\t }\n\t });\n\t }\n\t};\n\t\n\texports.default = new _vuex2.default.Store({\n\t state: state,\n\t mutations: mutations\n\t});\n\n/***/ }),\n/* 5 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\tObject.defineProperty(exports, \"__esModule\", {\n\t value: true\n\t});\n\t\n\tvar _NovelList = __webpack_require__(15);\n\t\n\tvar _NovelList2 = _interopRequireDefault(_NovelList);\n\t\n\tvar _Search = __webpack_require__(16);\n\t\n\tvar _Search2 = _interopRequireDefault(_Search);\n\t\n\tvar _NavBar = __webpack_require__(14);\n\t\n\tvar _NavBar2 = _interopRequireDefault(_NavBar);\n\t\n\tvar _store = __webpack_require__(4);\n\t\n\tvar _store2 = _interopRequireDefault(_store);\n\t\n\tfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\t\n\texports.default = {\n\t components: {\n\t NavBar: _NavBar2.default,\n\t Search: _Search2.default,\n\t NovelList: _NovelList2.default\n\t },\n\t store: _store2.default\n\t};\n\n/***/ }),\n/* 6 */\n/***/ (function(module, exports) {\n\n\t\"use strict\";\n\n\tObject.defineProperty(exports, \"__esModule\", {\n\t value: true\n\t});\n\texports.default = {};\n\n/***/ }),\n/* 7 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\t\n\tObject.defineProperty(exports, \"__esModule\", {\n\t value: true\n\t});\n\t\n\tvar _getters = __webpack_require__(3);\n\t\n\texports.default = {\n\t methods: {\n\t convert: function convert(similarity) {\n\t return Math.floor(similarity * 10000) / 100 + '%';\n\t }\n\t },\n\t vuex: {\n\t getters: {\n\t novels: _getters.getNovels\n\t }\n\t }\n\t};\n\n/***/ }),\n/* 8 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\t\"use strict\";\n\t\n\tObject.defineProperty(exports, \"__esModule\", {\n\t value: true\n\t});\n\t\n\tvar _actions = __webpack_require__(2);\n\t\n\texports.default = {\n\t data: function data() {\n\t return {\n\t novel_name: \"\"\n\t };\n\t },\n\t\n\t vuex: {\n\t actions: {\n\t searchNovels: _actions.searchNovels\n\t }\n\t },\n\t methods: {\n\t search: function search() {\n\t this.searchNovels(this.novel_name);\n\t }\n\t }\n\t};\n\n/***/ }),\n/* 9 */\n/***/ (function(module, exports) {\n\n\tmodule.exports = \"
\";\n\n/***/ }),\n/* 10 */\n/***/ (function(module, exports) {\n\n\tmodule.exports = \"

网络小说推荐系统

\";\n\n/***/ }),\n/* 11 */\n/***/ (function(module, exports) {\n\n\tmodule.exports = \"
书名 作者 分类 相似度
{{ novel.name }} {{ novel.author }} {{ novel.category }} {{ convert(novel.similarity) }}
\";\n\n/***/ }),\n/* 12 */\n/***/ (function(module, exports) {\n\n\tmodule.exports = \"
\";\n\n/***/ }),\n/* 13 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\tvar __vue_script__, __vue_template__\n\tvar __vue_styles__ = {}\n\t__vue_script__ = __webpack_require__(5)\n\t__vue_template__ = __webpack_require__(9)\n\tmodule.exports = __vue_script__ || {}\n\tif (module.exports.__esModule) module.exports = module.exports.default\n\tvar __vue_options__ = typeof module.exports === \"function\" ? (module.exports.options || (module.exports.options = {})) : module.exports\n\tif (__vue_template__) {\n\t__vue_options__.template = __vue_template__\n\t}\n\tif (!__vue_options__.computed) __vue_options__.computed = {}\n\tObject.keys(__vue_styles__).forEach(function (key) {\n\tvar module = __vue_styles__[key]\n\t__vue_options__.computed[key] = function () { return module }\n\t})\n\n\n/***/ }),\n/* 14 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\tvar __vue_script__, __vue_template__\n\tvar __vue_styles__ = {}\n\t__vue_script__ = __webpack_require__(6)\n\t__vue_template__ = __webpack_require__(10)\n\tmodule.exports = __vue_script__ || {}\n\tif (module.exports.__esModule) module.exports = module.exports.default\n\tvar __vue_options__ = typeof module.exports === \"function\" ? (module.exports.options || (module.exports.options = {})) : module.exports\n\tif (__vue_template__) {\n\t__vue_options__.template = __vue_template__\n\t}\n\tif (!__vue_options__.computed) __vue_options__.computed = {}\n\tObject.keys(__vue_styles__).forEach(function (key) {\n\tvar module = __vue_styles__[key]\n\t__vue_options__.computed[key] = function () { return module }\n\t})\n\n\n/***/ }),\n/* 15 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\tvar __vue_script__, __vue_template__\n\tvar __vue_styles__ = {}\n\t__vue_script__ = __webpack_require__(7)\n\t__vue_template__ = __webpack_require__(11)\n\tmodule.exports = __vue_script__ || {}\n\tif (module.exports.__esModule) module.exports = module.exports.default\n\tvar __vue_options__ = typeof module.exports === \"function\" ? (module.exports.options || (module.exports.options = {})) : module.exports\n\tif (__vue_template__) {\n\t__vue_options__.template = __vue_template__\n\t}\n\tif (!__vue_options__.computed) __vue_options__.computed = {}\n\tObject.keys(__vue_styles__).forEach(function (key) {\n\tvar module = __vue_styles__[key]\n\t__vue_options__.computed[key] = function () { return module }\n\t})\n\n\n/***/ }),\n/* 16 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\tvar __vue_script__, __vue_template__\n\tvar __vue_styles__ = {}\n\t__vue_script__ = __webpack_require__(8)\n\t__vue_template__ = __webpack_require__(12)\n\tmodule.exports = __vue_script__ || {}\n\tif (module.exports.__esModule) module.exports = module.exports.default\n\tvar __vue_options__ = typeof module.exports === \"function\" ? (module.exports.options || (module.exports.options = {})) : module.exports\n\tif (__vue_template__) {\n\t__vue_options__.template = __vue_template__\n\t}\n\tif (!__vue_options__.computed) __vue_options__.computed = {}\n\tObject.keys(__vue_styles__).forEach(function (key) {\n\tvar module = __vue_styles__[key]\n\t__vue_options__.computed[key] = function () { return module }\n\t})\n\n\n/***/ })\n]);\n\n\n// WEBPACK FOOTER //\n// static/js/app.d2d173414305722a1a2d.js","import Vue from 'vue'\r\nimport App from './App'\r\n\r\nnew Vue({\r\n el: 'body',\r\n components: { App }\r\n});\r\n\n\n\n// WEBPACK FOOTER //\n// ./src/main.js","/**\r\n * Created by kalen on 10/14/16.\r\n */\r\n\r\nexport const searchNovels = ({ dispatch }, novel_name) => {\r\n dispatch('SEARCH_NOVELS', novel_name)\r\n};\r\n\n\n\n// WEBPACK FOOTER //\n// ./src/vuex/actions.js","/**\r\n * Created by kalen on 10/14/16.\r\n */\r\n\r\nexport function getNovels (state) {\r\n return state.novels\r\n}\r\n\n\n\n// WEBPACK FOOTER //\n// ./src/vuex/getters.js","/**\r\n * Created by kalen on 10/11/16.\r\n */\r\n\r\nimport Vue from 'vue'\r\nimport Vuex from 'vuex'\r\n\r\nVue.use(Vuex);\r\n\r\nconst state = {\r\n novels: []\r\n};\r\n\r\nconst mutations = {\r\n SEARCH_NOVELS (state, name) {\r\n let url = '/api/search/' + name;\r\n $.ajax({\r\n type: \"GET\",\r\n url: url,\r\n dataType: \"json\",\r\n success: (data) => {\r\n state.novels = data;\r\n if (data.length == 0){\r\n alert(\"未找到\" + name);\r\n }\r\n }\r\n });\r\n }\r\n};\r\n\r\n\r\nexport default new Vuex.Store({\r\n state,\r\n mutations\r\n});\r\n\n\n\n// WEBPACK FOOTER //\n// ./src/vuex/store.js","\r\n\r\n\r\n\n\n\n// WEBPACK FOOTER //\n// App.vue?2755cc5b","\r\n\r\n\r\n\n\n\n// WEBPACK FOOTER //\n// NovelList.vue?f1707bf2","\r\n\r\n\r\n\n\n\n// WEBPACK FOOTER //\n// Search.vue?4b8ffd04","module.exports = \"
\";\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/vue-html-loader!./~/vue-loader/lib/selector.js?type=template&index=0!./src/App.vue\n// module id = 9\n// module chunks = 1","module.exports = \"

网络小说推荐系统

\";\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/vue-html-loader!./~/vue-loader/lib/selector.js?type=template&index=0!./src/components/NavBar.vue\n// module id = 10\n// module chunks = 1","module.exports = \"
书名 作者 分类 相似度
{{ novel.name }} {{ novel.author }} {{ novel.category }} {{ convert(novel.similarity) }}
\";\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/vue-html-loader!./~/vue-loader/lib/selector.js?type=template&index=0!./src/components/NovelList.vue\n// module id = 11\n// module chunks = 1","module.exports = \"
\";\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/vue-html-loader!./~/vue-loader/lib/selector.js?type=template&index=0!./src/components/Search.vue\n// module id = 12\n// module chunks = 1","var __vue_script__, __vue_template__\nvar __vue_styles__ = {}\n__vue_script__ = require(\"!!babel-loader?presets[]=es2015&plugins[]=transform-runtime&comments=false!../node_modules/vue-loader/lib/selector.js?type=script&index=0!./App.vue\")\n__vue_template__ = require(\"!!vue-html-loader!../node_modules/vue-loader/lib/selector.js?type=template&index=0!./App.vue\")\nmodule.exports = __vue_script__ || {}\nif (module.exports.__esModule) module.exports = module.exports.default\nvar __vue_options__ = typeof module.exports === \"function\" ? (module.exports.options || (module.exports.options = {})) : module.exports\nif (__vue_template__) {\n__vue_options__.template = __vue_template__\n}\nif (!__vue_options__.computed) __vue_options__.computed = {}\nObject.keys(__vue_styles__).forEach(function (key) {\nvar module = __vue_styles__[key]\n__vue_options__.computed[key] = function () { return module }\n})\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/App.vue\n// module id = 13\n// module chunks = 1","var __vue_script__, __vue_template__\nvar __vue_styles__ = {}\n__vue_script__ = require(\"!!babel-loader?presets[]=es2015&plugins[]=transform-runtime&comments=false!../../node_modules/vue-loader/lib/selector.js?type=script&index=0!./NavBar.vue\")\n__vue_template__ = require(\"!!vue-html-loader!../../node_modules/vue-loader/lib/selector.js?type=template&index=0!./NavBar.vue\")\nmodule.exports = __vue_script__ || {}\nif (module.exports.__esModule) module.exports = module.exports.default\nvar __vue_options__ = typeof module.exports === \"function\" ? (module.exports.options || (module.exports.options = {})) : module.exports\nif (__vue_template__) {\n__vue_options__.template = __vue_template__\n}\nif (!__vue_options__.computed) __vue_options__.computed = {}\nObject.keys(__vue_styles__).forEach(function (key) {\nvar module = __vue_styles__[key]\n__vue_options__.computed[key] = function () { return module }\n})\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/components/NavBar.vue\n// module id = 14\n// module chunks = 1","var __vue_script__, __vue_template__\nvar __vue_styles__ = {}\n__vue_script__ = require(\"!!babel-loader?presets[]=es2015&plugins[]=transform-runtime&comments=false!../../node_modules/vue-loader/lib/selector.js?type=script&index=0!./NovelList.vue\")\n__vue_template__ = require(\"!!vue-html-loader!../../node_modules/vue-loader/lib/selector.js?type=template&index=0!./NovelList.vue\")\nmodule.exports = __vue_script__ || {}\nif (module.exports.__esModule) module.exports = module.exports.default\nvar __vue_options__ = typeof module.exports === \"function\" ? (module.exports.options || (module.exports.options = {})) : module.exports\nif (__vue_template__) {\n__vue_options__.template = __vue_template__\n}\nif (!__vue_options__.computed) __vue_options__.computed = {}\nObject.keys(__vue_styles__).forEach(function (key) {\nvar module = __vue_styles__[key]\n__vue_options__.computed[key] = function () { return module }\n})\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/components/NovelList.vue\n// module id = 15\n// module chunks = 1","var __vue_script__, __vue_template__\nvar __vue_styles__ = {}\n__vue_script__ = require(\"!!babel-loader?presets[]=es2015&plugins[]=transform-runtime&comments=false!../../node_modules/vue-loader/lib/selector.js?type=script&index=0!./Search.vue\")\n__vue_template__ = require(\"!!vue-html-loader!../../node_modules/vue-loader/lib/selector.js?type=template&index=0!./Search.vue\")\nmodule.exports = __vue_script__ || {}\nif (module.exports.__esModule) module.exports = module.exports.default\nvar __vue_options__ = typeof module.exports === \"function\" ? (module.exports.options || (module.exports.options = {})) : module.exports\nif (__vue_template__) {\n__vue_options__.template = __vue_template__\n}\nif (!__vue_options__.computed) __vue_options__.computed = {}\nObject.keys(__vue_styles__).forEach(function (key) {\nvar module = __vue_styles__[key]\n__vue_options__.computed[key] = function () { return module }\n})\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/components/Search.vue\n// module id = 16\n// module chunks = 1"],"sourceRoot":""} -------------------------------------------------------------------------------- /web_demo/dist/static/js/manifest.b6282e62578cc7cefbdf.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(r){if(a[r])return a[r].exports;var n=a[r]={exports:{},id:r,loaded:!1};return e[r].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var r=window.webpackJsonp;window.webpackJsonp=function(o,p){for(var c,l,s=0,d=[];s1?t.apply(e,arguments):t.call(e,i):t.call(e)}}function g(t,e){e=e||0;for(var i=t.length-e,n=new Array(i);i--;)n[i]=t[i+e];return n}function _(t,e){for(var i=Object.keys(e),n=i.length;n--;)t[i[n]]=e[i[n]];return t}function y(t){return null!==t&&"object"==typeof t}function b(t){return Qi.call(t)===Gi}function w(t,e,i,n){Object.defineProperty(t,e,{value:i,enumerable:!!n,writable:!0,configurable:!0})}function C(t,e){var i,n,r,s,o,a=function a(){var h=Date.now()-s;h=0?i=setTimeout(a,e-h):(i=null,o=t.apply(r,n),i||(r=n=null))};return function(){return r=this,n=arguments,s=Date.now(),i||(i=setTimeout(a,e)),o}}function $(t,e){for(var i=t.length;i--;)if(t[i]===e)return i;return-1}function k(t){var e=function e(){if(!e.cancelled)return t.apply(this,arguments)};return e.cancel=function(){e.cancelled=!0},e}function x(t,e){return t==e||!(!y(t)||!y(e))&&JSON.stringify(t)===JSON.stringify(e)}function A(t){return/native code/.test(t.toString())}function O(t){this.size=0,this.limit=t,this.head=this.tail=void 0,this._keymap=Object.create(null)}function T(){return vn.charCodeAt(_n+1)}function N(){return vn.charCodeAt(++_n)}function E(){return _n>=gn}function j(){for(;T()===jn;)N()}function S(t){return t===On||t===Tn}function F(t){return Sn[t]}function D(t,e){return Fn[t]===e}function P(){for(var t,e=N();!E();)if(t=N(),t===En)N();else if(t===e)break}function M(t){for(var e=0,i=t;!E();)if(t=T(),S(t))P();else if(i===t&&e++,D(i,t)&&e--,N(),0===e)break}function R(){for(var t=_n;!E();)if(yn=T(),S(yn))P();else if(F(yn))M(yn);else if(yn===Nn){if(N(),yn=T(),yn!==Nn){bn!==$n&&bn!==An||(bn=kn);break}N()}else{if(yn===jn&&(bn===xn||bn===An)){j();break}bn===kn&&(bn=xn),N()}return vn.slice(t+1,_n)||null}function L(){for(var t=[];!E();)t.push(H());return t}function H(){var t,e={};return bn=kn,e.name=R().trim(),bn=An,t=V(),t.length&&(e.args=t),e}function V(){for(var t=[];!E()&&bn!==kn;){var e=R();if(!e)break;t.push(I(e))}return t}function I(t){if(Cn.test(t))return{value:l(t),dynamic:!1};var e=c(t),i=e===t;return{value:i?t:e,dynamic:i}}function W(t){var e=wn.get(t);if(e)return e;vn=t,mn={},gn=vn.length,_n=-1,yn="",bn=$n;var i;return vn.indexOf("|")<0?mn.expression=vn.trim():(mn.expression=R().trim(),i=L(),i.length&&(mn.filters=i)),wn.put(t,mn),mn}function B(t){return t.replace(Pn,"\\$&")}function U(){var t=B(Bn.delimiters[0]),e=B(Bn.delimiters[1]),i=B(Bn.unsafeDelimiters[0]),n=B(Bn.unsafeDelimiters[1]);Rn=new RegExp(i+"((?:.|\\n)+?)"+n+"|"+t+"((?:.|\\n)+?)"+e,"g"),Ln=new RegExp("^"+i+"((?:.|\\n)+?)"+n+"$"),Mn=new O(1e3)}function z(t){Mn||U();var e=Mn.get(t);if(e)return e;if(!Rn.test(t))return null;for(var i,n,r,s,o,a,h=[],l=Rn.lastIndex=0;i=Rn.exec(t);)n=i.index,n>l&&h.push({value:t.slice(l,n)}),r=Ln.test(i[0]),s=r?i[1]:i[2],o=s.charCodeAt(0),a=42===o,s=a?s.slice(1):s,h.push({tag:!0,value:s.trim(),html:r,oneTime:a}),l=n+i[0].length;return l1?t.map(function(t){return q(t,e)}).join("+"):q(t[0],e,!0)}function q(t,e,i){return t.tag?t.oneTime&&e?'"'+e.$eval(t.value)+'"':Q(t.value,i):'"'+t.value+'"'}function Q(t,e){if(Hn.test(t)){var i=W(t);return i.filters?"this._applyFilters("+i.expression+",null,"+JSON.stringify(i.filters)+",false)":"("+t+")"}return e?t:"("+t+")"}function G(t,e,i,n){Y(t,1,function(){e.appendChild(t)},i,n)}function Z(t,e,i,n){Y(t,1,function(){rt(t,e)},i,n)}function X(t,e,i){Y(t,-1,function(){ot(t)},e,i)}function Y(t,e,i,n,r){var s=t.__v_trans;if(!s||!s.hooks&&!an||!n._isCompiled||n.$parent&&!n.$parent._isCompiled)return i(),void(r&&r());var o=e>0?"enter":"leave";s[o](i,r)}function K(t){if("string"==typeof t){t=document.querySelector(t)}return t}function tt(t){if(!t)return!1;var e=t.ownerDocument.documentElement,i=t.parentNode;return e===t||e===i||!(!i||1!==i.nodeType||!e.contains(i))}function et(t,e){var i=t.getAttribute(e);return null!==i&&t.removeAttribute(e),i}function it(t,e){var i=et(t,":"+e);return null===i&&(i=et(t,"v-bind:"+e)),i}function nt(t,e){return t.hasAttribute(e)||t.hasAttribute(":"+e)||t.hasAttribute("v-bind:"+e)}function rt(t,e){e.parentNode.insertBefore(t,e)}function st(t,e){e.nextSibling?rt(t,e.nextSibling):e.parentNode.appendChild(t)}function ot(t){t.parentNode.removeChild(t)}function at(t,e){e.firstChild?rt(t,e.firstChild):e.appendChild(t)}function ht(t,e){var i=t.parentNode;i&&i.replaceChild(e,t)}function lt(t,e,i,n){t.addEventListener(e,i,n)}function ut(t,e,i){t.removeEventListener(e,i)}function ct(t){var e=t.className;return"object"==typeof e&&(e=e.baseVal||""),e}function ft(t,e){nn&&!/svg$/.test(t.namespaceURI)?t.className=e:t.setAttribute("class",e)}function pt(t,e){if(t.classList)t.classList.add(e);else{var i=" "+ct(t)+" ";i.indexOf(" "+e+" ")<0&&ft(t,(i+e).trim())}}function dt(t,e){if(t.classList)t.classList.remove(e);else{for(var i=" "+ct(t)+" ",n=" "+e+" ";i.indexOf(n)>=0;)i=i.replace(n," ");ft(t,i.trim())}t.className||t.removeAttribute("class")}function vt(t,e){var i,n;if(_t(t)&&$t(t.content)&&(t=t.content),t.hasChildNodes())for(mt(t),n=e?document.createDocumentFragment():document.createElement("div");i=t.firstChild;)n.appendChild(i);return n}function mt(t){for(var e;e=t.firstChild,gt(e);)t.removeChild(e);for(;e=t.lastChild,gt(e);)t.removeChild(e)}function gt(t){return t&&(3===t.nodeType&&!t.data.trim()||8===t.nodeType)}function _t(t){return t.tagName&&"template"===t.tagName.toLowerCase()}function yt(t,e){var i=Bn.debug?document.createComment(t):document.createTextNode(e?" ":"");return i.__v_anchor=!0,i}function bt(t){if(t.hasAttributes())for(var e=t.attributes,i=0,n=e.length;i=h.length){for(var t=0;t=97&&e<=122||e>=65&&e<=90?"ident":e>=49&&e<=57?"number":"else"}function Bt(t){var e=t.trim();return("0"!==t.charAt(0)||!isNaN(t))&&(o(e)?c(e):"*"+e)}function Ut(t){function e(){var e=t[u+1];if(c===dr&&"'"===e||c===vr&&'"'===e)return u++,n="\\"+e,p[sr](),!0}var i,n,r,s,o,a,h,l=[],u=-1,c=lr,f=0,p=[];for(p[or]=function(){void 0!==r&&(l.push(r),r=void 0)},p[sr]=function(){void 0===r?r=n:r+=n},p[ar]=function(){p[sr](),f++},p[hr]=function(){if(f>0)f--,c=pr,p[sr]();else{if(f=0,r=Bt(r),r===!1)return!1;p[or]()}};null!=c;)if(u++,i=t[u],"\\"!==i||!e()){if(s=Wt(i),h=_r[c],o=h[s]||h.else||gr,o===gr)return;if(c=o[0],a=p[o[1]],a&&(n=o[2],n=void 0===n?i:n,a()===!1))return;if(c===mr)return l.raw=t,l}}function zt(t){var e=rr.get(t);return e||(e=Ut(t),e&&rr.put(t,e)),e}function Jt(t,e){return ee(e).get(t)}function qt(t,e,i){var r=t;if("string"==typeof e&&(e=Ut(e)),!e||!y(t))return!1;for(var s,o,a=0,h=e.length;a-1?i.replace(Tr,Xt):i,e+"scope."+i)}function Xt(t,e){return Sr[e]}function Yt(t){kr.test(t),Sr.length=0;var e=t.replace(Or,Gt).replace(xr,"");return e=(" "+e).replace(Er,Zt).replace(Tr,Xt),Kt(e)}function Kt(t){try{return new Function("scope","return "+t+";")}catch(t){return Qt}}function te(t){var e=zt(t);if(e)return function(t,i){qt(t,e,i)}}function ee(t,e){t=t.trim();var i=br.get(t);if(i)return e&&!i.set&&(i.set=te(i.exp)),i;var n={exp:t};return n.get=ie(t)&&t.indexOf("[")<0?Kt("scope."+t):Yt(t),e&&(n.set=te(t)),br.put(t,n),n}function ie(t){return Nr.test(t)&&!jr.test(t)&&"Math."!==t.slice(0,5)}function ne(){Dr.length=0,Pr.length=0,Mr={},Rr={},Lr=!1}function re(){for(var t=!0;t;)t=!1,se(Dr),se(Pr),Dr.length?t=!0:(Ki&&Bn.devtools&&Ki.emit("flush"),ne())}function se(t){for(var e=0;e0){var o=s+(n?e:kt(e));r=Yr.get(o),r||(r=Ze(i,t.$options,!0),Yr.put(o,r))}else r=Ze(i,t.$options,!0);this.linker=r}function Ce(t,e,i){var n=t.node.previousSibling;if(n){for(t=n.__v_frag;!(t&&t.forId===i&&t.inserted||n===e);){if(n=n.previousSibling,!n)return;t=n.__v_frag}return t}}function $e(t){for(var e=-1,i=new Array(Math.floor(t));++e47&&e<58?parseInt(t,10):1===t.length&&(e=t.toUpperCase().charCodeAt(0),e>64&&e<91)?e:ys[t]});return i=[].concat.apply([],i),function(e){if(i.indexOf(e.keyCode)>-1)return t.call(this,e)}}function Ne(t){return function(e){return e.stopPropagation(),t.call(this,e)}}function Ee(t){return function(e){return e.preventDefault(),t.call(this,e)}}function je(t){return function(e){if(e.target===e.currentTarget)return t.call(this,e)}}function Se(t){if(ks[t])return ks[t];var e=Fe(t);return ks[t]=ks[e]=e,e}function Fe(t){t=d(t);var e=f(t),i=e.charAt(0).toUpperCase()+e.slice(1);xs||(xs=document.createElement("div"));var n,r=ws.length;if("filter"!==e&&e in xs.style)return{kebab:t,camel:e};for(;r--;)if(n=Cs[r]+i,n in xs.style)return{kebab:ws[r]+t,camel:n}}function De(t){var e=[];if(Zi(t))for(var i=0,n=t.length;i=r?i():t[s].call(e,n)}var r=t.length,s=0;t[0].call(e,n)}function Re(t,e,i){for(var n,r,s,a,h,l,u,c=[],p=i.$options.propsData,v=Object.keys(e),m=v.length;m--;)if(r=v[m],n=e[r]||Is,h=f(r),Ws.test(h)){if(u={name:r,path:h,options:n,mode:Vs.ONE_WAY,raw:null},s=d(r),null===(a=it(t,s))&&(null!==(a=it(t,s+".sync"))?u.mode=Vs.TWO_WAY:null!==(a=it(t,s+".once"))&&(u.mode=Vs.ONE_TIME)),null!==a)u.raw=a,l=W(a),a=l.expression,u.filters=l.filters,o(a)&&!l.filters?u.optimizedLiteral=!0:u.dynamic=!0,u.parentPath=a;else if(null!==(a=et(t,s)))u.raw=a;else if(p&&null!==(a=p[r]||p[h]))u.raw=a;else;c.push(u)}return Le(c)}function Le(t){return function(e,i){e._props={};for(var n,r,o,a,h,f=e.$options.propsData,p=t.length;p--;)if(n=t[p],h=n.raw,r=n.path,o=n.options,e._props[r]=n,f&&s(f,r)&&Ve(e,n,f[r]),null===h)Ve(e,n,void 0);else if(n.dynamic)n.mode===Vs.ONE_TIME?(a=(i||e._context||e).$get(n.parentPath),Ve(e,n,a)):e._context?e._bindDir({name:"prop",def:Us,prop:n},null,null,i):Ve(e,n,e.$get(n.parentPath));else if(n.optimizedLiteral){var v=c(h);a=v===h?u(l(h)):v,Ve(e,n,a)}else a=o.type===Boolean&&(""===h||h===d(n.name))||h,Ve(e,n,a)}}function He(t,e,i,n){var r=e.dynamic&&ie(e.parentPath),s=i;void 0===s&&(s=We(t,e)),s=Ue(e,s,t);var o=s!==i;Be(e,s,t)||(s=void 0),r&&!o?Pt(function(){n(s)}):n(s)}function Ve(t,e,i){He(t,e,i,function(i){Vt(t,e.path,i)})}function Ie(t,e,i){He(t,e,i,function(i){t[e.path]=i})}function We(t,e){var i=e.options;if(!s(i,"default"))return i.type!==Boolean&&void 0;var n=i.default;return y(n),"function"==typeof n&&i.type!==Function?n.call(t):n}function Be(t,e,i){if(!t.options.required&&(null===t.raw||null==e))return!0;var n=t.options,r=n.type,s=!r,o=[];if(r){Zi(r)||(r=[r]);for(var a=0;ae?-1:t===e?0:1}),e=0,i=a.length;ep.priority)&&(p=f,u=r.name,a=gi(r.name),o=r.value,l=h[1],c=h[2]));return p?vi(t,l,o,i,p,u,c,a):void 0}function di(){}function vi(t,e,i,n,r,s,o,a){var h=W(i),l={name:e,arg:o,expression:h.expression,filters:h.filters,raw:i,attr:s,modifiers:a,def:r};"for"!==e&&"router-view"!==e||(l.ref=bt(t));var u=function(t,e,i,n,r){l.ref&&Vt((n||t).$refs,l.ref,null),t._bindDir(l,e,i,n,r)};return u.terminal=!0,u}function mi(t,e){function i(t,e,i){var n=i&&yi(i),r=!n&&W(s);v.push({name:t,attr:o,raw:a,def:e,arg:l,modifiers:u,expression:r&&r.expression,filters:r&&r.filters,interp:i,hasOneTime:n})}for(var n,r,s,o,a,h,l,u,c,f,p,d=t.length,v=[];d--;)if(n=t[d],r=o=n.name,s=a=n.value,f=z(s),l=null,u=gi(r),r=r.replace(so,""),f)s=J(f),l=r,i("bind",Rs.bind,f);else if(oo.test(r))u.literal=!io.test(r),i("transition",eo.transition);else if(no.test(r))l=r.replace(no,""),i("on",Rs.on);else if(io.test(r))h=r.replace(io,""),"style"===h||"class"===h?i(h,eo[h]):(l=h,i("bind",Rs.bind));else if(p=r.match(ro)){if(h=p[1],l=p[2],"else"===h)continue;c=Ft(e,"directives",h,!0),c&&i(h,c)}if(v.length)return _i(v)}function gi(t){var e=Object.create(null),i=t.match(so);if(i)for(var n=i.length;n--;)e[i[n].slice(1)]=!0;return e}function _i(t){return function(e,i,n,r,s){for(var o=t.length;o--;)e._bindDir(t[o],i,n,r,s)}}function yi(t){for(var e=t.length;e--;)if(t[e].oneTime)return!0}function bi(t){return"SCRIPT"===t.tagName&&(!t.hasAttribute("type")||"text/javascript"===t.getAttribute("type"))}function wi(t,e){return e&&(e._containerAttrs=$i(t)),_t(t)&&(t=pe(t)),e&&(e._asComponent&&!e.template&&(e.template=""),e.template&&(e._content=vt(t),t=Ci(t,e))),$t(t)&&(at(yt("v-start",!0),t),t.appendChild(yt("v-end",!0))),t}function Ci(t,e){var i=e.template,n=pe(i,!0);if(n){var r=n.firstChild;if(!r)return n;var s=r.tagName&&r.tagName.toLowerCase();return e.replace?(t===document.body,n.childNodes.length>1||1!==r.nodeType||"component"===s||Ft(e,"components",s)||nt(r,"is")||Ft(e,"elementDirectives",s)||r.hasAttribute("v-for")||r.hasAttribute("v-if")?n:(e._replacerAttrs=$i(r),ki(t,r),r)):(t.appendChild(n),t)}}function $i(t){if(1===t.nodeType&&t.hasAttributes())return g(t.attributes)}function ki(t,e){for(var i,n,r=t.attributes,s=r.length;s--;)i=r[s].name,n=r[s].value,e.hasAttribute(i)||lo.test(i)?"class"===i&&!z(n)&&(n=n.trim())&&n.split(/\s+/).forEach(function(t){pt(e,t)}):e.setAttribute(i,n)}function xi(t,e){if(e){for(var i,n,r=t._slotContents=Object.create(null),s=0,o=e.children.length;s1?g(i):i;var r=e&&i.some(function(t){return t._fromParent});r&&(n=!1);for(var s=g(arguments,1),o=0,a=i.length;oe?s:-s}var i=null,n=void 0;t=go(t);var r=g(arguments,1),s=r[r.length-1];"number"==typeof s?(s=s<0?-1:1,r=r.length>1?r.slice(0,-1):r):s=1;var o=r[0];return o?("function"==typeof o?i=function(t,e){return o(t,e)*s}:(n=Array.prototype.concat.apply([],r),i=function(t,r,s){return s=s||0,s>=n.length-1?e(t,r,s):e(t,r,s)||i(t,r,s+1)}),t.slice().sort(i)):t}function Ii(t,e){var i;if(b(t)){var n=Object.keys(t);for(i=n.length;i--;)if(Ii(t[n[i]],e))return!0}else if(Zi(t)){for(i=t.length;i--;)if(Ii(t[i],e))return!0}else if(null!=t)return t.toString().toLowerCase().indexOf(e)>-1}function Wi(t){function e(t){return new Function("return function "+v(t)+" (options) { this._init(options) }")()}t.options={directives:Rs,elementDirectives:mo,filters:yo,transitions:{},components:{},partials:{},replace:!0},t.util=ir,t.config=Bn,t.set=n,t.delete=r,t.nextTick=fn,t.compiler=uo,t.FragmentFactory=we,t.internalDirectives=eo,t.parsers={path:yr,text:Vn,template:Zr,directive:Dn,expression:Fr},t.cid=0;var i=1;t.extend=function(t){t=t||{};var n=this,r=0===n.cid;if(r&&t._Ctor)return t._Ctor;var s=t.name||n.options.name,o=e(s||"VueComponent");return o.prototype=Object.create(n.prototype),o.prototype.constructor=o,o.cid=i++,o.options=St(n.options,t),o.super=n,o.extend=n.extend,Bn._assetTypes.forEach(function(t){o[t]=n[t]}),s&&(o.options.components[s]=o),r&&(t._Ctor=o),o},t.use=function(t){if(!t.installed){var e=g(arguments,1);return e.unshift(this),"function"==typeof t.install?t.install.apply(t,e):t.apply(null,e),t.installed=!0,this}},t.mixin=function(e){t.options=St(t.options,e)},Bn._assetTypes.forEach(function(e){t[e]=function(i,n){return n?("component"===e&&b(n)&&(n.name||(n.name=i),n=t.extend(n)),this.options[e+"s"][i]=n,n):this.options[e+"s"][i]}}),_(t.transition,zn)}var Bi=Object.prototype.hasOwnProperty,Ui=/^\s?(true|false|-?[\d\.]+|'[^']*'|"[^"]*")\s?$/,zi=/-(\w)/g,Ji=/([^-])([A-Z])/g,qi=/(?:^|[-_\/])(\w)/g,Qi=Object.prototype.toString,Gi="[object Object]",Zi=Array.isArray,Xi="__proto__"in{},Yi="undefined"!=typeof window&&"[object Object]"!==Object.prototype.toString.call(window),Ki=Yi&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__,tn=Yi&&window.navigator.userAgent.toLowerCase(),en=tn&&tn.indexOf("trident")>0,nn=tn&&tn.indexOf("msie 9.0")>0,rn=tn&&tn.indexOf("android")>0,sn=tn&&/iphone|ipad|ipod|ios/.test(tn),on=void 0,an=void 0,hn=void 0,ln=void 0;if(Yi&&!nn){var un=void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend,cn=void 0===window.onanimationend&&void 0!==window.onwebkitanimationend;on=un?"WebkitTransition":"transition",an=un?"webkitTransitionEnd":"transitionend",hn=cn?"WebkitAnimation":"animation",ln=cn?"webkitAnimationEnd":"animationend"}var fn=function(){function t(){i=!1;var t=e.slice(0);e.length=0;for(var n=0;n=this.length&&(this.length=Number(t)+1),this.splice(t,1,e)[0]}),w(Yn,"$remove",function(t){if(this.length){var e=$(this,t);return e>-1?this.splice(e,1):void 0}});var tr=Object.getOwnPropertyNames(Kn),er=!0;Mt.prototype.walk=function(t){for(var e=Object.keys(t),i=0,n=e.length;i",""],tr:[2,"","
"],col:[2,"","
"]};Ur.td=Ur.th=[3,"","
"],Ur.option=Ur.optgroup=[1,'"],Ur.thead=Ur.tbody=Ur.colgroup=Ur.caption=Ur.tfoot=[1,"","
"],Ur.g=Ur.defs=Ur.symbol=Ur.use=Ur.image=Ur.text=Ur.circle=Ur.ellipse=Ur.line=Ur.path=Ur.polygon=Ur.polyline=Ur.rect=[1,'',""];var zr=/<([\w:-]+)/,Jr=/&#?\w+?;/,qr=/ 14 | 15 | 16 | -------------------------------------------------------------------------------- /web_demo/lib/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | __pycache__ -------------------------------------------------------------------------------- /web_demo/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nladuo/novelRS/6bbb687e385f1137f387547d201f0dbbee1538c0/web_demo/lib/__init__.py -------------------------------------------------------------------------------- /web_demo/lib/config.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | config = { 4 | 'timeout': 10, 5 | 'db_user': '', # mongodb的用户名 6 | 'db_pass': '', # mongodb的密码 7 | 'db_host': 'localhost', 8 | 'db_port': 27017, 9 | 'db_name': 'novelRS', 10 | 'cpu_num': 4 # 开几个进程计算 11 | } 12 | -------------------------------------------------------------------------------- /web_demo/lib/model.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | class Novel: 5 | """ 小说结构 """ 6 | def __init__(self, name, url, author, category, abstract, download_url): 7 | self.name = name 8 | self.url = url 9 | self.author = author 10 | self.category = category 11 | self.abstract = abstract 12 | self.download_url = download_url 13 | 14 | def dict(self): 15 | return { 16 | 'name': self.name, 17 | 'url': self.url, 18 | 'author': self.author, 19 | 'category': self.category, 20 | 'abstract': self.abstract, 21 | 'download_url': self.download_url, 22 | 'is_downloaded': False, # 是否下载 23 | 'success': True, # 下载是否成功, 24 | 'is_segment': False, # 是否分词 25 | } 26 | 27 | 28 | class FailedUrl: 29 | """ 失败的链接 """ 30 | def __init__(self, url): 31 | self.url = url 32 | 33 | def dict(self): 34 | return {'url': self.url} 35 | 36 | 37 | class Similarity: 38 | """ 保存两个小说之间相似度 """ 39 | def __init__(self, novel_id, similarity): 40 | self.novel_id = novel_id 41 | self.similarity = similarity 42 | 43 | def dict(self): 44 | return { 45 | 'novel_id': self.novel_id, 46 | 'similarity': self.similarity, 47 | } 48 | -------------------------------------------------------------------------------- /web_demo/lib/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pymongo 3 | import requests 4 | from .model import FailedUrl 5 | from .config import * 6 | 7 | 8 | def init_client(): 9 | """ 初始化mongo客户端 """ 10 | client = pymongo.MongoClient(config['db_host'], config['db_port']) 11 | if len(config['db_user']) != 0: 12 | admin = client['admin'] 13 | admin.authenticate(config['db_user'], config['db_pass']) 14 | return client 15 | 16 | 17 | def get_body(url): 18 | """ 发送http请求 """ 19 | retry_times = 0 20 | while retry_times < 3: 21 | try: 22 | content = requests.get(url, timeout=config['timeout']).content 23 | return content 24 | except KeyboardInterrupt: 25 | print("KeyboardInterrupt, now_url:", url) 26 | raise 27 | except: 28 | retry_times += 1 29 | return '' 30 | 31 | 32 | def add_failed_url(db, url): 33 | """ 把失败的url添加到数据库 """ 34 | collection = db.failed_urls 35 | if collection.find({'url': url}).count() == 0: 36 | collection.insert(FailedUrl(url).dict()) 37 | 38 | def read_novel(path): 39 | with open(path, "r") as f: 40 | return f.read().decode("gb2312", 'ignore') 41 | -------------------------------------------------------------------------------- /web_demo/main.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from flask import Flask, send_from_directory 3 | from bson.objectid import ObjectId 4 | import json 5 | from lib.utils import * 6 | from lib.config import * 7 | 8 | app = Flask(__name__, static_folder='dist') 9 | 10 | 11 | def get_novels(name): 12 | client = init_client() 13 | collection = client[config['db_name']].novels 14 | novel = collection.find_one({ 15 | 'name': name, 16 | 'success': True, 17 | 'is_compute': True 18 | }) 19 | if novel is None: 20 | return [] 21 | result = [] 22 | similarities = novel['similarities'] 23 | for similarity in similarities: 24 | novel = collection.find_one({ 25 | '_id': ObjectId(similarity["id"]), 26 | 'success': True, 27 | 'is_compute': True 28 | }) 29 | 30 | if novel is None: 31 | continue 32 | n = { 33 | 'name': novel['name'], 34 | 'author': novel['author'], 35 | 'category': novel['category'], 36 | 'similarity': similarity['similarity'] 37 | } 38 | result.append(n) 39 | client.close() 40 | return result 41 | 42 | 43 | @app.route('/') 44 | def index(): 45 | return app.send_static_file('index.html') 46 | 47 | 48 | @app.route('/static/') 49 | def serve_static(path): 50 | return send_from_directory('./dist/static', path) 51 | 52 | 53 | @app.route('/api/search/') 54 | def search(name=""): 55 | return json.dumps(get_novels(name)) 56 | 57 | 58 | if __name__ == '__main__': 59 | app.run(host="0.0.0.0", port=38438, debug=True) 60 | -------------------------------------------------------------------------------- /web_demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web_demo", 3 | "version": "1.0.0", 4 | "description": "Novel Recommend System WebSite Demo.", 5 | "author": "nladuo ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "build": "node build/build.js" 10 | }, 11 | "dependencies": { 12 | "babel-runtime": "^6.0.0", 13 | "vue": "^1.0.21", 14 | "vuex": "^1.0.0" 15 | }, 16 | "devDependencies": { 17 | "babel-core": "^6.0.0", 18 | "babel-loader": "^6.0.0", 19 | "babel-plugin-transform-runtime": "^6.0.0", 20 | "babel-preset-es2015": "^6.0.0", 21 | "babel-preset-stage-2": "^6.0.0", 22 | "babel-register": "^6.0.0", 23 | "connect-history-api-fallback": "^1.1.0", 24 | "css-loader": "^0.23.0", 25 | "eventsource-polyfill": "^0.9.6", 26 | "express": "^4.13.3", 27 | "extract-text-webpack-plugin": "^1.0.1", 28 | "file-loader": "^0.8.4", 29 | "function-bind": "^1.0.2", 30 | "html-webpack-plugin": "^2.8.1", 31 | "http-proxy-middleware": "^0.12.0", 32 | "json-loader": "^0.5.4", 33 | "ora": "^0.2.0", 34 | "shelljs": "^0.6.0", 35 | "url-loader": "^0.5.7", 36 | "vue-hot-reload-api": "^1.2.0", 37 | "vue-html-loader": "^1.0.0", 38 | "vue-loader": "^8.3.0", 39 | "vue-style-loader": "^1.0.0", 40 | "webpack": "^1.12.2", 41 | "webpack-dev-middleware": "^1.4.0", 42 | "webpack-hot-middleware": "^2.6.0", 43 | "webpack-merge": "^0.8.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /web_demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | -------------------------------------------------------------------------------- /web_demo/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nladuo/novelRS/6bbb687e385f1137f387547d201f0dbbee1538c0/web_demo/src/assets/logo.png -------------------------------------------------------------------------------- /web_demo/src/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /web_demo/src/components/NovelList.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 37 | -------------------------------------------------------------------------------- /web_demo/src/components/Search.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | -------------------------------------------------------------------------------- /web_demo/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | new Vue({ 5 | el: 'body', 6 | components: { App } 7 | }); 8 | -------------------------------------------------------------------------------- /web_demo/src/vuex/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kalen on 10/14/16. 3 | */ 4 | 5 | export const searchNovels = ({ dispatch }, novel_name) => { 6 | dispatch('SEARCH_NOVELS', novel_name) 7 | }; 8 | -------------------------------------------------------------------------------- /web_demo/src/vuex/getters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kalen on 10/14/16. 3 | */ 4 | 5 | export function getNovels (state) { 6 | return state.novels 7 | } 8 | -------------------------------------------------------------------------------- /web_demo/src/vuex/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kalen on 10/11/16. 3 | */ 4 | 5 | import Vue from 'vue' 6 | import Vuex from 'vuex' 7 | 8 | Vue.use(Vuex); 9 | 10 | const state = { 11 | novels: [] 12 | }; 13 | 14 | const mutations = { 15 | SEARCH_NOVELS (state, name) { 16 | let url = '/api/search/' + name; 17 | $.ajax({ 18 | type: "GET", 19 | url: url, 20 | dataType: "json", 21 | success: (data) => { 22 | state.novels = data; 23 | if (data.length == 0){ 24 | alert("未找到" + name); 25 | } 26 | } 27 | }); 28 | } 29 | }; 30 | 31 | 32 | export default new Vuex.Store({ 33 | state, 34 | mutations 35 | }); 36 | -------------------------------------------------------------------------------- /web_demo/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nladuo/novelRS/6bbb687e385f1137f387547d201f0dbbee1538c0/web_demo/static/.gitkeep --------------------------------------------------------------------------------