相关技术 Golang+chromedp+goquery

技术简介

Chromedp

chromedp是一个更快、更简单的Golang库用于调用支持Chrome DevTools协议的浏览器,同时不需要额外的依赖(例如Selenium和PhantomJS)--- 需使用到Chrome浏览器

相关文档:https://pkg.go.dev/github.com/chromedp/chromedp
Chrome和Golang都与Google有着相当密切的关系,而Chrome DevTools其实就是Chrome浏览器按下F12之后的控制终端
下载:go get -u github.com/chromedp/chromedp
基本我们可以熟悉最常用的几个方法了:

  • chromedp.NewContext() 初始化chromedp的上下文,后续这个页面都使用这个上下文进行操作
  • chromedp.Flag("headless", true) 给 chromedp设置参数,设置为 无头模式 headless ,无头模式即Chrome浏览器的无GUI的命令行版浏览器,但功能上和我们平常使用的chrome没有区别,若该参数不设置为true,则在程序运行的时候,chromedp会拉取我们环境中的chrome浏览器,显示页面
  • chromedp.Flag("blink-settings", "imagesEnabled=false")设置为不显示图片
  • chromedp.UserAgent(Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36) UserAgent是用于设置默认用户代理头的命令行选项。
  • chromedp.Run() 运行一个chrome的一系列操作
  • chromedp.Sleep() 延时
  • chromedp.Navigate() 将浏览器导航到某个页面
  • chromedp.WaitVisible() 等候某个元素可见,再继续执行。
  • chromedp.Click() 模拟鼠标点击某个元素
  • chromedp.Value() 获取某个元素的value值
  • chromedp.ActionFunc() 再当前页面执行某些自定义函数
  • chromedp.Text() 读取某个元素的text值
  • chromedp.Evaluate() 执行某个js,相当于控制台输入js
  • network.SetExtraHTTPHeaders() 截取请求,额外增加header头
  • chromedp.SendKeys() 模拟键盘操作,输入字符
  • chromedp.Nodes() 根据xpath获取某些元素,并存储进入数组
  • chromedp.NewRemoteAllocator
  • chromedp.OuterHTML() 获取元素的outer html
  • chromedp.Screenshot() 根据某个元素截图
  • page.CaptureScreenshot() 截取整个页面的元素
  • chromedp.Submit() 提交某个表单
  • chromedp.WaitNotPresent() 等候某个元素不存在,比如“正在搜索。。。”
  • chromedp.Tasks{} 一系列Action组成的任务

Goquery

在写爬虫的时候,想要对HTML内容进行选择和查找匹配时通常是不直接写正则表达式的:因为正则表达式可读性和可维护性比较差。用Python写爬虫这方面可选择的方案非常多了,其中有一个被开发者常用的库pyquery,而Golang也有对应的goquery,可以说goquery是jQuery的Golang版本实现。借用jQueryCSS选择器的语法可以非常方面的实现内容匹配和查找。

go get https://github.com/PuerkitoBio/goquery

示例代码(总)

package main

import (
    "context"
    "fmt"
    "log"
    "strings"
    "time"

    "github.com/PuerkitoBio/goquery"
    "github.com/chromedp/chromedp"
)

// 全局数据--列表
var htmlContent string

// 全局数据--详情
var infoContent string

// 初始化chromedb
func initchromedp() (c context.Context) {
    // 初始化chromedp
    options := []chromedp.ExecAllocatorOption{
        chromedp.Flag("headless", true), // debug使用
        chromedp.Flag("blink-settings", "imagesEnabled=false"),
        chromedp.UserAgent(`Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36`),
    }
    //初始化参数,先传一个空的数据
    options = append(chromedp.DefaultExecAllocatorOptions[:], options...)
    c, _ = chromedp.NewExecAllocator(context.Background(), options...)
    return c
}

//获取网站上爬取的数据
func GetHttpHtmlContent(url string, selector string, sel interface{}) {
    // 初始化chromedb
    c := initchromedp()
    // create context
    chromeCtx1, cancel := chromedp.NewContext(c, chromedp.WithLogf(log.Printf))
    chromeCtx2, cancel := chromedp.NewContext(c, chromedp.WithLogf(log.Printf))
    // 执行一个空task, 用提前创建Chrome实例
    // chromedp.Run(chromeCtx1, make([]chromedp.Action, 0, 1)...)

    // 创建一个上下文,超时时间为40s---首页
    timeoutCtx1, cancel := context.WithTimeout(chromeCtx1, 40*time.Second)
    // 创建一个上下文,超时时间为40s---列表
    timeoutCtx2, cancel := context.WithTimeout(chromeCtx1, 40*time.Second)
    // 创建一个上下文,超时时间为40s---详情
    timeoutCtx3, cancel := context.WithTimeout(chromeCtx2, 40*time.Second)

    // 第一步:初始化首页  爬取首页数据
    err := chromedp.Run(timeoutCtx1, initvisitweb(url, selector, sel))
    if err != nil {
        log.Fatal(err)
    }
    // 获取首页数据
    doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
    if err != nil {
        log.Fatal(err)
    }
    // 循环li标签
    doc.Find("li").Each(func(i int, s *goquery.Selection) {
        // 获取a标签中的标题数据
        band := s.Find("a").Text()
        // 获取span标签中的时间数据
        time := s.Find("span").Text()
        // 获取详情链接
        href, _ := s.Find("a").Attr("href")
        // 获取某消息详情html数据
        err = chromedp.Run(timeoutCtx3, GetInfo(href))
        infodoc, err := goquery.NewDocumentFromReader(strings.NewReader(infoContent))
        if err != nil {
            log.Fatal(err)
        }
        // 详情标题
        // title := infodoc.Find("h1").Text()
        // 详情基础信息---html
        em, _ := infodoc.Find("h6").Find("em").Html()
        // 详情内容---html
        // content, _ := infodoc.Find(".content").Html()
        fmt.Println(i, band, time, href, em)
    })

    defer cancel()
    // 循环翻页
    for i := 0; i < 2; i++ {
        //执行下一页
        err = chromedp.Run(timeoutCtx2, DoCrawler(selector, sel)) //执行爬虫任务

        doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
        if err != nil {
            log.Fatal(err)
        }
        doc.Find("li").Each(func(i int, s *goquery.Selection) {
            // 获取a标签中的标题数据
            band := s.Find("a").Text()
            // 获取span标签中的时间数据
            time := s.Find("span").Text()
            // 获取详情链接
            href, _ := s.Find("a").Attr("href")
            // 获取某消息详情html数据
            err = chromedp.Run(timeoutCtx3, GetInfo(href))
            infodoc, err := goquery.NewDocumentFromReader(strings.NewReader(infoContent))
            if err != nil {
                log.Fatal(err)
            }
            // 详情标题
            // title := infodoc.Find("h1").Text()
            // 详情基础信息---html
            em, _ := infodoc.Find("h6").Find("em").Html()
            // 详情内容---html
            // content, _ := infodoc.Find(".content").Html()
            fmt.Println(i, band, time, href, em)
        })
    }

    // return htmlContent, nil
}

// 初始化访问
func initvisitweb(url string, selector string, sel interface{}) chromedp.Tasks {
    return chromedp.Tasks{
        chromedp.Navigate(url),                                   // 将浏览器导航到某个页面
        chromedp.WaitVisible(selector),                           // 等候某个元素可见,再继续执行。
        chromedp.OuterHTML(sel, &htmlContent, chromedp.ByJSPath), //获取指定标签的html
    }
}

// 任务 主要执行翻页功能和或者html
func DoCrawler(selector string, sel interface{}) chromedp.Tasks {
    return chromedp.Tasks{
        chromedp.Click(`#pageDec > span:nth-child(3)`),           //点击翻页
        chromedp.Sleep(1 * time.Second),                          // 延时
        chromedp.WaitVisible(selector),                           // 等候某个元素可见,再继续执行。
        chromedp.OuterHTML(sel, &htmlContent, chromedp.ByJSPath), //获取指定标签的html
    }
}

// 获取详情
func GetInfo(href string) chromedp.Tasks {
    return chromedp.Tasks{
        chromedp.Navigate(href), // 将浏览器导航到某个页面
        chromedp.WaitVisible("body > div.wrap > div.main > div.mt15.details-box > div.details-main"),   // 等候某个元素可见,再继续执行。
        chromedp.OuterHTML(`document.querySelector(".details-main")`, &infoContent, chromedp.ByJSPath), //获取指定标签的html
    }
}

func main() {
    // 获取html
    GetHttpHtmlContent("https://www.henan.gov.cn/ywdt/szf/", "body > div.wrap > div.main > div.mt15.list-box", `document.querySelector(".mt15")`)
}

代码解析

1. 入口

参数分别为:
        https://www.henan.gov.cn/ywdt/szf/   爬取的地址
        body > div.wrap > div.main > div.mt15.list-box   获取的元素  (参考下面翻页函数)
        document.querySelector(".mt15")   从class="mt15"中获取**
func main() {
    // 获取html
    GetHttpHtmlContent("https://www.henan.gov.cn/ywdt/szf/", "body > div.wrap > div.main > div.mt15.list-box", `document.querySelector(".mt15")`)
}

2. 初始化Chromedp

初始化chromedp操作,重新设置chromedp使用"有头"的方式打开,以便于我们进行debug。"imagesEnabled=false"页面不显示图片,返回c context.Contex

// 初始化chromedp
func initchromedp() (c context.Context) {
    // 初始化chromedp
    options := []chromedp.ExecAllocatorOption{
        chromedp.Flag("headless", true), // debug使用
        chromedp.Flag("blink-settings", "imagesEnabled=false"),
        chromedp.UserAgent(`Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36`),
    }
    //初始化参数,先传一个空的数据
    options = append(chromedp.DefaultExecAllocatorOptions[:], options...)
    c, _ = chromedp.NewExecAllocator(context.Background(), options...)
    return c
}

3. 初始化页面

初始化访问,获取某网页首页html数据
imagePreview.png

// 初始化访问
func initvisitweb(url string, selector string, sel interface{}) chromedp.Tasks {
    return chromedp.Tasks{
        chromedp.Navigate(url),                                   // 将浏览器导航到某个页面
        chromedp.WaitVisible(selector),                           // 等候某个元素可见,再继续执行。
        chromedp.OuterHTML(sel, &htmlContent, chromedp.ByJSPath), //获取指定标签的html
    }
}

4. 翻页任务

处理翻页,操作点击下一页等待元素可见,获取下一页数据。
imagePreview (1).png

#pageDec > span:nth-child(3):页面F12,右键元素检查>右键标签Copy>Copy selector获取
imagePreview (2).png


// 任务 主要执行翻页功能和或者html
func DoCrawler(selector string, sel interface{}) chromedp.Tasks {
    return chromedp.Tasks{
        chromedp.Click(`#pageDec > span:nth-child(3)`),           //点击翻页
        chromedp.Sleep(1 * time.Second),                          // 延时
        chromedp.WaitVisible(selector),                           // 等候某个元素可见,再继续执行。
        chromedp.OuterHTML(sel, &htmlContent, chromedp.ByJSPath), //获取指定标签的html
    }
}

5. 获取详情

跳转详情页面,等待元素可见,获取指定html
imagePreview (3).png

// 获取详情
func GetInfo(href string) chromedp.Tasks {
    return chromedp.Tasks{
        chromedp.Navigate(href), // 将浏览器导航到某个页面
        chromedp.WaitVisible("body > div.wrap > div.main > div.mt15.details-box > div.details-main"),   // 等候某个元素可见,再继续执行。
        chromedp.OuterHTML(`document.querySelector(".details-main")`, &infoContent, chromedp.ByJSPath), //获取指定标签的html
    }
}

6. 总代码逻辑:

GetHttpHtmlContent函数先调用initchromedp初始化,创建三个上下文,第一个用于进入首页,第二个用于翻页,第三个用于查看详情

1. 操作浏览器进入首页(此配置不会调出真实浏览器),获取首页数据,根据标题href获取详情数据
2. 第一步执行过后,当前页还是在首页,在循环里调用翻页操作获取下一页的数据以及下一页所有文章详情
3. 注:循环次数为页面的页数(后期可优化)
//获取网站上爬取的数据
func GetHttpHtmlContent(url string, selector string, sel interface{}) {
    // 初始化chromedb
    c := initchromedp()
    // 初始化上下文
    chromeCtx1, cancel := chromedp.NewContext(c, chromedp.WithLogf(log.Printf))
    chromeCtx2, cancel := chromedp.NewContext(c, chromedp.WithLogf(log.Printf))
    // 执行一个空task, 用提前创建Chrome实例
    // chromedp.Run(chromeCtx1, make([]chromedp.Action, 0, 1)...)

    // 创建一个上下文,超时时间为40s---首页
    timeoutCtx1, cancel := context.WithTimeout(chromeCtx1, 40*time.Second)
    // 创建一个上下文,超时时间为40s---列表
    timeoutCtx2, cancel := context.WithTimeout(chromeCtx1, 40*time.Second)
    // 创建一个上下文,超时时间为40s---详情
    timeoutCtx3, cancel := context.WithTimeout(chromeCtx2, 40*time.Second)

    // 第一步:初始化首页  爬取首页数据
    err := chromedp.Run(timeoutCtx1, initvisitweb(url, selector, sel))
    if err != nil {
        log.Fatal(err)
    }
    // 获取首页数据
    doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
    if err != nil {
        log.Fatal(err)
    }
    // 循环li标签
    doc.Find("li").Each(func(i int, s *goquery.Selection) {
        // 获取a标签中的标题数据
        band := s.Find("a").Text()
        // 获取span标签中的时间数据
        time := s.Find("span").Text()
        // 获取详情链接
        href, _ := s.Find("a").Attr("href")
        // 获取某消息详情html数据
        err = chromedp.Run(timeoutCtx3, GetInfo(href))
        infodoc, err := goquery.NewDocumentFromReader(strings.NewReader(infoContent))
        if err != nil {
            log.Fatal(err)
        }
        // 详情标题
        // title := infodoc.Find("h1").Text()
        // 详情基础信息---html
        em, _ := infodoc.Find("h6").Find("em").Html()
        // 详情内容---html
        // content, _ := infodoc.Find(".content").Html()
        fmt.Println(i, band, time, href, em)
    })

    defer cancel()
    // 循环翻页
    for i := 0; i < 2; i++ {
        //执行下一页
        err = chromedp.Run(timeoutCtx2, DoCrawler(selector, sel)) //执行爬虫任务

        doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
        if err != nil {
            log.Fatal(err)
        }
        doc.Find("li").Each(func(i int, s *goquery.Selection) {
            // 获取a标签中的标题数据
            band := s.Find("a").Text()
            // 获取span标签中的时间数据
            time := s.Find("span").Text()
            // 获取详情链接
            href, _ := s.Find("a").Attr("href")
            // 获取某消息详情html数据
            err = chromedp.Run(timeoutCtx3, GetInfo(href))
            infodoc, err := goquery.NewDocumentFromReader(strings.NewReader(infoContent))
            if err != nil {
                log.Fatal(err)
            }
            // 详情标题
            // title := infodoc.Find("h1").Text()
            // 详情基础信息---html
            em, _ := infodoc.Find("h6").Find("em").Html()
            // 详情内容---html
            // content, _ := infodoc.Find(".content").Html()
            fmt.Println(i, band, time, href, em)
        })
    }

    // return htmlContent, nil
}
Last modification:August 11th, 2021 at 08:09 pm
如果觉得我的文章对你有用,请随意赞赏