go爬虫简单实践
使用go语言原生的net包实现简单爬虫
网络爬虫大致步骤
①、创建请求代码:包括设置请求url,设置代理服务器,设置请求头
②、执行请求,获取响应结果,关闭请求流
③、从响应结果中提取有效数据
④、将数据保存到本地,比如excel表
案例需求
需求:某网站的公开了国内某市各个县的的日均降雨量,网页内容就是某天的降雨量表,在请求url中包含了日期字段,可以通过修改请求url中的日期字段来获取不同日期的降雨量,现需要爬取该网站上若干年的降雨数据。
代码实现
首先创建单次的请求代码,也就是只获取一天的数据,可以借助http请求代码生成网站去生成对应代码。
具体步骤为:按F12打开浏览器开发者模式,刷新网页内容,找到返回了你想要的数据的那个http请求,右键这个请求,点复制为curl(bash),然后粘贴到http请求代码生成网站中,选择go语言,既可获取go语言的请求代码。
// 单次查询请求
func queryData(date string) {
fmt.Println("=========================================================" + "正在获取:" + date + "的数据=========================================================")
client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
//设置请求url,依照具体网页而定
var url = "http://XXX/XXX/XXX?date=" + date + "+type=1"
//创建请求
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatal(err)
}
//设置请求头
req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Referer", "http://http://XXX/XXX/XXX")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
//执行请求
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
//关闭请求流
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
var responsejson DictResponse
//将json格式的bodyText反序列化为struct格式
err = json.Unmarshal(bodyText, &responsejson)
if err != nil {
log.Fatal(err)
}
//打印responsejson中的具体字段
for _, item := range responsejson.Rows {
fmt.Println(item)
}
//通过save函数保存在本地
save(responsejson, date)
}
当然,上述代码的反序列化操作前要定义好结构体,这里假设需要的数据包含【县区】、【日降雨】、【时间】这三个字段,其中的Rows数组的长度有多长就代表了一个表格里有多少行数据
type DictResponse struct {
Rows []struct {
COUNTY string `json:"COUNTY"` //所在县
RAINFALL string `json:"RAINFALL"` //降雨量
DATE string `json:"DATE"` //日期
} `json:"rows"`
}
然后具体定义save函数,用于把一次请求的数据保存到本地,这里以保存为excel表格数据为例,用到了第三方的库“github.com/360EntSecGroup-Skylar/excelize”库,需要提前安装。
// 保存为excel文件
func save(resp DictResponse, date string) {
// 创建一个新的Excel文件
f := excelize.NewFile()
// 创建一个名为“Sheet1”的工作表
const sheetName = "Sheet1"
index := f.NewSheet(sheetName)
//设置第一行的标题
f.SetCellValue(sheetName, "A1", "县区")
f.SetCellValue(sheetName, "B1", "降雨量")
f.SetCellValue(sheetName, "C1", "日期")
row := 2 // 从第二行开始写具体数据
//将各个县的降雨数据写入表中
for _, x := range resp.Rows {
f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), x.COUNTY)
f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), x.RAINFALL)
f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), x.DATE)
row += 1
}
// 设置默认工作表
f.SetActiveSheet(index)
//按年份划分文件夹
end := strings.Index(date, "-")
dir := date[:end]
//父文件夹不存在则创建
if PathExists(dir) == false {
os.MkdirAll(dir, os.ModePerm)
}
fmt.Println(dir)
// 将工作表保存为文件
if err := f.SaveAs("./" + dir + "/data_" + date + ".xlsx"); err != nil {
panic(err)
}
fmt.Println("写入" + "data_" + date + ".xlsx" + "成功!")
}
// PathExists 判断一个文件或文件夹是否存在
// 输入文件路径,根据返回的bool值来判断文件或文件夹是否存在
func PathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
最后,为了方便连续获取某一时间段的降雨数据,以起止时间作为参数,写一个批量请求函数,按照时间顺序依次请求每天的数据,每次请求后休眠0-1秒
func queryDataAll(startDate string, endDate string) {
// 设置时间模板格式
myLayout := time.DateOnly
// 转为time
start, _ := time.Parse(myLayout, startDate)
end, _ := time.Parse(myLayout, endDate)
fmt.Println("开始时间:" + start.String() + " 结束时间:" + end.String())
for current := start; current.Before(end) == true || current.Equal(end); current = current.Add(time.Hour * 24) {
temString := current.Format(myLayout)
//执行请求
queryData(temString)
//随机休眠0-1s
rand.Seed(time.Now().UnixNano())
sleep_time := rand.Float32()
fmt.Println("休眠", sleep_time, "秒")
time.Sleep((time.Duration(sleep_time)) * (time.Second)) //休眠
}
}
最后把如上代码整个到一个go文件汇中,并添如下的导包及main函数定义既可。
package main
import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
"net/http"
"os"
"strings"
"time"
"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
queryDataAll("1990-01-01", "1999-12-31")
}
此时在main函数中调用queryDataAll函数即可实现爬取某一时间段的数据,并保存在本地。