老菜鸡陪你一块儿从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)

前言

目的

掘金里大佬太多了,分享各类牛批技术、代码、经验,看的美滋滋,可是在身为一只菜鸡的我也在思考,你们看了那么多文章,了解了那么多前沿技术,真正落地使用的又有多少?仍是须要实战一下才能知道,何时须要用到什么技术手段,怎么合适的运用各类技术来解决问题。所以本篇文章并非分享哪些技术多666,而是跟各位老哥一块儿根据一个具体需求,完成分析、设计编码、上线的全流程,造成一个真正可用的产品,以此来造成一种为了解决问题而选取相应技术的思惟(牛批要先吹好)。php

需求

Screenlapse也算是个比较有意思的产品了,它主要的功能就是帮助用户按期对指定网页进行截图,能够设置天天、每周、每个月某一个特定时间点去作截图,截图随时预览,有了这么个服务你就能够给你的网站造成一个历史档案库,或者默默的视奸竞争对手网站网页的变化html

然而!!它是个PC站没有移动端,并且他好像被墙了,百度也不索引它 那么机会来了,咱们本身作一个本土化的移动版的Screenlase,顺便把他作到公众号里前端

先放个成品

分析

冷静睿智机敏的分析一下问题vue

初步分析

用户功能:

  1. 微信用户登录
  2. 用户添加爬取任务
  3. 用户删除爬取任务
  4. 用户修改爬取任务
  5. 用户查看全部任务
  6. 用户查看某一个任务的截图

后端功能:

  1. 定时把须要爬取的连接抓取个截图存下来

这么一看功能简单的一批,不就是增删改查么,辣鸡! 可是定眼一看,发现事情并不简单。node

详细分析

1. 关于微信用户登录

相比于小程序,公众号的微信用户登录更麻烦一些,须要经过连接的跳转来获取到openid,然而坑并不在这!!!,由于个人公众号是个弱鸡的我的订阅号!连获取openid的权限都没有!!若是是原来,基本上就全剧终了,然而因为前端事件开通了个PayJs的我的微信支付接口,而里面恰好有个获取openid的接口(不知道用了什么黑科技),所以直接用这个接口来替代官方的接口获取openidlinux

2. 用户登录与验证

有了openid,后端直接根据openid在用户表里生成一条用户记录便可,关于如何生成,路子有不少,各位大佬都是明白人,根据具体状况选择合适的方法插一条就行ios

而后验证的话固然用烂大街的JWT方式,后台在用户登录后验证完用户信息,返回个token给前端,前端存着token,每次调用的时候都带着,老套路了redis

3. 关于后台爬取

因为个人后端使用php写的,所以引伸出了一些问题,vuex

爬取页面截图

这种事情php虽然有办法但仍是弱鸡一些没有nodejs使用puppeteer来的方便,既然nodejs能很方便的解决,那么天然就选择nodejs来爬取页面截图,然而这就会涉及到php的后端和nodejs爬取程序交互的问题,这里也有不少方式能够解决,能够经过接口,也能够经过其余方式,可是考虑到爬取任务的特色,在这里我选择了用redis作中间层(其余中间件作任务队列也都OJBK),利用redis的发布订阅模式,php将待爬取的任务放到redis里,nodejs的程序也做为订阅者,获取到任务,爬取完图片后经过接口将相关信息返回给php的后台chrome

图片存放位置

考虑到手上是个乞丐版的服务器,若是把图片放到服务器上,占用空间不说,每次客户端访问从服务器加载图片会占用大量带宽,影响网站性能,所以确定要找个图床啦,个人服务器是腾讯云的给提供免费50G的对象存储,有了这个就好说了,puppeteer爬取完图片后把图片先暂存到服务器上,而后把图片上传到图床,得到图片连接,而后把服务器上的图片删掉,给php的后端返回的时候也只是返回一下图片连接就行啦

定时任务

系统须要一个定时任务,定时从数据库中取出须要进行爬取的数据,而后放入redis,然而php在这方面也是比较鸡肋,虽然有Swoole这种牛批的框架,可是为了解决一个小问题,不值当引入这么大的框架,所以选个这种方案,作个接口,而后利用BT面板配置个linux的定时任务,每隔几分钟就访问一下这个接口,而后接口就去搞一些列操做就完事啦

流程图

流程仍是比较简单的

设计

数据字段设计

根据上面的分析大概也知道了,核心功能区就是把用户须要爬取的连接存下来,按期取出来爬一下,而后把爬取到的图片连接存一下和用户的任务关联上便可,所以设计两张表就OJBK,简单的一批

  1. 任务表
字段名 含义
id 主键
user_id 用户id
url 任务连接
start_time 开始时间
next_time 下次爬取时间
period 周期(以秒来存取)
period_type 周期类型 天、周、月
status 状态
create_time 建立时间
update_time 更新时间
more 扩展属性
  1. 任务图片表
字段名 含义
id 主键
task_id 用户id
url 任务连接
plan_time 计划爬取时间
create_time 建立时间
update_time 更新时间

接口设计

数据表设计完了,如今来识别一下,看看须要哪些接口

用户方面:

  • 获取任务列表的接口(task/getByPage)
  • 增长任务的接口(task/add)
  • 删除任务的接口(task/delete)
  • 修改任务的接口(task/update)
  • 获取任务详细的接口(task/get)
  • 查看任务已爬取图片的接口(task/getScreenshotByPage)
  • 用户登录接口
  • 验证用户登陆状态的接口

后台管理方面:

各位看官根据状况自行发挥啦

其余接口:

  • 定时任务调用的接口(用来取处任务放入redis)
  • 接收爬取图片信息的接口(供nodejs程序调用)

核心的接口就这些,足以撑起整个业务功能的闭环了

实现

实现分三部分分别实现,移动端、后台、nodeJS爬取程序

采起的一些工具以下:

  • 后端:PHP/Thinkphp5.1 Nodejs
  • 数据库:MySQL Redis
  • 移动端:Vue+Vant
  • 其余:服务器、BT面板(懒人专用、谁用谁知道)、公众号、对象存储

后端:

都是普通的增删改查,须要注意的是再添加任务的时候,须要在添加完成后当即生成一个爬取任务,爬一张图做为初始图片

public function add(Request $request){
       //验证输入
        (new TaskAddValidate())->goCheck();
        //根据token获取用户id
        $userId=TokenService::getCurrentVars(TokenService::currentToken(),'id');
        $startTime=$request->param('start_time');
        $period=$request->param('period');
        $periodType=$request->param('period_type',0);
        $url=$request->param('url');
        //转换一下时间戳,php里是10位时间戳,js里是精确到毫秒的13位
        if (strlen($startTime)>10){
            $startTime=substr($startTime,0,10);
        }
        $nextTime=$startTime+$period;
        $more=[];
        if($request->has('more')){
            $more=json_decode(htmlspecialchars_decode($request->param('more')),true);
        }
        $task=TaskModel::create([
            'start_time'=>$startTime,
            'next_time'=>$nextTime,
            'period'=>$period,
            'period_type'=>$periodType,
            'url'=>$url,
            'user_id'=>$userId,
            'more'=>$more
        ]);
        //直接生成一个爬取任务,发布到redis
        RedisService::pushTask('tasks',$task);
        return ResultService::success('',$task);
    }
复制代码

爬取图片服务(使用puppeteer):

爬取并上传到腾讯云的对象存储

const puppeteer = require('puppeteer');
const redis=require('redis');
const redisConfig=require('./redis.config');
var COS = require('cos-nodejs-sdk-v5');
const axios=require('axios')
const fs=require('fs');
const config=require('./config');

//订阅redis,接收任务
const redisClient=redis.createClient(redisConfig.port,redisConfig.host);
redisClient.auth(redisConfig.password);
redisClient.subscribe('tasks');
redisClient.on('message',(channel,msg)=>{
    if(channel=='tasks'){
        let task=JSON.parse(msg);
       
            getScreenshot(task);
        
        
    }
});
//爬取截图
async function getScreenshot(task){
    const browser = await puppeteer.launch({ headless: true,args: ['--no-sandbox', '--disable-setuid-sandbox'] })
    const page = await browser.newPage()
    page.setViewport({ 
        width: 1280, 
        height: 960,
        });
    await page.goto(task.url, {
        waitUntil: 'networkidle2' //等待页面不动了,说明加载完毕了
    })
    let basePath=config.basePath;
    let filenName=`${task.id}-${Date.now()}.png`;
    let filePath=basePath+filenName;
    await page.screenshot({ path: filePath ,fullPage:true})
    //上传图片到云对象存储
    let imgRes=await uploadScreenshot(filePath,filenName);
    //将图片连接回传给php服务端
    let res=await axios.post(config.apiUrl,{
        id:task.id,
        url:'http://'+imgRes.Location,
        plan_time:task.next_time
    });
    //删除图片
    fs.unlink(filePath,(err)=>{
        if(err){
            console.log(`删除文件不成功,缘由${err}`);
        }
        
    });
}

// 使用永久密钥建立实例
var cos = new COS({
SecretId: '***',
SecretKey: '***'
});
//上传图片到云对象存储
function uploadScreenshot(filePath,filenName){
    return new Promise((resolve,reject)=>{
        cos.sliceUploadFile({
            Bucket: '***',
            Region: '***',
            Key: filenName,
            FilePath: filePath
            },(err,data)=>{
                if(!err){
                   resolve(data);
                }
                else{
                    reject(err);
                }
            });
    })
}
复制代码

移动端:

用的是vue+vant进行移动端开发,因为本菜鸡没什么审美,基本上也就是直接对vant组件进行使用拼拼凑凑就完事了,

项目目录以下(常规的一批,弱鸡)

想分享的一点是,看了许多文章,对于在vue项目中进行网络接口调用的方式,一部分人偏心于将全部的接口调用都以action的形式放到vuex里,全部的数据基本上都集中在了vuex中,然而以我得理解, vuex是为了全局数据共享而存在的,将全部数据都集中在vuex中可能会使vuex变得庞大,有违初衷,所以,我仍是将全部的接口按业务区分都放在了api文件夹中,在须要的时候直接调用便可

其中http.js是对axios进行的一个小封装,添加了一些拦截器,

每一个业务文件夹中的接口均作成promise形式的方便并导出

import http from './../http'

let register=(params=null)=>http.post('/api/front/user/register',params);
let login=(params=null)=>http.post('/api/front/user/login',params);
let checkLogin=()=>http.get('/api/front/user/checkLogin');
let logout=()=>http.post('/api/front/user/logout');
let getUser=()=>http.get('api/front/user/get');
export default {
    register,
    login,
    logout,
    checkLogin,
    getUser
}

复制代码

apis则是用来将不一样业务的接口进行集中,在使用的时候直接引用apis就能够了

import user from './user'
import portal from './portal'
import screenlapse from './screenlapse'

let exportApi={
    getSetting
};
Object.assign(exportApi,user,portal,screenlapse)
export default exportApi

复制代码

上线:

本地测试没问题,开始上线工做,结合上面的分析,咱们须要对三个部分进行上线

系统是Centos7,使用的是BT面板一键搭建lnmp环境,进行平常运维与站点管理

后端服务上线(php)

使用bt面板直接建一个站点便可,使用面板自带的伪静态配置下隐藏index.php

移动端

本地打包完成后,上传到服务器,利用bt面板新建一个站点,该站点的目录指向移动端的文件,而后须要配置一下反向代理来解决一下跨域,基本上就是把某一个标识开头的请求转发一下

location ^~ /apis/ 
    {
          proxy_pass https://api.yizhisamoye.cn/;
    }
    location ^~ /upload/ 
    {
          proxy_pass https://api.yizhisamoye.cn;
    }
复制代码

爬取服务

注意两点

  1. 爬取服务因为只是一个简单的小程序,并无使用node的http server相关的东西,所以无法使用pm2来启动和管理,不过也不要紧,只要让程序跑起来在后台一直运行便可,使用命令来启动便可,定位到爬虫文件夹,使用命令nohup node spider.js便可

  2. 安装完相关依赖启动后,爬虫报找不到chrome的错误,这时候须要对centos系统安装一下插件和依赖来保障puppeteer的运行

yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y
复制代码

最后

上线完后测试没什么问题,至此,完成了一个项目从需求到上线的所有过程,虽然没有用什么牛批技术,项目管理也基本没有,可是毕竟是菜鸡啊哈哈哈,主要仍是给一些我的开发者提供一个思路,以解决问题的目标去应用技术,而不是为了应用技术而应用技术。 最后惯例仍是要放出二维码供你们直接使用(小弟服务器辣鸡,若反应慢了请你们谅解)~稍后整理好后会放出gayhub地址

相关文章
相关标签/搜索