From a2091c4a6d67c7afa8f0b92e3b026fcd85c637d0 Mon Sep 17 00:00:00 2001 From: okxlin Date: Wed, 16 Aug 2023 21:41:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0spider-admin-pro?= =?UTF-8?q?=E5=88=B0=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/spider-admin-pro/2.0.3/.env.sample | 2 + apps/spider-admin-pro/2.0.3/data.yml | 10 + .../spider-admin-pro/2.0.3/docker-compose.yml | 16 + apps/spider-admin-pro/README.md | 362 ++++++++++++++++++ apps/spider-admin-pro/data.yml | 20 + apps/spider-admin-pro/latest/.env.sample | 2 + apps/spider-admin-pro/latest/data.yml | 10 + .../latest/docker-compose.yml | 16 + apps/spider-admin-pro/logo.png | Bin 0 -> 16485 bytes 9 files changed, 438 insertions(+) create mode 100644 apps/spider-admin-pro/2.0.3/.env.sample create mode 100644 apps/spider-admin-pro/2.0.3/data.yml create mode 100644 apps/spider-admin-pro/2.0.3/docker-compose.yml create mode 100644 apps/spider-admin-pro/README.md create mode 100644 apps/spider-admin-pro/data.yml create mode 100644 apps/spider-admin-pro/latest/.env.sample create mode 100644 apps/spider-admin-pro/latest/data.yml create mode 100644 apps/spider-admin-pro/latest/docker-compose.yml create mode 100644 apps/spider-admin-pro/logo.png diff --git a/apps/spider-admin-pro/2.0.3/.env.sample b/apps/spider-admin-pro/2.0.3/.env.sample new file mode 100644 index 00000000..94e0f0cf --- /dev/null +++ b/apps/spider-admin-pro/2.0.3/.env.sample @@ -0,0 +1,2 @@ +CONTAINER_NAME="spider-admin-pro" +PANEL_APP_PORT_HTTP="40121" diff --git a/apps/spider-admin-pro/2.0.3/data.yml b/apps/spider-admin-pro/2.0.3/data.yml new file mode 100644 index 00000000..597f02c8 --- /dev/null +++ b/apps/spider-admin-pro/2.0.3/data.yml @@ -0,0 +1,10 @@ +additionalProperties: + formFields: + - default: 40121 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + required: true + rule: paramPort + type: number diff --git a/apps/spider-admin-pro/2.0.3/docker-compose.yml b/apps/spider-admin-pro/2.0.3/docker-compose.yml new file mode 100644 index 00000000..7072a6bd --- /dev/null +++ b/apps/spider-admin-pro/2.0.3/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3' +services: + spider-admin-pro: + container_name: ${CONTAINER_NAME} + restart: always + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:8000" + image: mouday/spider-admin-pro:2.0.3 + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/spider-admin-pro/README.md b/apps/spider-admin-pro/README.md new file mode 100644 index 00000000..61079557 --- /dev/null +++ b/apps/spider-admin-pro/README.md @@ -0,0 +1,362 @@ +# 使用说明 + +- 默认账户密码 + +``` +username:admin +password:123456 +``` + +# 原始相关 + +# Spider Admin Pro + +[![PyPI](https://img.shields.io/pypi/v/spider-admin-pro.svg)](https://pypi.org/project/spider-admin-pro) +[![PyPI - Downloads](https://img.shields.io/pypi/dm/spider-admin-pro)](https://pypi.org/project/spider-admin-pro) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/spider-admin-pro)](https://pypi.org/project/spider-admin-pro) +[![Docker Image Version (latest semver)](https://img.shields.io/docker/v/mouday/spider-admin-pro?label=docker%20version&sort=semver)](https://hub.docker.com/r/mouday/spider-admin-pro) +[![Docker Pulls](https://img.shields.io/docker/pulls/mouday/spider-admin-pro)](https://app.travis-ci.com/mouday/spider-admin-pro) +[![Build Status](https://app.travis-ci.com/mouday/spider-admin-pro.svg?branch=master)](https://app.travis-ci.com/mouday/spider-admin-pro) +[![PyPI - License](https://img.shields.io/pypi/l/spider-admin-pro)](https://github.com/mouday/spider-admin-pro/blob/master/LICENSE) + +![](https://github.com/mouday/spider-admin-pro/raw/master/doc/img/logo.png) + +## 简介 + +Spider Admin Pro 是[Spider Admin](https://github.com/mouday/SpiderAdmin)的升级版,一个可视化的Scrapy爬虫管理平台,依赖于Scrapyd + +- Github: [https://github.com/mouday/spider-admin-pro](https://github.com/mouday/spider-admin-pro) +- Gitee: [https://gitee.com/mouday/spider-admin-pro](https://gitee.com/mouday/spider-admin-pro) + +- Pypi: [https://pypi.org/project/spider-admin-pro](https://pypi.org/project/spider-admin-pro) +- Docker: [https://hub.docker.com/r/mouday/spider-admin-pro](https://hub.docker.com/r/mouday/spider-admin-pro) +- releases: [https://github.com/mouday/spider-admin-pro/releases](https://github.com/mouday/spider-admin-pro/releases) + +![](https://github.com/mouday/spider-admin-pro/raw/master/doc/img/spider-admin-pro.png) + +## 安装启动 + +本项目基于Python3.7.0 开发,所以推荐使用Python3.7.0及其以上版本 + +运行项目前,请先确保[scrapyd](https://pengshiyu.blog.csdn.net/article/details/79842514)服务已经启动 + +方式一: + +```bash +$ python3 --version +Python 3.7.0 + +# 创建名为 venv 的虚拟环境 +$ python3 -m venv venv + +# 激活虚拟环境 +$ source venv/bin/activate + +# 安装spider-admin-pro +$ pip3 install spider-admin-pro + +# 可选 +$ pip3 install -U spider-admin-pro -i https://pypi.org/simple + +# Linux macOS 运行启动 +$ gunicorn 'spider_admin_pro.main:app' + +# windows 环境使用waitress 替换 gunicorn +$ pip install waitress + +$ waitress-serve --listen=127.0.0.1:8000 'spider_admin_pro.main:app' +``` + +方式二: + +```bash +$ git clone https://github.com/mouday/spider-admin-pro.git + +$ cd spider-admin-pro + +# 安装依赖(建议:最好新建一个虚拟环境) +$ pip3 install -r requirements.txt + +# Linux/macOS 以开发模式运行 +$ make dev + +# windows 以开发模式运行 +$ python3 dev.py + +# 以生产模式运行 +$ make pro +``` + +安装 scrapy 全家桶`[可选]` + +```bash +pip install scrapy scrapyd scrapyd-client +``` + +方式三: + +```bash +docker run -p 8000:8000 mouday/spider-admin-pro +``` + +## 配置参数 + +> - v2.0版本移除了`.env`环境变量配置方式,仅支持yaml格式配置 +> - v2.0版本移除了`PORT`和`HOST`配置项,推荐统一采用gunicorn 管理 + +[Spider Admin Pro V1版本文档看这里](README-v1.md) + +自定义配置 + +在运行目录下新建`config.yml` 文件,也就是执行启动命令的目录,运行时会自动读取该配置文件 + +例如 +```bash +$ ls +config.yml + +$ gunicorn 'spider_admin_pro.main:app' +``` +> 强烈建议:修改密码和秘钥项 + +eg: + +```yaml +# 登录账号密码 +USERNAME: admin +PASSWORD: "123456" + +# scrapyd地址, 结尾不要加斜杆 +SCRAPYD_SERVER: "http://127.0.0.1:6800" +``` + +## 使用扩展 + +收集运行日志:[scrapy-util](https://github.com/mouday/scrapy-util) 可以帮助你收集到程序运行的统计数据 + +## 技术栈: + +1、前端技术: + +| 功能 | 第三方库及文档 | +| - | - | +| 基本框架 | [vue2.js](https://cn.vuejs.org/) | +| 仪表盘图表 | [echarts](https://echarts.apache.org/) | +| 网络请求 | [axios](https://www.npmjs.com/package/axios) | +| 界面样式 | [Element-UI](https://element.eleme.cn/) | + +2、后端技术 + +| 功能 | 第三方库及文档 | +| - | - | +| 接口服务 | [Flask](https://dormousehole.readthedocs.io/) | +| 任务调度 | [apscheduler](https://apscheduler.readthedocs.io/) | +| scrapyd接口 | [scrapyd-api](https://github.com/mouday/scrapyd-api) | +| 网络请求 | [session-request](https://github.com/mouday/session-request) | +| ORM | [peewee](http://docs.peewee-orm.com/) | +| jwt | [jwt](https://pyjwt.readthedocs.io/) | +| 系统信息 | [psutil](https://psutil.readthedocs.io/) | + +## 项目结构 + +【公开仓库】基于Flask的后端项目spider-admin-pro: [https://github.com/mouday/spider-admin-pro](https://github.com/mouday/spider-admin-pro) + +【私有仓库】基于Vue的前端项目spider-admin-pro-web: [https://github.com/mouday/spider-admin-pro-web](https://github.com/mouday/spider-admin-pro-web) + +> 备注:前端Vue项目,可入QQ群发送github用户名获取权限 + +spider-admin-pro项目主要目录结构: + +```bash +. +├── run.py # 程序入口 +├── api # Controller层 +├── service # Sevice层 +├── model # Model层 +├── exceptions # 异常 +├── utils # 工具类 +└── web # 静态web页 + +``` + +## 经验总结 + +Scrapyd 不能直接暴露在外网 + +1. 其他人通过deploy部署可以将代码部署到你的机器上,如果是root用户运行,还会在你机器上做其他的事情 +2. 还有运行日志中会出现配置文件中的信息,存在信息泄露的危险 + + +## TODO + +~~1. 补全开发文档~~ + +~~2. 支持命令行安装可用~~ + +~~3. 优化代码布局,提取公共库~~ + +~~4. 日志自动刷新~~ + +~~5. scrapy项目数据收集~~ + +[ok]6. 定时任务spider列左对齐,支持本地排序 + +[x]7. 调度器控制移除停止开启开关,只保留暂停继续 + +[x]8. 添加任务,默认项目名,关闭弹框取消form校验结果 + +[x]9. 统计的日志量太大,增加一个一个定时清理的功能 + +[x]10. 定时任务备份,不小心把任务清空 + +[x]11. 希望能加入更好的定时方式,类似 scrapyd_web那种定时 + +[x]12. 简单的爬虫不用非要去打包,比如我自己上传一个py文件,可以定时任务,脚本的方式运行 + +[x]13. 爬虫能配置带参数运行 + +## 交流沟通 + +关注本项目的小伙伴越来越多,为了更好地交流沟通,可以加入群聊 + +- 一群: 1074075691(已满) +- 二群: 864983297 + +问题:邀请码 答案:SpiderAdmin + + + +## 项目赞助 + +| 日期 | 姓名 | 金额 | +| - | - | - | +| 2022-04-16 | [@realhellosunsun](https://github.com/realhellosunsun) | ¥188.00 +| 2022-08-30 | [@yangxiaozhe13](https://github.com/yangxiaozhe13) | ¥88.00 +| 2022-09-01 | [@robot-2233](https://github.com/robot-2233) | ¥88.00 +| 2023-05-09 | 埃菲尔没有塔尖 | ¥68.80 + +## 项目截图 + +![](https://github.com/mouday/spider-admin-pro/raw/master/doc/img/dashboard.png) + +![](https://github.com/mouday/spider-admin-pro/raw/master/doc/img/project.png) + +![](https://github.com/mouday/spider-admin-pro/raw/master/doc/img/schedule.png) + +![](https://github.com/mouday/spider-admin-pro/raw/master/doc/img/logs.png) + +## 安装升级 +``` +pip3 install -U spider-admin-pro -i https://pypi.org/simple +``` + + +## Stargazers over time + +[![Stargazers over time](https://starchart.cc/mouday/spider-admin-pro.svg)](https://starchart.cc/mouday/spider-admin-pro) + + + +## 其他问题 + +1、windows系统 scrapyd 启动失败,可能缺少依赖pywin32 + +``` +pip install pywin32 +``` + +感谢[@whobywind](https://github.com/whobywind),提供的解决方案 + +2、网站有ip校验,刚访问几个请求就被禁止访问? + +同一个ip可能有被封的风险,可以使用代理ip去请求,有免费和付费。 + +如果是个人使用,可以找一些免费的ip临时使用 + +如果是企业项目,可以使用付费代理ip + +如果有问题,可以加QQ群,群里的小伙伴会积极解答喔 + +3、为什么外网访问不到,如何修改端口号 + +增加`--bind` 参数 + +格式 + +```bash +--bind 监听地址:监听端口号 +``` + +例如 + +```bash +# 启动运行 +$ gunicorn 'spider_admin_pro.main:app' + +# 支持外网可访问,云服务器(阿里云或腾讯云)需要设置安全组 +# 默认内网访问 --bind 127.0.0.1:8000 +$ gunicorn --bind '0.0.0.0:8000' 'spider_admin_pro.main:app' +``` + +更多设置,可参考[gunicorn](https://docs.gunicorn.org/en/stable/index.html) + +4、提示缺少libfile + +群友 `@Yuan、红尘美` 提供的解决方法 + +安装依赖 + +```bash +yum install libffi-devel -y +``` + +## 更新日志 + +- v2.0.3 + - 修复mysql作为后端存储的文档和登录bug + +- v2.0.2 + - 优化文档 + - 优化日志 + +- v2.0.1 + - 优化前端界面在windows平台显示异常的问题 + - 修复前端调度日志 列表显示异常的问题 + - 优化定时任务添加,自动选中项目和爬虫 + +- v2.0.0 + - 升级依赖 requirements.txt, Flask 1.0.3 升级为 2.2.2 + - 优化启动方式 + - 优化启动配置,移除`PORT` 和`HOST` 配置项 + - 移除.env环境变量配置,简化配置流程 + - 移除Flask配置读取,推荐使用`gunicorn`启动服务 + +- 2021-09-03 + - [bugfix]修复【任务列表】运行中项目无法取消的bug + +- 2022-04-01 + - [bugfix] 当修改scrapyd的端口号后,在配置文件中指定scrapyd为修改后的端口号。配置文件不生效 + - 感谢:@洒脱的狂者 发现的问题及解决办法 + +- 2022-05-27 + - [update] requirements.txt 文件中增加 flask_cors 依赖 + + +## 社区其他优秀工具推荐 + +- https://github.com/DormyMo/SpiderKeeper +- https://github.com/my8100/scrapydweb +- https://github.com/ouqiang/gocron 使用Go语言开发的轻量级定时任务集中调度和管理系统, 用于替代Linux-crontab + +## Spider Admin Pro vs. Spider Admin + +1. 简化了一些功能; +2. 优化了前端界面,基于Vue的组件化开发; +3. 优化了后端接口,对后端项目进行了目录划分; +4. 整体代码利于升级维护。 +5. 目前仅对Python3进行了支持 +6. 路由统一管理 +7. 全局异常捕获 +8. 接口统一返回 +9. 前后端分离 +10. 可视化参数配置 diff --git a/apps/spider-admin-pro/data.yml b/apps/spider-admin-pro/data.yml new file mode 100644 index 00000000..a0832b02 --- /dev/null +++ b/apps/spider-admin-pro/data.yml @@ -0,0 +1,20 @@ +name: Spider Admin Pro +tags: + - 工具 +title: 一个可视化的Scrapy爬虫管理平台,依赖于Scrapyd +type: 工具 +description: 一个可视化的Scrapy爬虫管理平台,依赖于Scrapyd +additionalProperties: + key: spider-admin-pro + name: Spider Admin Pro + tags: + - Tool + shortDescZh: 一个可视化的Scrapy爬虫管理平台,依赖于Scrapyd + shortDescEn: A visual Scrapy crawler management platform that relies on Scrapyd + type: tool + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://github.com/mouday/spider-admin-pro + github: https://github.com/mouday/spider-admin-pro + document: https://github.com/mouday/spider-admin-pro diff --git a/apps/spider-admin-pro/latest/.env.sample b/apps/spider-admin-pro/latest/.env.sample new file mode 100644 index 00000000..94e0f0cf --- /dev/null +++ b/apps/spider-admin-pro/latest/.env.sample @@ -0,0 +1,2 @@ +CONTAINER_NAME="spider-admin-pro" +PANEL_APP_PORT_HTTP="40121" diff --git a/apps/spider-admin-pro/latest/data.yml b/apps/spider-admin-pro/latest/data.yml new file mode 100644 index 00000000..597f02c8 --- /dev/null +++ b/apps/spider-admin-pro/latest/data.yml @@ -0,0 +1,10 @@ +additionalProperties: + formFields: + - default: 40121 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + required: true + rule: paramPort + type: number diff --git a/apps/spider-admin-pro/latest/docker-compose.yml b/apps/spider-admin-pro/latest/docker-compose.yml new file mode 100644 index 00000000..c474967d --- /dev/null +++ b/apps/spider-admin-pro/latest/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3' +services: + spider-admin-pro: + container_name: ${CONTAINER_NAME} + restart: always + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:8000" + image: mouday/spider-admin-pro:latest + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/spider-admin-pro/logo.png b/apps/spider-admin-pro/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f3c7ae32d1052e4e5d4938e55a6e1bc3a171487b GIT binary patch literal 16485 zcmeHPF$scq+@i$bAErp z^X7T^yf`~Aw$FCXeeV0ZzI883Lrno6hYAM-0^z?>l+yyvwf`MZOyE^=7?U43VY?_A zx`9C4%>Nx=?>uQw5C~5GN=`<{JL90utJdUq+Tp#s*Cguygpy+>(&C=lFlb`Tvjo?*pQtehINZLf-R`;2@v|#^irSz%bJfvmy{G0t9

VC5YgBI4ty5WgL|L@>J9h$ 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