本文為轉(zhuǎn)載案铺,原文:Golang 簡單爬蟲實(shí)現(xiàn) · 定時(shí)任務(wù)
介紹
通過前一篇文章,我們已經(jīng)實(shí)現(xiàn)了簡單的爬蟲梆靖,爬取小說控汉。但是仔細(xì)思考,可以發(fā)現(xiàn)返吻,有很多缺陷姑子。
第一,我們爬取的地址是寫死的测僵,如果再想爬一本其他的書街佑,豈不是還有修改代碼?這樣很明顯是不合理的捍靠。
第二沐旨,對(duì)于連載的小說,我們不知道什么時(shí)候會(huì)有更新榨婆,所以磁携,我們也不知道什么時(shí)候去執(zhí)行這個(gè)爬取的任務(wù),而且還全都是手動(dòng)執(zhí)行
那么良风,今天就先針對(duì)這2個(gè)問題來說明下颜武。
思路
對(duì)于第一個(gè)問題,其實(shí)很簡單啦拖吼,只要改一改數(shù)據(jù)庫,然后將待爬取的任務(wù)都存到數(shù)據(jù)庫里这吻,然后查出來遍歷爬取數(shù)據(jù)即可吊档。
對(duì)于第二個(gè)問題,也不復(fù)雜唾糯。只需要搞個(gè)死循環(huán)怠硼,讓程序一直執(zhí)行鬼贱,而爬取數(shù)據(jù)的任務(wù),隔一段時(shí)間跑一次即可香璃。在這里我用了個(gè)第三方的包來做這件事:github.com/robfig/cron
實(shí)現(xiàn)
下面就前面的2個(gè)問題这难,及解決思路,來分別實(shí)現(xiàn)葡秒。
圖書配置
首先姻乓,要修改數(shù)據(jù)結(jié)構(gòu),在原有的book變中新增以下from
, url
, status
眯牧。
修改后的結(jié)構(gòu)如下圖:
新增字段的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 - 已完結(jié)蹋岩;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;
具體字段的含義,可見注釋学少。
然后要做的就是將數(shù)據(jù)庫里的內(nèi)容查出來剪个,然后遍歷爬取即可
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)
}
}
這樣,只要數(shù)據(jù)庫book表中有的數(shù)據(jù)版确,且為連載的書扣囊,就會(huì)被爬取了。第一個(gè)問題也就解決了绒疗。
定時(shí)任務(wù)
接下來就是如何實(shí)現(xiàn)定時(shí)爬取任務(wù)了侵歇。之前提到了第三方包:cron。
cron
cron(計(jì)劃任務(wù))忌堂,顧名思義盒至,按照約定的時(shí)間,定時(shí)的執(zhí)行特定的任務(wù)(job)士修。cron 表達(dá)式 表達(dá)了這種約定
cron 表達(dá)式
cron 表達(dá)式代表了一個(gè)時(shí)間集合枷遂,使用 6 個(gè)空格分隔的字段表示。
字段名 是否必須 允許的值 允許的特定字符
秒(Seconds) 是 0-59
* / , -
分(Minutes) 是 0-59
* / , -
時(shí)(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)字段的值不區(qū)分大小寫棋嘲,如:SUN酒唉、Sun 和 sun 是一樣的。
2)星期
(Day of week)字段如果沒提供沸移,相當(dāng)于是 *
特殊符號(hào)說明
星號(hào)(*)
表示 cron 表達(dá)式能匹配該字段的所有值痪伦。如在第5個(gè)字段使用星號(hào)(month),表示每個(gè)月斜線(/)
表示增長間隔雹锣,如第1個(gè)字段(minutes) 值是 3-59/15网沾,表示每小時(shí)的第3分鐘開始執(zhí)行一次,之后每隔 15 分鐘執(zhí)行一次(即 3蕊爵、18辉哥、33、48 這些時(shí)間點(diǎn)執(zhí)行),這里也可以表示為:3/15逗號(hào)(,)
用于枚舉值醋旦,如第6個(gè)字段值是 MON,WED,FRI恒水,表示 星期一、三饲齐、五 執(zhí)行連字號(hào)(-)
表示一個(gè)范圍钉凌,如第3個(gè)字段的值為 9-17 表示 9am 到 5pm 直接每個(gè)小時(shí)(包括9和17)問號(hào)(?)
只用于 日(Day of month) 和 星期(Day of week),表示不指定值捂人,可以用于代替 *
cron表達(dá)式示例
spec1 := "*/5 * * * * 御雕?" //每5秒執(zhí)行一次
spec2 := "0 */5 * * * ?" //每5分鐘執(zhí)行一次
spec3 := "0 0 * * * ?" //沒小時(shí)執(zhí)行一次
spec3 := "0 0 2 * * ?" //每天凌晨2點(diǎn)執(zhí)行
spec4 := "0 0 2 1 * ?" //每月1號(hào)的凌晨2點(diǎn)執(zhí)行
spec5 := "0 0 2 ? * mon,wed,fri" //每周一,三先慷,五凌晨2點(diǎn)執(zhí)行
spec6 := "0 12-59/5 * * * ?" //每小時(shí)的12分鐘之后饮笛,每5分鐘執(zhí)行一次
實(shí)例
介紹完表達(dá)式,就簡單的來個(gè)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的基本使用應(yīng)該沒有問題了福青。更深次的可以多看下源碼。
spider中的使用
既然前面已經(jīng)學(xué)會(huì)了cron的使用脓诡,后面就簡單了无午,先在配置文件中加一個(gè)corn表達(dá)式的配置:
[task]
spec = 0 */5 * * * ? //每5分鐘執(zhí)行一次
使用配置的方式,會(huì)更加靈活一點(diǎn)祝谚。
然后就是項(xiàng)目中的實(shí)際使用了:
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)
}
}
運(yùn)行一段時(shí)間后宪迟,日志中記載的內(nèi)容如下:
當(dāng)然嘍,數(shù)據(jù)庫中的數(shù)據(jù)肯定不會(huì)少的啦交惯。
目前為止次泽,我的這個(gè)小爬蟲基本已經(jīng)完成了,剩下的就是對(duì)于可能遇到的站點(diǎn)的擴(kuò)展了席爽。當(dāng)然代碼里也早已經(jīng)留好了接口意荤,當(dāng)時(shí)候擴(kuò)展的話也會(huì)很容易的。
萬事俱備只锻,只欠一個(gè)前端了玖像。后面會(huì)持續(xù)跟進(jìn),屆時(shí)可以做成wap站齐饮,畢竟用手機(jī)看小說是多數(shù)情況的啦捐寥。
源碼
完
轉(zhuǎn)載請(qǐng)注明出處:
Golang 簡單爬蟲實(shí)現(xiàn) · 定時(shí)任務(wù)