diff --git a/CHANGELOG.md b/CHANGELOG.md index cc910ea9..8d4cfe09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.2.4 (unreleased) +- **Documentation**: Better and much more detailed documentation. + + # 0.2.3 (2019-06-12) ### Features / Enhancement - **Docker**: User can run docker image to speed up deployment. diff --git a/gitbook/Installation/Direct.md b/gitbook/Installation/Direct.md new file mode 100644 index 00000000..c0cc3fc4 --- /dev/null +++ b/gitbook/Installation/Direct.md @@ -0,0 +1,88 @@ +## 直接部署 + +直接部署是之前没有Docker时的部署方式,相对于Docker部署来说有些繁琐。但了解如何直接部署可以帮助更深入地理解Docker是如何构建Crawlab镜像的。这里简单介绍一下。 + +### 拉取代码 + +首先是将github上的代码拉取到本地。 + +```bash +git clone https://github.com/tikazyq/crawlab +``` + +### 安装 + +安装前端所需库。 + +```bash +npm install -g yarn pm2 +cd frontend +yarn install +``` + +安装后端所需库。 + +```bash +cd ../crawlab +pip install -r requirements +``` + +### 配置 + +分别配置前端配置文件`./frontend/.env.production`和后端配置文件`./crawlab/config/config.py`。分别需要对部署后API地址以及数据库地址进行配置。 + +### 构建 + +这里的构建是指前端构建,需要执行以下命令。 + +```bash +cd ../frontend +npm run build:prod +``` + +构建完成后,会在`./frontend`目录下创建一个`dist`文件夹,里面是打包好后的静态文件。 + +### Nginx + +安装`nginx`,在`ubuntu 16.04`是以下命令。 + +```bash +sudo apt-get install nginx +``` + +添加`/etc/nginx/conf.d/crawlab.conf`文件,输入以下内容。 + +``` +server { + listen 8080; + server_name dev.crawlab.com; + root /home/yeqing/jenkins_home/workspace/crawlab_develop/frontend/dist; + index index.html; +} +``` + +其中,`root`是静态文件的根目录,这里是`npm`打包好后的静态文件。 + +现在,只需要启动`nginx`服务就完成了启动前端服务。 + +```bash +nginx reload +``` + +### 启动服务 + +这里是指启动后端服务。我们用`pm2`来管理进程。执行以下命令。 + +```bash +pm2 start app.py # API服务 +pm2 start worker.py # Worker +pm2 start flower.py # Flower +``` + +这样,`pm2`会启动3个守护进程来管理这3个服务。我们如果想看后端服务的日志的话,可以执行以下命令。 + +```bash +pm2 logs [app] +``` + +然后在浏览器中输入`http://localhost:8080`就可以看到界面了。 \ No newline at end of file diff --git a/gitbook/Installation/Docker.md b/gitbook/Installation/Docker.md new file mode 100644 index 00000000..fc4c017c --- /dev/null +++ b/gitbook/Installation/Docker.md @@ -0,0 +1,158 @@ +## Docker安装部署 + +这应该是部署应用的最方便也是最节省时间的方式了。在最近的一次版本更新[v0.2.3](https://github.com/tikazyq/crawlab/releases/tag/v0.2.3)中,我们发布了Docker功能,让大家可以利用Docker来轻松部署Crawlab。下面将一步一步介绍如何使用Docker来部署Crawlab。 + +对Docker不了解的开发者,可以参考一下这篇文章([9102 年了,学点 Docker 知识](https://juejin.im/post/5c2c69cee51d450d9707236e))做进一步了解。简单来说,Docker可以利用已存在的镜像帮助构建一些常用的服务和应用,例如Nginx、MongoDB、Redis等等。用Docker运行一个MongoDB服务仅需`docker run -d --name mongo -p 27017:27017 mongo`一行命令。如何安装Docker跟操作系统有关,这里就不展开讲了,需要的同学自行百度一下相关教程。 + +### 下载镜像 + +我们已经在[DockerHub](https://hub.docker.com/r/tikazyq/crawlab)上构建了Crawlab的镜像,开发者只需要将其pull下来使用。在pull 镜像之前,我们需要配置一下镜像源。因为我们在墙内,使用原有的镜像源速度非常感人,因此将使用DockerHub在国内的加速器。创建`/etc/docker/daemon.json`文件,在其中输入如下内容。 + +```json +{ + "registry-mirrors": ["https://registry.docker-cn.com"] +} +``` + +这样的话,pull镜像的速度会比不改变镜像源的速度快很多。 + +执行以下命令将Crawlab的镜像下载下来。镜像大小大概在几百兆,因此下载需要几分钟时间。 + +```bash +docker pull tikazyq/crawlab:latest +``` + +### 更改配置文件 + +拷贝一份后端配置文件`./crawlab/config/config.py`以及前端配置文件`./frontend/.env.production`到某一个地方。例如我的例子,分别为`/home/yeqing/config.py`和`/home/yeqing/.env.production`。 + +更改后端配置文件`config.py`,将MongoDB、Redis的指向IP更改为自己数据的值。注意,容器中对应的宿主机的IP地址不是`localhost`,而是`172.17.0.1`(当然也可以用network来做,只是稍微麻烦一些)。更改前端配置文件`.env.production`,将API地址`VUE_APP_BASE_URL`更改为宿主机所在的IP地址,例如`http://192.168.0.8:8000`,这将是前端调用API会用到的URL。 + +### 运行Docker容器 + +更改好配置文件之后,接下来就是运行容器了。执行以下命令来启动容器。 + +```bash +docker run -d --rm --name crawlab \ + -p 8080:8080 \ + -p 8000:8000 \ + -v /home/yeqing/.env.production:/opt/crawlab/frontend/.env.production \ + -v /home/yeqing/config.py:/opt/crawlab/crawlab/config/config.py \ + tikazyq/crawlab master +``` + +其中,我们映射了8080端口(Nginx前端静态文件)以及8000端口(后端API)到宿主机。另外还将前端配置文件`/home/yeqing/.env.production`和后端配置文件`/home/yeqing/config.py`映射到了容器相应的目录下。传入参数`master`是代表该启动方式为主机启动模式,也就是所有服务(前端、Api、Flower、Worker)都会启动。另外一个模式是`worker`模式,只会启动必要的Api和Worker服务,这个对于分布式部署比较有用。等待大约20-30秒的时间来build前端静态文件,之后就可以打开Crawlab界面地址地址看到界面了。界面地址默认为`http://localhost:8080`。 + + + +### Docker-Compose + +当然,也可以用`docker-compose`的方式来部署。`docker-compose`是一个集群管理方式,可以利用名为`docker-compose.yml`的`yaml`文件来定义需要启动的容器,可以是单个,也可以(通常)是多个的。Crawlab的`docker-compose.yml`定义如下。 + +```yaml +version: '3.3' +services: + master: + image: tikazyq/crawlab:latest + container_name: crawlab + volumns: + - /home/yeqing/config.py:/opt/crawlab/crawlab/config/config.py # 后端配置文件 + - /home/yeqing/.env.production:/opt/crawlab/frontend/.env.production # 前端配置文件 + ports: + - "8080:8080" # nginx + - "8000:8000" # app + depends_on: + - mongo + - redis + entrypoint: + - /bin/sh + - /opt/crawlab/docker_init.sh + - master + mongo: + image: mongo:latest + restart: always + ports: + - "27017:27017" + redis: + image: redis:latest + restart: always + ports: + - "6379:6379" +``` + +这里先定义了`master`节点,也就是Crawlab的主节点。`master`依赖于`mongo`和`redis`容器,因此在启动之前会同时启动`mongo`和`redis`容器。这样就不需要单独配置`mongo`和`redis`服务了,大大节省了环境配置的时间。 + +安装`docker-compose`也很简单,大家去网上百度一下就可以了。 + +安装完`docker-compose`和定义好`docker-compose.yml`后,只需要运行以下命令就可以启动Crawlab。 + +```bash +docker-compose up +``` + +同样,在浏览器中输入`http://localhost:8080`就可以看到界面。 + +### 多节点模式 + +`docker-compose`的方式很适合多节点部署,在原有的`master`基础上增加几个`worker`节点,达到多节点部署的目的。将`docker-compose.yml`更改为如下内容。 + +```yaml +version: '3.3' +services: + master: + image: tikazyq/crawlab:latest + container_name: crawlab + volumns: + - /home/yeqing/config.master.py:/opt/crawlab/crawlab/config/config.py # 后端配置文件 + - /home/yeqing/.env.production.master:/opt/crawlab/frontend/.env.production # 前端配置文件 + ports: + - "8080:8080" # nginx + - "8000:8000" # app + depends_on: + - mongo + - redis + entrypoint: + - /bin/sh + - /opt/crawlab/docker_init.sh + - master + worker1: + image: tikazyq/crawlab:latest + volumns: + - /home/yeqing/config.worker.py:/opt/crawlab/crawlab/config/config.py # 后端配置文件 + - /home/yeqing/.env.production.worker:/opt/crawlab/frontend/.env.production # 前端配置文件 + ports: + - "8001:8000" # app + depends_on: + - mongo + - redis + entrypoint: + - /bin/sh + - /opt/crawlab/docker_init.sh + - worker + worker2: + image: tikazyq/crawlab:latest + volumns: + - /home/yeqing/config.worker.py:/opt/crawlab/crawlab/config/config.py # 后端配置文件 + - /home/yeqing/.env.production.worker:/opt/crawlab/frontend/.env.production # 前端配置文件 + ports: + - "8002:8000" # app + depends_on: + - mongo + - redis + entrypoint: + - /bin/sh + - /opt/crawlab/docker_init.sh + - worker + mongo: + image: mongo:latest + restart: always + ports: + - "27017:27017" + redis: + image: redis:latest + restart: always + ports: + - "6379:6379" +``` + +这里启动了多增加了两个`worker`节点,以`worker`模式启动。这样,多节点部署,也就是分布式部署就完成了。 \ No newline at end of file diff --git a/gitbook/Installation/Preview.md b/gitbook/Installation/Preview.md new file mode 100644 index 00000000..b37cebd4 --- /dev/null +++ b/gitbook/Installation/Preview.md @@ -0,0 +1,9 @@ +## 预览模式 + +**预览模式**是一种让用户比较快的上手的一种部署模式。跟**直接部署**类似,但不用经过`构建`、`nginx`和`启动服务`的步骤。在启动时只需要执行以下命令就可以了。相较于直接部署来说方便一些。 + +```bash +python manage.py serve +``` + +该模式同样会启动3个后端服务和1个前端服务。前端服务是通过`npm run serve`来进行的,因此是开发者模式。**注意:强烈不建议在生产环境中用预览模式**。预览模式只是让开发者快速体验Crawlab以及调试代码问题的一种方式,而不是用作生产环境部署的。 \ No newline at end of file diff --git a/gitbook/Installation/README.md b/gitbook/Installation/README.md new file mode 100644 index 00000000..1f3bfabe --- /dev/null +++ b/gitbook/Installation/README.md @@ -0,0 +1,4 @@ +本小节将介绍三种安装Docker的方式: +1. [Docker](/Installation/Docker.md) +2. [直接部署](/Installation/Direct.md) +3. [预览模式](/Installation/Preview.md) \ No newline at end of file diff --git a/gitbook/QuickStart/Installation.md b/gitbook/QuickStart/Installation.md deleted file mode 100644 index 3fce3e1c..00000000 --- a/gitbook/QuickStart/Installation.md +++ /dev/null @@ -1,22 +0,0 @@ -# 安装 - -最快安装Crawlab的方式是克隆一份代码到本地 - -```bash -git clone https://github.com/tikazyq/crawlab -``` - -安装类库 - -```bash -# 安装后台类库 -pip install -r requirements.txt -``` - -```bash -# 安装前台类库 -cd frontend -npm install -``` - - diff --git a/gitbook/QuickStart/README.md b/gitbook/QuickStart/README.md deleted file mode 100644 index 6a6ea76f..00000000 --- a/gitbook/QuickStart/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# 快速开始 - -- [安装](Installation.md) -- [运行](Run.md) diff --git a/gitbook/QuickStart/Run.md b/gitbook/QuickStart/Run.md deleted file mode 100644 index bd6b9ba9..00000000 --- a/gitbook/QuickStart/Run.md +++ /dev/null @@ -1,53 +0,0 @@ -# 运行 - -在运行之前需要对Crawlab进行一些配置,配置文件为`config.py`。 - -```python -# project variables -PROJECT_SOURCE_FILE_FOLDER = '/Users/yeqing/projects/crawlab/spiders' # 爬虫源码根目录 -PROJECT_DEPLOY_FILE_FOLDER = '/var/crawlab' # 爬虫部署根目录 -PROJECT_LOGS_FOLDER = '/var/logs/crawlab' # 日志目录 -PROJECT_TMP_FOLDER = '/tmp' # 临时文件目录 - -# celery variables -BROKER_URL = 'redis://192.168.99.100:6379/0' # 中间者URL,连接redis -CELERY_RESULT_BACKEND = 'mongodb://192.168.99.100:27017/' # CELERY后台URL -CELERY_MONGODB_BACKEND_SETTINGS = { - 'database': 'crawlab_test', - 'taskmeta_collection': 'tasks_celery', -} -CELERY_TIMEZONE = 'Asia/Shanghai' -CELERY_ENABLE_UTC = True - -# flower variables -FLOWER_API_ENDPOINT = 'http://localhost:5555/api' # Flower服务地址 - -# database variables -MONGO_HOST = '192.168.99.100' -MONGO_PORT = 27017 -MONGO_DB = 'crawlab_test' - -# flask variables -DEBUG = True -FLASK_HOST = '127.0.0.1' -FLASK_PORT = 8000 -``` - -启动后端API,也就是一个Flask App,可以直接启动,或者用gunicorn代替。 - -```bash -python manage.py app -``` - -启动本地Worker。在其他节点中如果想只是想执行任务的话,只需要启动这一个服务就可以了。 - -```bash -python manage.py worker -``` - -启动前端服务器。 - -```bash -cd frontend -npm run serve -``` diff --git a/gitbook/README.md b/gitbook/README.md index 613decad..6112b7cc 100644 --- a/gitbook/README.md +++ b/gitbook/README.md @@ -1,167 +1,14 @@ # Crawlab 基于Celery的爬虫分布式爬虫管理平台,支持多种编程语言以及多种爬虫框架. -[查看演示 Demo](http://139.129.230.98:8080) +[查看演示 Demo](http://114.67.75.98:8080) -[English Documentation](https://github.com/tikazyq/crawlab/blob/master/README.md) +Crawlab是基于Celery的分布式爬虫管理平台,可以集成任何语言和任何框架。 -## 要求 -- Python3 -- MongoDB -- Redis +项目自今年三月份上线以来受到爬虫爱好者们和开发者们的好评,不少使用者还表示会用Crawlab搭建公司的爬虫平台。经过近3个月的迭代,我们陆续上线了定时任务、数据分析、网站信息、可配置爬虫、自动提取字段、下载结果、上传爬虫等功能,将Crawlab打造得更加实用,更加全面,能够真正帮助用户解决爬虫管理困难的问题。 -## 安装 +Crawlab主要解决的是大量爬虫管理困难的问题,例如需要监控上百个网站的参杂scrapy和selenium的项目不容易做到同时管理,而且命令行管理的成本非常高,还容易出错。Crawlab支持任何语言和任何框架,配合任务调度、任务监控,很容易做到对成规模的爬虫项目进行有效监控管理。 -```bash -# 安装后台类库 -pip install -r requirements.txt -``` +本使用手册会帮助您解决在安装使用Crawlab遇到的任何问题。 -```bash -# 安装前台类库 -cd frontend -npm install -``` - -## 配置 - -请更改配置文件`config.py`,配置API和数据库连接. - -## 快速开始 -```bash -# 启动后端API -python app.py - -# 启动Flower服务 -python ./bin/run_flower.py - -# 启动worker -python ./bin/run_worker.py -``` - -```bash -# 运行前端 -cd frontend -npm run serve -``` - -## 截图 - -#### 首页 - - -#### 爬虫列表 - - - -#### 爬虫详情 - 概览 - - - -#### 任务详情 - 抓取结果 - - - -## 架构 - -Crawlab的架构跟Celery非常相似,但是加入了包括前端、爬虫、Flower在内的额外模块,以支持爬虫管理的功能。 - - - -### 节点 - -节点其实就是Celery中的Worker。一个节点运行时会连接到一个任务队列(例如Redis)来接收和运行任务。所有爬虫需要在运行时被部署到节点上,用户在部署前需要定义节点的IP地址和端口。 - -### 爬虫 - -##### 自动发现 - -在`config.py`文件中,修改变量`PROJECT_SOURCE_FILE_FOLDER`作为爬虫项目所在的目录。Crawlab后台程序会自动发现这些爬虫项目并储存到数据库中。是不是很方便? - -##### 部署爬虫 - -所有爬虫需要在抓取前被部署当相应当节点中。在"爬虫详情"页面点击"Deploy"按钮,爬虫将被部署到所有有效到节点中。 - -##### 运行爬虫 - -部署爬虫之后,你可以在"爬虫详情"页面点击"Run"按钮来启动爬虫。一个爬虫任务将被触发,你可以在任务列表页面中看到这个任务。 - -### 任务 - -任务被触发并被节点执行。用户可以在任务详情页面中看到任务到状态、日志和抓取结果。 - -### 后台应用 - -这是一个Flask应用,提供了必要的API来支持常规操作,例如CRUD、爬虫部署以及任务运行。每一个节点需要启动Flask应用来支持爬虫部署。运行`python manage.py app`或`python ./bin/run_app.py`来启动应用。 - -### 中间者 - -中间者跟Celery中定义的一样,作为运行异步任务的队列。 - -### 前端 - -前端其实就是一个基于[Vue-Element-Admin](https://github.com/PanJiaChen/vue-element-admin)的单页应用。其中重用了很多Element-UI的控件来支持相应的展示。 - -## 与其他框架的集成 - -任务是利用python的`subprocess`模块中的`Popen`来实现的。任务ID将以环境变量`CRAWLAB_TASK_ID`的形式存在于爬虫任务运行的进程中,并以此来关联抓取数据。 - -在你的爬虫程序中,你需要将`CRAWLAB_TASK_ID`的值以`task_id`作为可以存入数据库中。这样Crawlab就直到如何将爬虫任务与抓取数据关联起来了。当前,Crawlab只支持MongoDB。 - -### Scrapy - -以下是Crawlab跟Scrapy集成的例子,利用了Crawlab传过来的task_id和collection_name。 - -```python -import os -from pymongo import MongoClient - -MONGO_HOST = '192.168.99.100' -MONGO_PORT = 27017 -MONGO_DB = 'crawlab_test' - -# scrapy example in the pipeline -class JuejinPipeline(object): - mongo = MongoClient(host=MONGO_HOST, port=MONGO_PORT) - db = mongo[MONGO_DB] - col_name = os.environ.get('CRAWLAB_COLLECTION') - if not col_name: - col_name = 'test' - col = db[col_name] - - def process_item(self, item, spider): - item['task_id'] = os.environ.get('CRAWLAB_TASK_ID') - self.col.save(item) - return item -``` - -## 与其他框架比较 - -限制以及有一些爬虫管理框架了,因此为啥还要用Crawlab? - -因为很多现有当平台都依赖于Scrapyd,限制了爬虫的编程语言以及框架,爬虫工程师只能用scrapy和python。当然,scrapy是非常优秀的爬虫框架,但是它不能做一切事情。 - -Crawlab使用起来很方便,也很通用,可以适用于几乎任何主流语言和框架。它还有一个精美的前端界面,让用户可以方便的管理和运行爬虫。 - -|框架 | 类型 | 分布式 | 前端 | 依赖于Scrapyd | -|:---:|:---:|:---:|:---:|:---:| -| [Crawlab](https://github.com/tikazyq/crawlab) | 管理平台 | Y | Y | N -| [Gerapy](https://github.com/Gerapy/Gerapy) | 管理平台 | Y | Y | Y -| [SpiderKeeper](https://github.com/DormyMo/SpiderKeeper) | 管理平台 | Y | Y | Y -| [ScrapydWeb](https://github.com/my8100/scrapydweb) | 管理平台 | Y | Y | Y -| [Scrapyd](https://github.com/scrapy/scrapyd) | 网络服务 | Y | N | N/A - -## TODOs -##### 后端 -- [ ] 文件管理 -- [ ] MySQL数据库支持 -- [ ] 重跑任务 -- [ ] 节点监控 -- [ ] 更多爬虫例子 - -##### 前端 -- [ ] 任务数据统计 -- [ ] 表格过滤 -- [x] 多语言支持 (中文) -- [ ] 登录和用户管理 -- [ ] 全局搜索 +首先,我们来看如何安装Crawlab吧,请查看[安装](/Installation/README.md)。 \ No newline at end of file diff --git a/gitbook/SUMMARY.md b/gitbook/SUMMARY.md index 9cbc8dec..ffecadff 100644 --- a/gitbook/SUMMARY.md +++ b/gitbook/SUMMARY.md @@ -1,18 +1,31 @@ # Summary -* [简介](README.md) -* [快速开始](QuickStart/README.md) - * [安装](QuickStart/Installation.md) - * [运行](QuickStart/Run.md) -* [概念](Concept/README.md) - * [节点](Concept/Node.md) - * [爬虫](Concept/Spider.md) - * [任务](Concept/Task.md) - * [部署](Concept/Deploy.md) +* [Crawlab简介](README.md) +* [安装Crawlab](Installation/README.md) + * [Docker](Installation/Docker.md) + * [直接部署](Installation/Direct.md) + * [预览模式](Installation/Preview.md) +* [使用Crawlab](Usage/README.md) + * [节点](Usage/Node/README.md) + * [查看节点列表](Usage/Node/View.md) + * [修改节点信息](Usage/Node/Edit.md) + * [爬虫](Usage/Spider/README.md) + * [创建爬虫](Usage/Spider/Create.md) + * [自定义爬虫](Usage/Spider/CustomizedSpider.md) + * [可配置爬虫](Usage/Spider/ConfigurableSpider.md) + * [部署爬虫](Usage/Spider/Deploy.md) + * [运行爬虫](Usage/Spider/Run.md) + * [统计数据](Usage/Spider/Analytics.md) + * [任务](Usage/Task/README.md) + * [查看任务](Usage/Task/View.md) + * [删除任务](Usage/Task/Delete.md) + * [下载结果](Usage/Task/DownloadResults.md) + * [定时任务](Usage/Schedule/README.md) + * [网站](Usage/Site/README.md) * [架构](Architecture/README.md) * [Celery](Architecture/Celery.md) * [App](Architecture/App.md) -* [Examples](Examples/README.md) +* [样例](Examples/README.md) * [与Scrapy集成](Examples/README.md) * [与Puppeteer集成](Examples/README.md) diff --git a/gitbook/Usage/Node/Edit.md b/gitbook/Usage/Node/Edit.md new file mode 100644 index 00000000..37e2b533 --- /dev/null +++ b/gitbook/Usage/Node/Edit.md @@ -0,0 +1,9 @@ +## 修改节点信息 + +后面我们需要让爬虫运行在各个节点上,需要让主机与节点进行通信,因此需要知道节点的IP地址和端口。我们需要手动配置一下节点的IP和端口。在`节点列表`中点击`操作`列里的蓝色查看按钮进入到节点详情。节点详情样子如下。 + + + +在右侧分别输入该节点对应的`节点IP`和`节点端口`,然后点击`保存`按钮,保存该节点信息。 + +这样,我们就完成了节点的配置工作。 \ No newline at end of file diff --git a/gitbook/Usage/Node/README.md b/gitbook/Usage/Node/README.md new file mode 100644 index 00000000..f132dcdf --- /dev/null +++ b/gitbook/Usage/Node/README.md @@ -0,0 +1,6 @@ +## 节点 + +节点其实就是Celery中的Worker。一个节点运行时会连接到一个任务队列(例如Redis)来接收和运行任务。所有爬虫需要在运行时被部署到节点上,用户在部署前需要定义节点的IP地址和端口(默认为`localhost:8000`)。 + +1. [查看节点](/Usage/Node/View.md) +2. [修改节点信息](/Usage/Node/Edit.md) diff --git a/gitbook/Usage/Node/View.md b/gitbook/Usage/Node/View.md new file mode 100644 index 00000000..86c150d8 --- /dev/null +++ b/gitbook/Usage/Node/View.md @@ -0,0 +1,5 @@ +## 查看节点列表 + +点击`侧边栏`的`节点`导航至`节点列表`,可以看到已上线的节点。这里的节点其实就是已经运行起来的`celery worker`,他们通过连接到配置好的`broker`(通常是`redis`)来进行与主机的通信。 + + diff --git a/gitbook/Usage/README.md b/gitbook/Usage/README.md new file mode 100644 index 00000000..dbfb648f --- /dev/null +++ b/gitbook/Usage/README.md @@ -0,0 +1,6 @@ +本小节将介绍如何使用Crawlab,包括如下内容: + +1. [节点](/Usage/Node/README.md) +2. [爬虫](/Usage/Spider/README.md) +3. [任务](/Usage/Task/README.md) +4. [定时任务](/Usage/Schedule/README.md) \ No newline at end of file diff --git a/gitbook/Usage/Schedule/README.md b/gitbook/Usage/Schedule/README.md new file mode 100644 index 00000000..e69de29b diff --git a/gitbook/Usage/Site/README.md b/gitbook/Usage/Site/README.md new file mode 100644 index 00000000..e69de29b diff --git a/gitbook/Usage/Spider/Analytics.md b/gitbook/Usage/Spider/Analytics.md new file mode 100644 index 00000000..066a3258 --- /dev/null +++ b/gitbook/Usage/Spider/Analytics.md @@ -0,0 +1,7 @@ +## 统计数据 + +在运行了一段时间之后,爬虫会积累一些统计数据,例如`运行成功率`、`任务数`、`运行时长`等指标。Crawlab将这些指标汇总并呈现给开发者。 + +要查看统计数据的话,只需要在`爬虫详情`中,点击`分析`标签,就可以看到爬虫的统计数据了。 + + \ No newline at end of file diff --git a/gitbook/Usage/Spider/ConfigurableSpider.md b/gitbook/Usage/Spider/ConfigurableSpider.md new file mode 100644 index 00000000..4e7c5ff2 --- /dev/null +++ b/gitbook/Usage/Spider/ConfigurableSpider.md @@ -0,0 +1,64 @@ +## 可配置爬虫 + +可配置爬虫是版本[v0.2.1](https://github.com/tikazyq/crawlab/releases/tag/v0.2.1)开发的功能。目的是将具有相似网站结构的爬虫项目可配置化,将开发爬虫的过程流程化,大大提高爬虫开发效率。 + +Crawlab的可配置爬虫是基于Scrapy的,因此天生支持并发。而且,可配置爬虫完全支持[自定义爬虫](/Usage/Spider/CustomizedSpider)的一般功能,因此也支持任务调度、任务监控、日志监控、数据分析。 + +### 添加爬虫 + +在`侧边栏`点击`爬虫`导航至`爬虫列表`,点击**添加爬虫**按钮。 + + + +点击**可配置爬虫**。 + + + +输入完基本信息,点击**添加**。 + + + +### 配置爬虫 + +添加完成后,可以看到刚刚添加的可配置爬虫出现了在最下方,点击**查看**进入到**爬虫详情**。 + + + +点击**配置**标签进入到配置页面。接下来,我们需要对爬虫规则进行配置。 + + + +这里已经有一些配置好的初始输入项。我们简单介绍一下各自的含义。 + +#### 抓取类别 + +这也是爬虫抓取采用的策略,也就是爬虫遍历网页是如何进行的。作为第一个版本,我们有**仅列表**、**仅详情页**、**列表+详情页**。 +- 仅列表页。这也是最简单的形式,爬虫遍历列表上的列表项,将数据抓取下来。 +- 仅详情页。爬虫只抓取详情页。 +- 列表+详情页。爬虫先遍历列表页,将列表项中的详情页地址提取出来并跟进抓取详情页。 + +这里我们选择**列表+详情页**。 + +#### 列表项选择器 & 分页选择器 + +列表项的匹和分页按钮的匹配查询,由CSS或XPath来进行匹配。 + +#### 开始URL + +爬虫最开始遍历的网址。 + +#### 遵守Robots协议 + +这个默认是开启的。如果开启,爬虫将先抓取网站的robots.txt并判断页面是否可抓;否则,不会对此进行验证。用户可以选择将其关闭。请注意,任何无视Robots协议的行为都有法律风险。 + +#### 列表页字段 & 详情页字段 + +这些都是再列表页或详情页中需要提取的字段。字段由CSS选择器或者XPath来匹配提取。可以选择文本或者属性。 + +在检查完目标网页的元素CSS选择器之后,我们输入列表项选择器、开始URL、列表页/详情页等信息。注意勾选url为详情页URL。 + + + +点击保存、预览,查看预览内容。 + + diff --git a/gitbook/Usage/Spider/Create.md b/gitbook/Usage/Spider/Create.md new file mode 100644 index 00000000..1b934523 --- /dev/null +++ b/gitbook/Usage/Spider/Create.md @@ -0,0 +1,7 @@ +## 创建爬虫 + +Crawlab允许用户创建两种爬虫: +1. [自定义爬虫](/Usage/Spider/CustomizedSpider.md) +2. [可配置爬虫](/Usage/Spider/ConfigurableSpider.md) + +前者可以通过Web界面和创建项目目录的方式来添加,后者由于没有源代码,只能通过Web界面来添加。 diff --git a/gitbook/Usage/Spider/CustomizedSpider.md b/gitbook/Usage/Spider/CustomizedSpider.md new file mode 100644 index 00000000..0115ab80 --- /dev/null +++ b/gitbook/Usage/Spider/CustomizedSpider.md @@ -0,0 +1,31 @@ +## 自定义爬虫 + +自定义爬虫是指用户可以添加的任何语言任何框架的爬虫,高度自定义化。当用户添加好自定义爬虫之后,Crawlab就可以将其集成到爬虫管理的系统中来。 + +自定义爬虫的添加有两种方式: +1. 通过Web界面上传爬虫 +2. 通过创建项目目录 + +### 通过Web界面上传 + +在通过Web界面上传之前,需要将爬虫项目文件打包成`zip`格式。 + + + +然后,在`侧边栏`点击`爬虫`导航至`爬虫列表`,点击`添加爬虫`按钮,选择`自定义爬虫`,点击`上传`按钮,选择刚刚打包好的`zip`文件。上传成功后,`爬虫列表`中会出现新添加的自定义爬虫。这样就算添加好了。 + +这个方式稍微有些繁琐,但是对于无法轻松获取服务器的读写权限时是非常有用的,适合在生产环境上使用。 + +### 通过添加项目目录 + +Crawlab会自动发现`PROJECT_SOURCE_FILE_FOLDER`目录下的所有爬虫目录,并将这些目录生成自定义爬虫并集成到Crawlab中。因此,将爬虫项目目录拷贝到`PROJECT_SOURCE_FILE_FOLDER`目录下,就可以添加一个爬虫了。 + +这种方式非常方便,但是需要获得主机服务器的读写权限,因而比较适合在开发环境上采用。 + +### 配置爬虫 + +在定义爬虫中,我们需要配置一下`执行命令`(运行爬虫时后台执行的`shell`命令)和`结果集`(通过`CRAWLAB_COLLECTION`传递给爬虫程序,爬虫程序存储结果的地方),然后点击`保存`按钮保存爬虫信息。 + + + +接下来,我们就可以部署、运行自定义爬虫了。 diff --git a/gitbook/Usage/Spider/Deploy.md b/gitbook/Usage/Spider/Deploy.md new file mode 100644 index 00000000..a7f46130 --- /dev/null +++ b/gitbook/Usage/Spider/Deploy.md @@ -0,0 +1,10 @@ +## 部署爬虫 + +这里的爬虫部署是指[自定义爬虫](/Usage/Spider/CustomizedSpider)的部署,因为[可配置爬虫](/Usage/Spider/ConfigurableSpider)已经内嵌到Crawlab中了,所有节点都可以使用,不需要额外部署。简单来说,就是将主机上的爬虫源代码通过`HTTP`的方式打包传输至`worker`节点上,因此节点就可以运行传输过来的爬虫了。 + +部署爬虫很简单,有三种方式: +1. 在`爬虫列表`中点击`部署所有爬虫`,将所有爬虫部署到所有在线节点中; +2. 在`爬虫列表`中点击`操作`列的`部署`按钮,将指定爬虫部署到所有在线节点中; +3. 在`爬虫详情`的`概览`标签中,点击`部署`按钮,将指定爬虫部署到所有在线节点中。 + +部署好之后,我们就可以运行爬虫了。 diff --git a/gitbook/Usage/Spider/README.md b/gitbook/Usage/Spider/README.md new file mode 100644 index 00000000..c346fdd0 --- /dev/null +++ b/gitbook/Usage/Spider/README.md @@ -0,0 +1,9 @@ +## 爬虫 + +爬虫就是我们通常说的网络爬虫了,本小节将介绍如下内容: + +1. [创建爬虫](/Usage/Spider/Create.md) +2. [部署爬虫](/Usage/Spider/Deploy.md) +3. [运行爬虫](/Usage/Spider/Run.md) +4. [可配置爬虫](/Usage/Spider/ConfigurableSpider.md) +5. [统计数据](/Usage/Spider/Analytics.md) \ No newline at end of file diff --git a/gitbook/Usage/Spider/Run.md b/gitbook/Usage/Spider/Run.md new file mode 100644 index 00000000..83ede39f --- /dev/null +++ b/gitbook/Usage/Spider/Run.md @@ -0,0 +1,17 @@ +## 运行爬虫 + +我们有两种运行爬虫的方式: +1. 手动触发 +2. 定时任务触发 + +### 手动触发 + +1. 在`爬虫列表`中`操作`列点击`运行`按钮,或者 +2. 在`爬虫详情`中`概览`标签下点击`运行`按钮,或者 +3. 对于`自定义爬虫`,可以在`配置`标签下点击`运行`按钮 + +然后,Crawlab会提示任务已经派发到队列中去了,然后你可以在`爬虫详情`左侧看到新创建的任务。点击`创建时间`可以导航至`任务详情`。 + +### 定时任务触发 + +`定时任务触发`是比较常用的功能,对于`增量抓取`或对实时性有要求的任务很重要。这在[定时任务](/Usage/Schedule/README.md)中会详细介绍。 \ No newline at end of file diff --git a/gitbook/Usage/Task/README.md b/gitbook/Usage/Task/README.md new file mode 100644 index 00000000..e69de29b diff --git a/gitbook/_book/Architecture/App.html b/gitbook/_book/Architecture/App.html new file mode 100644 index 00000000..1a86914f --- /dev/null +++ b/gitbook/_book/Architecture/App.html @@ -0,0 +1,629 @@ + + + +
+ + +直接部署是之前没有Docker时的部署方式,相对于Docker部署来说有些繁琐。但了解如何直接部署可以帮助更深入地理解Docker是如何构建Crawlab镜像的。这里简单介绍一下。
+首先是将github上的代码拉取到本地。
+git clone https://github.com/tikazyq/crawlab
+
+安装前端所需库。
+npm install -g yarn pm2
+cd frontend
+yarn install
+
+安装后端所需库。
+cd ../crawlab
+pip install -r requirements
+
+分别配置前端配置文件./frontend/.env.production和后端配置文件./crawlab/config/config.py。分别需要对部署后API地址以及数据库地址进行配置。
这里的构建是指前端构建,需要执行以下命令。
+cd ../frontend
+npm run build:prod
+
+构建完成后,会在./frontend目录下创建一个dist文件夹,里面是打包好后的静态文件。
安装nginx,在ubuntu 16.04是以下命令。
sudo apt-get install nginx
+
+添加/etc/nginx/conf.d/crawlab.conf文件,输入以下内容。
server {
+ listen 8080;
+ server_name dev.crawlab.com;
+ root /home/yeqing/jenkins_home/workspace/crawlab_develop/frontend/dist;
+ index index.html;
+}
+其中,root是静态文件的根目录,这里是npm打包好后的静态文件。
现在,只需要启动nginx服务就完成了启动前端服务。
nginx reload
+
+这里是指启动后端服务。我们用pm2来管理进程。执行以下命令。
pm2 start app.py # API服务
+pm2 start worker.py # Worker
+pm2 start flower.py # Flower
+
+这样,pm2会启动3个守护进程来管理这3个服务。我们如果想看后端服务的日志的话,可以执行以下命令。
pm2 logs [app]
+
+然后在浏览器中输入http://localhost:8080就可以看到界面了。
这应该是部署应用的最方便也是最节省时间的方式了。在最近的一次版本更新v0.2.3中,我们发布了Docker功能,让大家可以利用Docker来轻松部署Crawlab。下面将一步一步介绍如何使用Docker来部署Crawlab。
+对Docker不了解的开发者,可以参考一下这篇文章(9102 年了,学点 Docker 知识)做进一步了解。简单来说,Docker可以利用已存在的镜像帮助构建一些常用的服务和应用,例如Nginx、MongoDB、Redis等等。用Docker运行一个MongoDB服务仅需docker run -d --name mongo -p 27017:27017 mongo一行命令。如何安装Docker跟操作系统有关,这里就不展开讲了,需要的同学自行百度一下相关教程。
我们已经在DockerHub上构建了Crawlab的镜像,开发者只需要将其pull下来使用。在pull 镜像之前,我们需要配置一下镜像源。因为我们在墙内,使用原有的镜像源速度非常感人,因此将使用DockerHub在国内的加速器。创建/etc/docker/daemon.json文件,在其中输入如下内容。
{
+ "registry-mirrors": ["https://registry.docker-cn.com"]
+}
+
+这样的话,pull镜像的速度会比不改变镜像源的速度快很多。
+执行以下命令将Crawlab的镜像下载下来。镜像大小大概在几百兆,因此下载需要几分钟时间。
+docker pull tikazyq/crawlab:latest
+
+拷贝一份后端配置文件./crawlab/config/config.py以及前端配置文件./frontend/.env.production到某一个地方。例如我的例子,分别为/home/yeqing/config.py和/home/yeqing/.env.production。
更改后端配置文件config.py,将MongoDB、Redis的指向IP更改为自己数据的值。注意,容器中对应的宿主机的IP地址不是localhost,而是172.17.0.1(当然也可以用network来做,只是稍微麻烦一些)。更改前端配置文件.env.production,将API地址VUE_APP_BASE_URL更改为宿主机所在的IP地址,例如http://192.168.0.8:8000,这将是前端调用API会用到的URL。
更改好配置文件之后,接下来就是运行容器了。执行以下命令来启动容器。
+docker run -d --rm --name crawlab \
+ -p 8080:8080 \
+ -p 8000:8000 \
+ -v /home/yeqing/.env.production:/opt/crawlab/frontend/.env.production \
+ -v /home/yeqing/config.py:/opt/crawlab/crawlab/config/config.py \
+ tikazyq/crawlab master
+
+其中,我们映射了8080端口(Nginx前端静态文件)以及8000端口(后端API)到宿主机。另外还将前端配置文件/home/yeqing/.env.production和后端配置文件/home/yeqing/config.py映射到了容器相应的目录下。传入参数master是代表该启动方式为主机启动模式,也就是所有服务(前端、Api、Flower、Worker)都会启动。另外一个模式是worker模式,只会启动必要的Api和Worker服务,这个对于分布式部署比较有用。等待大约20-30秒的时间来build前端静态文件,之后就可以打开Crawlab界面地址地址看到界面了。界面地址默认为http://localhost:8080。
当然,也可以用docker-compose的方式来部署。docker-compose是一个集群管理方式,可以利用名为docker-compose.yml的yaml文件来定义需要启动的容器,可以是单个,也可以(通常)是多个的。Crawlab的docker-compose.yml定义如下。
version: '3.3'
+services:
+ master:
+ image: tikazyq/crawlab:latest
+ container_name: crawlab
+ volumns:
+ - /home/yeqing/config.py:/opt/crawlab/crawlab/config/config.py # 后端配置文件
+ - /home/yeqing/.env.production:/opt/crawlab/frontend/.env.production # 前端配置文件
+ ports:
+ - "8080:8080" # nginx
+ - "8000:8000" # app
+ depends_on:
+ - mongo
+ - redis
+ entrypoint:
+ - /bin/sh
+ - /opt/crawlab/docker_init.sh
+ - master
+ mongo:
+ image: mongo:latest
+ restart: always
+ ports:
+ - "27017:27017"
+ redis:
+ image: redis:latest
+ restart: always
+ ports:
+ - "6379:6379"
+
+这里先定义了master节点,也就是Crawlab的主节点。master依赖于mongo和redis容器,因此在启动之前会同时启动mongo和redis容器。这样就不需要单独配置mongo和redis服务了,大大节省了环境配置的时间。
安装docker-compose也很简单,大家去网上百度一下就可以了。
安装完docker-compose和定义好docker-compose.yml后,只需要运行以下命令就可以启动Crawlab。
docker-compose up
+
+同样,在浏览器中输入http://localhost:8080就可以看到界面。
docker-compose的方式很适合多节点部署,在原有的master基础上增加几个worker节点,达到多节点部署的目的。将docker-compose.yml更改为如下内容。
version: '3.3'
+services:
+ master:
+ image: tikazyq/crawlab:latest
+ container_name: crawlab
+ volumns:
+ - /home/yeqing/config.master.py:/opt/crawlab/crawlab/config/config.py # 后端配置文件
+ - /home/yeqing/.env.production.master:/opt/crawlab/frontend/.env.production # 前端配置文件
+ ports:
+ - "8080:8080" # nginx
+ - "8000:8000" # app
+ depends_on:
+ - mongo
+ - redis
+ entrypoint:
+ - /bin/sh
+ - /opt/crawlab/docker_init.sh
+ - master
+ worker1:
+ image: tikazyq/crawlab:latest
+ volumns:
+ - /home/yeqing/config.worker.py:/opt/crawlab/crawlab/config/config.py # 后端配置文件
+ - /home/yeqing/.env.production.worker:/opt/crawlab/frontend/.env.production # 前端配置文件
+ ports:
+ - "8001:8000" # app
+ depends_on:
+ - mongo
+ - redis
+ entrypoint:
+ - /bin/sh
+ - /opt/crawlab/docker_init.sh
+ - worker
+ worker2:
+ image: tikazyq/crawlab:latest
+ volumns:
+ - /home/yeqing/config.worker.py:/opt/crawlab/crawlab/config/config.py # 后端配置文件
+ - /home/yeqing/.env.production.worker:/opt/crawlab/frontend/.env.production # 前端配置文件
+ ports:
+ - "8002:8000" # app
+ depends_on:
+ - mongo
+ - redis
+ entrypoint:
+ - /bin/sh
+ - /opt/crawlab/docker_init.sh
+ - worker
+ mongo:
+ image: mongo:latest
+ restart: always
+ ports:
+ - "27017:27017"
+ redis:
+ image: redis:latest
+ restart: always
+ ports:
+ - "6379:6379"
+
+这里启动了多增加了两个worker节点,以worker模式启动。这样,多节点部署,也就是分布式部署就完成了。
预览模式是一种让用户比较快的上手的一种部署模式。跟直接部署类似,但不用经过构建、nginx和启动服务的步骤。在启动时只需要执行以下命令就可以了。相较于直接部署来说方便一些。
python manage.py serve
+
+该模式同样会启动3个后端服务和1个前端服务。前端服务是通过npm run serve来进行的,因此是开发者模式。注意:强烈不建议在生产环境中用预览模式。预览模式只是让开发者快速体验Crawlab以及调试代码问题的一种方式,而不是用作生产环境部署的。
后面我们需要让爬虫运行在各个节点上,需要让主机与节点进行通信,因此需要知道节点的IP地址和端口。我们需要手动配置一下节点的IP和端口。在节点列表中点击操作列里的蓝色查看按钮进入到节点详情。节点详情样子如下。

在右侧分别输入该节点对应的节点IP和节点端口,然后点击保存按钮,保存该节点信息。
这样,我们就完成了节点的配置工作。
+ + +可配置爬虫是版本v0.2.1开发的功能。目的是将具有相似网站结构的爬虫项目可配置化,将开发爬虫的过程流程化,大大提高爬虫开发效率。
+Crawlab的可配置爬虫是基于Scrapy的,因此天生支持并发。而且,可配置爬虫完全支持自定义爬虫的一般功能,因此也支持任务调度、任务监控、日志监控、数据分析。
+在侧边栏点击爬虫导航至爬虫列表,点击添加爬虫按钮。
点击可配置爬虫。
+输入完基本信息,点击添加。
+添加完成后,可以看到刚刚添加的可配置爬虫出现了在最下方,点击查看进入到爬虫详情。
+点击配置标签进入到配置页面。接下来,我们需要对爬虫规则进行配置。
+这里已经有一些配置好的初始输入项。我们简单介绍一下各自的含义。
+这也是爬虫抓取采用的策略,也就是爬虫遍历网页是如何进行的。作为第一个版本,我们有仅列表、仅详情页、列表+详情页。
+这里我们选择列表+详情页。
+列表项的匹和分页按钮的匹配查询,由CSS或XPath来进行匹配。
+爬虫最开始遍历的网址。
+这个默认是开启的。如果开启,爬虫将先抓取网站的robots.txt并判断页面是否可抓;否则,不会对此进行验证。用户可以选择将其关闭。请注意,任何无视Robots协议的行为都有法律风险。
+这些都是再列表页或详情页中需要提取的字段。字段由CSS选择器或者XPath来匹配提取。可以选择文本或者属性。
+在检查完目标网页的元素CSS选择器之后,我们输入列表项选择器、开始URL、列表页/详情页等信息。注意勾选url为详情页URL。
+点击保存、预览,查看预览内容。
+自定义爬虫是指用户可以添加的任何语言任何框架的爬虫,高度自定义化。当用户添加好自定义爬虫之后,Crawlab就可以将其集成到爬虫管理的系统中来。
+自定义爬虫的添加有两种方式:
+在通过Web界面上传之前,需要将爬虫项目文件打包成zip格式。

然后,在侧边栏点击爬虫导航至爬虫列表,点击添加爬虫按钮,选择自定义爬虫,点击上传按钮,选择刚刚打包好的zip文件。上传成功后,爬虫列表中会出现新添加的自定义爬虫。这样就算添加好了。
这个方式稍微有些繁琐,但是对于无法轻松获取服务器的读写权限时是非常有用的,适合在生产环境上使用。
+Crawlab会自动发现PROJECT_SOURCE_FILE_FOLDER目录下的所有爬虫目录,并将这些目录生成自定义爬虫并集成到Crawlab中。因此,将爬虫项目目录拷贝到PROJECT_SOURCE_FILE_FOLDER目录下,就可以添加一个爬虫了。
这种方式非常方便,但是需要获得主机服务器的读写权限,因而比较适合在开发环境上采用。
+在定义爬虫中,我们需要配置一下执行命令(运行爬虫时后台执行的shell命令)和结果集(通过CRAWLAB_COLLECTION传递给爬虫程序,爬虫程序存储结果的地方),然后点击保存按钮保存爬虫信息。

接下来,我们就可以部署、运行自定义爬虫了。
+ + +这里的爬虫部署是指自定义爬虫的部署,因为可配置爬虫已经内嵌到Crawlab中了,所有节点都可以使用,不需要额外部署。简单来说,就是将主机上的爬虫源代码通过HTTP的方式打包传输至worker节点上,因此节点就可以运行传输过来的爬虫了。
部署爬虫很简单,有三种方式:
+爬虫列表中点击部署所有爬虫,将所有爬虫部署到所有在线节点中;爬虫列表中点击操作列的部署按钮,将指定爬虫部署到所有在线节点中;爬虫详情的概览标签中,点击部署按钮,将指定爬虫部署到所有在线节点中。部署好之后,我们就可以运行爬虫了。
+ + +我们有两种运行爬虫的方式:
+爬虫列表中操作列点击运行按钮,或者爬虫详情中概览标签下点击运行按钮,或者自定义爬虫,可以在配置标签下点击运行按钮然后,Crawlab会提示任务已经派发到队列中去了,然后你可以在爬虫详情左侧看到新创建的任务。点击创建时间可以导航至任务详情。
定时任务触发是比较常用的功能,对于增量抓取或对实时性有要求的任务很重要。这在定时任务中会详细介绍。
').html(content);
+
+ $link.appendTo($title);
+ $title.appendTo($li);
+ $content.appendTo($li);
+ $li.appendTo($searchList);
+ });
+ }
+
+ function launchSearch(q) {
+ // Add class for loading
+ $body.addClass('with-search');
+ $body.addClass('search-loading');
+
+ // Launch search query
+ throttle(gitbook.search.query(q, 0, MAX_RESULTS)
+ .then(function(results) {
+ displayResults(results);
+ })
+ .always(function() {
+ $body.removeClass('search-loading');
+ }), 1000);
+ }
+
+ function closeSearch() {
+ $body.removeClass('with-search');
+ $bookSearchResults.removeClass('open');
+ }
+
+ function launchSearchFromQueryString() {
+ var q = getParameterByName('q');
+ if (q && q.length > 0) {
+ // Update search input
+ $searchInput.val(q);
+
+ // Launch search
+ launchSearch(q);
+ }
+ }
+
+ function bindSearch() {
+ // Bind DOM
+ $searchInput = $('#book-search-input input');
+ $bookSearchResults = $('#book-search-results');
+ $searchList = $bookSearchResults.find('.search-results-list');
+ $searchTitle = $bookSearchResults.find('.search-results-title');
+ $searchResultsCount = $searchTitle.find('.search-results-count');
+ $searchQuery = $searchTitle.find('.search-query');
+
+ // Launch query based on input content
+ function handleUpdate() {
+ var q = $searchInput.val();
+
+ if (q.length == 0) {
+ closeSearch();
+ }
+ else {
+ launchSearch(q);
+ }
+ }
+
+ // Detect true content change in search input
+ // Workaround for IE < 9
+ var propertyChangeUnbound = false;
+ $searchInput.on('propertychange', function(e) {
+ if (e.originalEvent.propertyName == 'value') {
+ handleUpdate();
+ }
+ });
+
+ // HTML5 (IE9 & others)
+ $searchInput.on('input', function(e) {
+ // Unbind propertychange event for IE9+
+ if (!propertyChangeUnbound) {
+ $(this).unbind('propertychange');
+ propertyChangeUnbound = true;
+ }
+
+ handleUpdate();
+ });
+
+ // Push to history on blur
+ $searchInput.on('blur', function(e) {
+ // Update history state
+ if (usePushState) {
+ var uri = updateQueryString('q', $(this).val());
+ history.pushState({ path: uri }, null, uri);
+ }
+ });
+ }
+
+ gitbook.events.on('page.change', function() {
+ bindSearch();
+ closeSearch();
+
+ // Launch search based on query parameter
+ if (gitbook.search.isInitialized()) {
+ launchSearchFromQueryString();
+ }
+ });
+
+ gitbook.events.on('search.ready', function() {
+ bindSearch();
+
+ // Launch search from query param at start
+ launchSearchFromQueryString();
+ });
+
+ function getParameterByName(name) {
+ var url = window.location.href;
+ name = name.replace(/[\[\]]/g, '\\$&');
+ var regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)', 'i'),
+ results = regex.exec(url);
+ if (!results) return null;
+ if (!results[2]) return '';
+ return decodeURIComponent(results[2].replace(/\+/g, ' '));
+ }
+
+ function updateQueryString(key, value) {
+ value = encodeURIComponent(value);
+
+ var url = window.location.href;
+ var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'),
+ hash;
+
+ if (re.test(url)) {
+ if (typeof value !== 'undefined' && value !== null)
+ return url.replace(re, '$1' + key + '=' + value + '$2$3');
+ else {
+ hash = url.split('#');
+ url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '');
+ if (typeof hash[1] !== 'undefined' && hash[1] !== null)
+ url += '#' + hash[1];
+ return url;
+ }
+ }
+ else {
+ if (typeof value !== 'undefined' && value !== null) {
+ var separator = url.indexOf('?') !== -1 ? '&' : '?';
+ hash = url.split('#');
+ url = hash[0] + separator + key + '=' + value;
+ if (typeof hash[1] !== 'undefined' && hash[1] !== null)
+ url += '#' + hash[1];
+ return url;
+ }
+ else
+ return url;
+ }
+ }
+});
diff --git a/gitbook/_book/gitbook/gitbook-plugin-sharing/buttons.js b/gitbook/_book/gitbook/gitbook-plugin-sharing/buttons.js
new file mode 100644
index 00000000..709a4e4c
--- /dev/null
+++ b/gitbook/_book/gitbook/gitbook-plugin-sharing/buttons.js
@@ -0,0 +1,90 @@
+require(['gitbook', 'jquery'], function(gitbook, $) {
+ var SITES = {
+ 'facebook': {
+ 'label': 'Facebook',
+ 'icon': 'fa fa-facebook',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href));
+ }
+ },
+ 'twitter': {
+ 'label': 'Twitter',
+ 'icon': 'fa fa-twitter',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href));
+ }
+ },
+ 'google': {
+ 'label': 'Google+',
+ 'icon': 'fa fa-google-plus',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href));
+ }
+ },
+ 'weibo': {
+ 'label': 'Weibo',
+ 'icon': 'fa fa-weibo',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title));
+ }
+ },
+ 'instapaper': {
+ 'label': 'Instapaper',
+ 'icon': 'fa fa-instapaper',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href));
+ }
+ },
+ 'vk': {
+ 'label': 'VK',
+ 'icon': 'fa fa-vk',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href));
+ }
+ }
+ };
+
+
+
+ gitbook.events.bind('start', function(e, config) {
+ var opts = config.sharing;
+
+ // Create dropdown menu
+ var menu = $.map(opts.all, function(id) {
+ var site = SITES[id];
+
+ return {
+ text: site.label,
+ onClick: site.onClick
+ };
+ });
+
+ // Create main button with dropdown
+ if (menu.length > 0) {
+ gitbook.toolbar.createButton({
+ icon: 'fa fa-share-alt',
+ label: 'Share',
+ position: 'right',
+ dropdown: [menu]
+ });
+ }
+
+ // Direct actions to share
+ $.each(SITES, function(sideId, site) {
+ if (!opts[sideId]) return;
+
+ gitbook.toolbar.createButton({
+ icon: site.icon,
+ label: site.text,
+ position: 'right',
+ onClick: site.onClick
+ });
+ });
+ });
+});
diff --git a/gitbook/_book/gitbook/gitbook.js b/gitbook/_book/gitbook/gitbook.js
new file mode 100644
index 00000000..13077b45
--- /dev/null
+++ b/gitbook/_book/gitbook/gitbook.js
@@ -0,0 +1,4 @@
+!function e(t,n,r){function o(s,a){if(!n[s]){if(!t[s]){var u="function"==typeof require&&require;if(!a&&u)return u(s,!0);if(i)return i(s,!0);var c=new Error("Cannot find module '"+s+"'");throw c.code="MODULE_NOT_FOUND",c}var l=n[s]={exports:{}};t[s][0].call(l.exports,function(e){var n=t[s][1][e];return o(n?n:e)},l,l.exports,e,t,n,r)}return n[s].exports}for(var i="function"==typeof require&&require,s=0;s =0&&n-1)o&&o.push(i);else if(c=de.contains(i.ownerDocument,i),s=v(f.appendChild(i),"script"),c&&y(s),n)for(l=0;i=s[l++];)Ve.test(i.type||"")&&n.push(i);return f}function b(){return!0}function w(){return!1}function T(){try{return te.activeElement}catch(e){}}function C(e,t,n,r,o,i){var s,a;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(a in t)C(e,a,n,r,t[a],i);return e}if(null==r&&null==o?(o=n,r=n=void 0):null==o&&("string"==typeof n?(o=r,r=void 0):(o=r,r=n,n=void 0)),o===!1)o=w;else if(!o)return e;return 1===i&&(s=o,o=function(e){return de().off(e),s.apply(this,arguments)},o.guid=s.guid||(s.guid=de.guid++)),e.each(function(){de.event.add(this,t,o,r,n)})}function j(e,t){return de.nodeName(e,"table")&&de.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e:e}function k(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function E(e){var t=rt.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function S(e,t){var n,r,o,i,s,a,u,c;if(1===t.nodeType){if(Fe.hasData(e)&&(i=Fe.access(e),s=Fe.set(t,i),c=i.events)){delete s.handle,s.events={};for(o in c)for(n=0,r=c[o].length;n=s&&(r!==u&&(c=void 0,l=[e]),n.rejectWith(c,l))}};t?p():(de.Deferred.getStackHook&&(p.stackTrace=de.Deferred.getStackHook()),e.setTimeout(p))}}var s=0;return de.Deferred(function(e){n[0][3].add(i(0,e,de.isFunction(o)?o:a,e.notifyWith)),n[1][3].add(i(0,e,de.isFunction(t)?t:a)),n[2][3].add(i(0,e,de.isFunction(r)?r:u))}).promise()},promise:function(e){return null!=e?de.extend(e,o):o}},i={};return de.each(n,function(e,t){var s=t[2],a=t[5];o[t[1]]=s.add,a&&s.add(function(){r=a},n[3-e][2].disable,n[0][2].lock),s.add(t[3].fire),i[t[0]]=function(){return i[t[0]+"With"](this===i?void 0:this,arguments),this},i[t[0]+"With"]=s.fireWith}),o.promise(i),t&&t.call(i,i),i},when:function(e){var t=arguments.length,n=t,r=Array(n),o=re.call(arguments),i=de.Deferred(),s=function(e){return function(n){r[e]=this,o[e]=arguments.length>1?re.call(arguments):n,--t||i.resolveWith(r,o)}};if(t<=1&&(c(e,i.done(s(n)).resolve,i.reject),"pending"===i.state()||de.isFunction(o[n]&&o[n].then)))return i.then();for(;n--;)c(o[n],s(n),i.reject);return i.promise()}});var De=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;de.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&De.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},de.readyException=function(t){e.setTimeout(function(){throw t})};var Oe=de.Deferred();de.fn.ready=function(e){return Oe.then(e).catch(function(e){de.readyException(e)}),this},de.extend({isReady:!1,readyWait:1,holdReady:function(e){e?de.readyWait++:de.ready(!0)},ready:function(e){(e===!0?--de.readyWait:de.isReady)||(de.isReady=!0,e!==!0&&--de.readyWait>0||Oe.resolveWith(te,[de]))}}),de.ready.then=Oe.then,"complete"===te.readyState||"loading"!==te.readyState&&!te.documentElement.doScroll?e.setTimeout(de.ready):(te.addEventListener("DOMContentLoaded",l),e.addEventListener("load",l));var Le=function(e,t,n,r,o,i,s){var a=0,u=e.length,c=null==n;if("object"===de.type(n)){o=!0;for(a in n)Le(e,t,a,n[a],!0,i,s)}else if(void 0!==r&&(o=!0,de.isFunction(r)||(s=!0),c&&(s?(t.call(e,r),t=null):(c=t,t=function(e,t,n){return c.call(de(e),n)})),t))for(;a1,null,!0)},removeData:function(e){return this.each(function(){Re.remove(this,e)})}}),de.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Fe.get(e,t),n&&(!r||de.isArray(n)?r=Fe.access(e,t,de.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=de.queue(e,t),r=n.length,o=n.shift(),i=de._queueHooks(e,t),s=function(){de.dequeue(e,t)};"inprogress"===o&&(o=n.shift(),r--),o&&("fx"===t&&n.unshift("inprogress"),delete i.stop,o.call(e,s,i)),!r&&i&&i.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Fe.get(e,n)||Fe.access(e,n,{empty:de.Callbacks("once memory").add(function(){Fe.remove(e,[t+"queue",n])})})}}),de.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length","
"],col:[2,"
"],tr:[2,"","
"],td:[3,"
"],_default:[0,"",""]};Ge.optgroup=Ge.option,Ge.tbody=Ge.tfoot=Ge.colgroup=Ge.caption=Ge.thead,Ge.th=Ge.td;var Ye=/<|?\w+;/;!function(){var e=te.createDocumentFragment(),t=e.appendChild(te.createElement("div")),n=te.createElement("input");n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),t.appendChild(n),pe.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,t.innerHTML="",pe.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue}();var Qe=te.documentElement,Je=/^key/,Ke=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ze=/^([^.]*)(?:\.(.+)|)/;de.event={global:{},add:function(e,t,n,r,o){var i,s,a,u,c,l,f,p,h,d,g,m=Fe.get(e);if(m)for(n.handler&&(i=n,n=i.handler,o=i.selector),o&&de.find.matchesSelector(Qe,o),n.guid||(n.guid=de.guid++),(u=m.events)||(u=m.events={}),(s=m.handle)||(s=m.handle=function(t){return"undefined"!=typeof de&&de.event.triggered!==t.type?de.event.dispatch.apply(e,arguments):void 0}),t=(t||"").match(qe)||[""],c=t.length;c--;)a=Ze.exec(t[c])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h&&(f=de.event.special[h]||{},h=(o?f.delegateType:f.bindType)||h,f=de.event.special[h]||{},l=de.extend({type:h,origType:g,data:r,handler:n,guid:n.guid,selector:o,needsContext:o&&de.expr.match.needsContext.test(o),namespace:d.join(".")},i),(p=u[h])||(p=u[h]=[],p.delegateCount=0,f.setup&&f.setup.call(e,r,d,s)!==!1||e.addEventListener&&e.addEventListener(h,s)),f.add&&(f.add.call(e,l),l.handler.guid||(l.handler.guid=n.guid)),o?p.splice(p.delegateCount++,0,l):p.push(l),de.event.global[h]=!0)},remove:function(e,t,n,r,o){var i,s,a,u,c,l,f,p,h,d,g,m=Fe.hasData(e)&&Fe.get(e);if(m&&(u=m.events)){for(t=(t||"").match(qe)||[""],c=t.length;c--;)if(a=Ze.exec(t[c])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){for(f=de.event.special[h]||{},h=(r?f.delegateType:f.bindType)||h,p=u[h]||[],a=a[2]&&new RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=i=p.length;i--;)l=p[i],!o&&g!==l.origType||n&&n.guid!==l.guid||a&&!a.test(l.namespace)||r&&r!==l.selector&&("**"!==r||!l.selector)||(p.splice(i,1),l.selector&&p.delegateCount--,f.remove&&f.remove.call(e,l));s&&!p.length&&(f.teardown&&f.teardown.call(e,d,m.handle)!==!1||de.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)de.event.remove(e,h+t[c],n,r,!0);de.isEmptyObject(u)&&Fe.remove(e,"handle events")}},dispatch:function(e){var t,n,r,o,i,s,a=de.event.fix(e),u=new Array(arguments.length),c=(Fe.get(this,"events")||{})[a.type]||[],l=de.event.special[a.type]||{};for(u[0]=a,t=1;t","