Go语言Golang程序员

Golang 简单爬虫实现 · 定时任务

2018-02-08  本文已影响154人  ChainZhang

本文为转载,原文:Golang 简单爬虫实现 · 定时任务

爬虫

介绍

通过前一篇文章,我们已经实现了简单的爬虫,爬取小说。但是仔细思考,可以发现,有很多缺陷。

第一,我们爬取的地址是写死的,如果再想爬一本其他的书,岂不是还有修改代码?这样很明显是不合理的。

第二,对于连载的小说,我们不知道什么时候会有更新,所以,我们也不知道什么时候去执行这个爬取的任务,而且还全都是手动执行

那么,今天就先针对这2个问题来说明下。

思路

  1. 对于第一个问题,其实很简单啦,只要改一改数据库,然后将待爬取的任务都存到数据库里,然后查出来遍历爬取数据即可。

  2. 对于第二个问题,也不复杂。只需要搞个死循环,让程序一直执行,而爬取数据的任务,隔一段时间跑一次即可。在这里我用了个第三方的包来做这件事:github.com/robfig/cron

实现

下面就前面的2个问题,及解决思路,来分别实现。

图书配置

首先,要修改数据结构,在原有的book变中新增以下from, url, status
修改后的结构如下图:

数据库结构

新增字段的sql语句如下:

-- MySQL Workbench Synchronization
-- Generated: 2018-02-07 16:50
-- Model: New Model
-- Version: 1.0
-- Project: Name of the project
-- Author: chain
-- Comment: Update book

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';

ALTER TABLE `chain_book`.`book` 
ADD COLUMN `status` INT(11) NULL DEFAULT NULL COMMENT '0 - 已完结;1 - 连载中' AFTER `image`,
ADD COLUMN `from` VARCHAR(100) NULL DEFAULT NULL COMMENT '源站' AFTER `status`,
ADD COLUMN `url` VARCHAR(100) NULL DEFAULT NULL COMMENT '源站地址' AFTER `from`;

SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

具体字段的含义,可见注释。

然后要做的就是将数据库里的内容查出来,然后遍历爬取即可

func GetBook(){
    ilog.AppLog.Info("spider start")
    books, _ := models.GetBookList("status", 1)
    for _, book := range books{
        go func(book *models.Book){
            s, err := spider.NewSpider(book.From)
            if err != nil{
                ilog.AppLog.Error("new Spider error: ", err.Error())
                return
            }
            err = s.SpiderUrl(book.Url)
            if err != nil{
                ilog.AppLog.Error("new Document error: ", err.Error())
            }
            ilog.AppLog.Info(book.Name, "已爬取完毕")
        }(book)
    }
}

这样,只要数据库book表中有的数据,且为连载的书,就会被爬取了。第一个问题也就解决了。

定时任务

接下来就是如何实现定时爬取任务了。之前提到了第三方包:cron

cron

cron(计划任务),顾名思义,按照约定的时间,定时的执行特定的任务(job)。cron 表达式 表达了这种约定

cron 表达式

cron 表达式代表了一个时间集合,使用 6 个空格分隔的字段表示。

字段名 是否必须 允许的值 允许的特定字符
秒(Seconds) 是 0-59
* / , -
分(Minutes) 是 0-59
* / , -
时(Hours) 是 0-23
* / , -
日(Day of month) 是 1-31
* / , – ?
月(Month) 是 1-12 or JAN-DEC
* / , -
星期(Day of week) 否 0-6 or SUM-SAT
* / , – ?

注:

1)月(Month)和星期(Day of week)字段的值不区分大小写,如:SUN、Sun 和 sun 是一样的。
2)星期

(Day of week)字段如果没提供,相当于是 *

特殊符号说明

  1. 星号(*)
    表示 cron 表达式能匹配该字段的所有值。如在第5个字段使用星号(month),表示每个月

  2. 斜线(/)
    表示增长间隔,如第1个字段(minutes) 值是 3-59/15,表示每小时的第3分钟开始执行一次,之后每隔 15 分钟执行一次(即 3、18、33、48 这些时间点执行),这里也可以表示为:3/15

  3. 逗号(,)
    用于枚举值,如第6个字段值是 MON,WED,FRI,表示 星期一、三、五 执行

  4. 连字号(-)
    表示一个范围,如第3个字段的值为 9-17 表示 9am 到 5pm 直接每个小时(包括9和17)

  5. 问号(?)
    只用于 日(Day of month) 和 星期(Day of week),表示不指定值,可以用于代替 *

cron表达式示例

spec1 := "*/5 * * * * ?" //每5秒执行一次
spec2 := "0 */5 * * * ?"  //每5分钟执行一次
spec3 := "0 0 * * * ?"    //没小时执行一次
spec3 := "0 0 2 * * ?"    //每天凌晨2点执行
spec4 := "0 0 2 1 * ?"    //每月1号的凌晨2点执行
spec5 := "0 0 2 ? * mon,wed,fri" //每周一,三,五凌晨2点执行
spec6 := "0 12-59/5 * * * ?" //每小时的12分钟之后,每5分钟执行一次

实例

介绍完表达式,就简单的来个corn的小例子吧

package main

import (
    "time"
    "fmt"
    "github.com/robfig/cron"
)
var i = 0

func main() {
    fmt.Println("start ")
    c := cron.New()
    spec := "*/5 * * * * ?"
    c.AddFunc(spec,func(){
        i ++
        fmt.Println(time.Now(), "cron running: ", i)
    })
    c.Start()
    select{}
}
结果

目前为止,corn的基本使用应该没有问题了。更深次的可以多看下源码。

spider中的使用

既然前面已经学会了cron的使用,后面就简单了,先在配置文件中加一个corn表达式的配置:

[task]
spec = 0 */5 * * * ?  //每5分钟执行一次

使用配置的方式,会更加灵活一点。
然后就是项目中的实际使用了:

package main

import (
    "github.com/Chain-Zhang/igo/ilog"
    "github.com/Chain-Zhang/igo/conf"
    "github.com/robfig/cron"

    "ispider/spider"
    "ispider/models"
)

func main() {
    ilog.AppLog.Info("service start")
    c := cron.New()
    spec := conf.AppConfig.GetString("task.spec")
    ilog.AppLog.Info("spec: ",spec)
    c.AddFunc(spec,GetBook)
    c.Start()
    select{}
}

func GetBook(){
    ilog.AppLog.Info("spider start")
    books, _ := models.GetBookList("status", 1)
    for _, book := range books{
        go func(book *models.Book){
            s, err := spider.NewSpider(book.From)
            if err != nil{
                ilog.AppLog.Error("new Spider error: ", err.Error())
                return
            }
            err = s.SpiderUrl(book.Url)
            if err != nil{
                ilog.AppLog.Error("new Document error: ", err.Error())
            }
            ilog.AppLog.Info(book.Name, "已爬取完毕")
        }(book)
    }
}

运行一段时间后,日志中记载的内容如下:


日志

当然喽,数据库中的数据肯定不会少的啦。

目前为止,我的这个小爬虫基本已经完成了,剩下的就是对于可能遇到的站点的扩展了。当然代码里也早已经留好了接口,当时候扩展的话也会很容易的。

万事俱备,只欠一个前端了。后面会持续跟进,届时可以做成wap站,毕竟用手机看小说是多数情况的啦。

源码

本文源码

转载请注明出处:
Golang 简单爬虫实现 · 定时任务

上一篇下一篇

猜你喜欢

热点阅读