1:爬取html页面
1:初始化,使用viper配置文件,包含网址的url
func init() {
//使用viper配置所需参数的文件
viper.SetConfigName("config.yaml") // 配置文件名(不含扩展名)
viper.SetConfigType("yaml") // 配置文件类型
viper.AddConfigPath(".") // 配置文件所在路径
err := viper.ReadInConfig()
if err != nil {
panic("配置文件读取失败")
}
}
2:建立连接;并使用defer关闭连接
//使用http.Get请求数据
//1.viper.GetString,使用viper技术去获取配置好的url,以免需要修改多处。
response, err := http.Get(viper.GetString("hot_api.baidu"))
if err != nil {
logrus.Error("请求百度数据失败:", err)
return
}
defer response.Body.Close()
3:读取响应内容
//在步骤2中已经把请求到的数据赋给了response
//1.这里使用go语言标准库的ioutil.ReadAll函数读取response.body文件。
//2.需要注意的是,自 Go 1.16 起,ioutil 包的一些功能已经被整合到 io 和 os 包中。ioutil.ReadAll 现在可以直接用 io.ReadAll 代替:
//3.ioutil.ReadAll函数的作用是读取一个io.Reader 接口中的所有数据,直到遇到 EOF(文件结束),并返回读取到的字节切片和可能遇到的错误。 func ReadAll(r io.Reader) ([]byte, error)
body, err := ioutil.ReadAll(response.Body)
if err != nil {
logrus.Error("读取响应内容失败:", err)
return
}
4:将读取到的文件加载到Goquery文档中
//1.首先将切片类型的body转换成字符串
//2.strings.NewReader是go语言标准库的函数,它的作用是创建一个 strings.Reader,这个 strings.Reader 实现了 io.Reader 接口,可以用于读取字符串数据。
//3.goquery.NewDocumentFromReader 将strings.NewReader读取到的body解析成goquery.Document对象
//4.把对象赋值给doc,这样就能对doc进行操作筛选里面的数据了。
doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
if err != nil {
logrus.Error("将html字符串加载到goquery失败:", err)
return
}
5:提取数据,并生成哈希值
//对数据进行筛选
//4里面已经把值赋给html文档doc了,可以使用doc.find进行筛选。each是遍历id为sanroot的每个匹配的元素
//strings.Split是go标准库的一个函数,用于将字符串按照指定的分割符进行分割,并返回一个字符串切片
doc.Find("#sanRoot").Each(func(i int, s *goquery.Selection) {
divContent, _ := s.Html()
res := strings.Split(divContent, "热搜榜")
re := strings.Split(res[0], "appUrl")
//新建个切片用来表示一版热搜数据
var data []BaiDu
//hashinfostr哈希值,每当出现一板新数据就会有一个新哈希值
var hotinfoStr string
for i1, s2 := range re {
if i1 != 0 {
//获取url
ss := strings.Split(s2, "&")[0]
u1 := strings.Split(ss, "https://www.baidu.com")[1]
url1 := fmt.Sprintf("https://www.baidu.com/%s", u1)
var title1 string
if i1 == 1 {
//获取标题
ss1 := strings.Split(s2, "word\":\"")[1]
a1 := strings.Split(ss1, "\"}")[0]
title1 = strings.Split(a1, "\",\"isTop")[0]
} else {
//获取标题
ss1 := strings.Split(s2, "word\":\"")[1]
title1 = strings.Split(ss1, "\"}")[0]
}
//获取热度
ss2 := strings.Split(s2, "hotScore\":\"")[1]
hot1 := strings.Split(ss2, "\",\"hotTag")[0]
//构建结构体,将每一条数据赋值给结构体
a := BaiDu{
UpdateVer: time.Now().Unix(),
Title: title1,
Url: url1,
Hot: hot1,
CreatedTime: time.Now(),
UpdatedTime: time.Now(),
}
//将每一个结构体添加到切片里面
data = append(data, a)
//每当完成一个切片的构建,也就是每当有一版新的热搜数据的同时,会生成一个哈希值用来表示这板数据
//hotinfostr由标题,url组成,会迭代进行。也就是说会先根据第一条数据的标题和url生成hotinfostr,然后第二条数据被添加到切片之后,hotinfostr会将第一条数据生成的hotinfostr和第二条数据的标题和url再次生成一个新的hotinfostr,这样,当数据被全部添加到切片之后,hotinfostr就是该板数据的哈希值。
hotinfoStr = title1 + url1 + hotinfoStr
}
}
//将哈希值加密
hashStr := tools.Sha256Hash(hotinfoStr)
6:保存/获取数据
//从redis获取字符串形式的baidu_info的值,这里存的是加密过的hotinfostr
//model.RedisClient.Get用于从redis里面获取值
value, err := model.RedisClient.Get(context.Background(), "baidu_hot").Result()
//获取值分情况讨论
//1.err == redis.Nil 表示查询的键在redis里面不存在
//2.err != nil 报错了
//3.hashstr != value 获取到的数据产生了变化,因此哈希值产生了变化,这时候需要重新设置redis里面的哈希值并更新数据库中的数据
//4,hash = value获取到的数据与原来相比并没有产生变化,因此哈希值没有变化,所以redis的值不需要改变,但是,由于时间发生了变化,updateVer是时间戳,所以需要更新UpdateVer和updatedtime
if hashStr != value {
err = model.RedisClient.Set(context.Background(), "baidu_hot", hashStr, 0).Err()
if err != nil {
logrus.Error("百度:更新redis中hashstr的值失败", err)
}
err = model.Conn.Create(data).Error
if err != nil {
logrus.Error("百度:将新数据写入数据库失败", err)
} else {
var maxUpdateVer int64
var BaiduSlice []BaiDu
err = model.Conn.Model(&BaiDu{}).Select("MAX(update_ver) as max_update_ver").Scan(&maxUpdateVer).Error
model.Conn.Where("update_ver = ?", maxUpdateVer).Find(&BaiduSlice)
for _, record := range BaiduSlice {
record.UpdateVer = time.Now().Unix()
record.UpdatedTime = time.Now()
err := model.Conn.Save(&record).Error
if err != nil {
logrus.Error("更新百度信息失败", err)
return
}
2:爬取json数据
1:初始化,使用viper配置文件,包含网址的url
func init() {
//使用viper配置所需参数的文件
viper.SetConfigName("config.yaml") // 配置文件名(不含扩展名)
viper.SetConfigType("yaml") // 配置文件类型
viper.AddConfigPath(".") // 配置文件所在路径
err := viper.ReadInConfig()
if err != nil {
panic("配置文件读取失败")
}
}
2:建立连接;并使用defer关闭连接
//使用http.Get请求数据
//1.viper.GetString,使用viper技术去获取配置好的url,以免需要修改多处。
client := &http.Client{}
request, err := http.NewRequest("GET", viper.GetString("hot_api.bilibili"), nil)
if err != nil {
logrus.Error("请求bilibili数据失败:", err)
return
}
//添加请求头
//1. request.Header.Add是go语言标准库中的一个方法,用于向http请求头中添加一个键值对
//2.发起请求,使用response,err = client.DO(request)
//3.同时使用defer关闭连接
request.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
response, err := client.Do(request)
defer response.Body.Close()
3:读取响应内容
//在步骤2中已经把请求到的数据赋给了response
//1.这里使用go语言标准库的ioutil.ReadAll函数读取response.body文件。
//2.需要注意的是,自 Go 1.16 起,ioutil 包的一些功能已经被整合到 io 和 os 包中。ioutil.ReadAll 现在可以直接用 io.ReadAll 代替:
//3.ioutil.ReadAll函数的作用是读取一个io.Reader 接口中的所有数据,直到遇到 EOF(文件结束),并返回读取到的字节切片和可能遇到的错误。 func ReadAll(r io.Reader) ([]byte, error)
body, err := ioutil.ReadAll(response.Body)
if err != nil {
logrus.Error("bilibili:读取响应内容失败:", err)
return
}
//因为json数据是这里的body实际上是字节流或者二进制所以需要解码
//1.使用json.Unmarshal方法就可以转换成json数据了
var ret Ret
_ = json.Unmarshal(body, &ret)
4:提取数据,并生成哈希值(json数据不需要将文件加载到goquery,可以直接提取数据)
//创建一个事先已经定义好的结构图的切片,用来存储数据
//1.UpdateVer是时间戳
//2.hotinfostr是字符串,用于标识唯一热搜数据
//3.这里的遍历过程,range ret.Data.Trending.List这些数据是与爬取的json数据对应的,所以在定义的时候要与爬取的数据对应
//4.先根据json数据创建结构体,用来表示爬取的数据,然后创建一个预先定义的要存储到数据库的结构体,将爬取到的数据赋给要存储的数据,再加上自己需要的数据。
data := make([]*BiliBili, 0)
now := time.Now().Unix()
var hotinfoStr string
for _, list := range ret.Data.Trending.List {
tmp := BiliBili{
UpdateVer: now,
Title: list.ShowName,
Icon: list.Icon,
KeyWord: list.KeyWord,
CreatedTime: time.Now(),
UpdatedTime: time.Now(),
}
//遍历数据,将每一条热搜添加到定义的切片里
//生成哈希,迭代方法,用来表示每一板的热搜数据
data = append(data, &tmp)
hotinfoStr = list.ShowName + list.Icon + hotinfoStr
}
hashStr := tools.Sha256Hash(hotinfoStr)
5:保存/获取数据
//从redis获取字符串形式的baidu_info的值,这里存的是加密过的hotinfostr
//model.RedisClient.Get用于从redis里面获取值
value, err := model.RedisClient.Get(context.Background(), "bilibili_hot").Result()
model.Conn.Create(data)
//获取值分情况讨论
//1.err == redis.Nil 表示查询的键在redis里面不存在
//2.err != nil 报错了
//3.hashstr != value 获取到的数据产生了变化,因此哈希值产生了变化,这时候需要重新设置redis里面的哈希值并更新数据库中的数据
//4,hash = value获取到的数据与原来相比并没有产生变化,因此哈希值没有变化,所以redis的值不需要改变,但是,由于时间发生了变化,updateVer是时间戳,所以需要更新UpdateVer和updatedtime
if hashStr != value {
err = model.RedisClient.Set(context.Background(), "bilibili_hot", hashStr, 0).Err()
if err != nil {
logrus.Error("bilibili:更新redis中hashstr的值失败", err)
}
//将数据存储到redis
err = model.Conn.Create(data).Error
if err != nil {
logrus.Error("bilibili:将新数据写入数据库失败", err)
} else {
var maxUpdateVer int64
var BilibiliSlice []BiliBili
err = model.Conn.Model(&BaiDu{}).Select("MAX(update_ver) as max_update_ver").Scan(&maxUpdateVer).Error
model.Conn.Where("update_ver = ?", maxUpdateVer).Find(&BilibiliSlice)
for _, record := range BilibiliSlice {
record.UpdateVer = time.Now().Unix()
record.UpdatedTime = time.Now()
err := model.Conn.Save(&record).Error
if err != nil {
logrus.Error("更新bilibili信息失败", err)
return
}