介绍
- 本章节使用OnResponse进行返回网页数据
- 使用xpath定位数据;
- 推荐htmlquery
demo
package main
import (
"fmt"
"github.com/antchfx/htmlquery"
"github.com/gocolly/colly"
"github.com/gocolly/colly/extensions"
"gopkg.in/xmlpath.v2"
"log"
"os"
"strings"
"time"
)
/*
请求执行之前调用
- OnRequest
响应返回之后调用
- OnResponse
监听执行 selector
- OnHTML
监听执行 selector
- OnXML
错误回调
- OnError
完成抓取后执行,完成所有工作后执行
- OnScraped
取消监听,参数为 selector 字符串
- OnHTMLDetach
取消监听,参数为 selector 字符串
- OnXMLDetach
*/
func init() {
// 日志配置
// 指定输出日志的前缀
log.SetPrefix("【UserCenter】")
// Ldate:时间
// Lshortfile:文件名+源代码
/*
const (
Ldate = 1 << iota //日期示例: 2009/01/23
Ltime //时间示例: 01:23:23
Lmicroseconds //毫秒示例: 01:23:23.123123.
Llongfile //绝对路径和行号: /a/b/c/d.go:23
Lshortfile //文件和行号: d.go:23.
LUTC //日期时间转为0时区的
LstdFlags = Ldate | Ltime //Go提供的标准抬头信息
)
*/
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
// 声明结构体
type Session struct {
session *colly.Collector
file *os.File
}
// 初始化
func (c *Session) Init() *colly.Collector {
// 实例化默认收集器
c.session = colly.NewCollector()
// 仅访问域
c.session.AllowedDomains = []string{"quotes.toscrape.com"}
// 允许重复访问
c.session.AllowURLRevisit = true
// 表示抓取时异步的
// c.session.Async = true
// 模拟浏览器
c.session.UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36"
// 随机UserAgent
extensions.RandomUserAgent(c.session)
// 限制采集规则
/*
在Colly里面非常方便控制并发度,只抓取符合某个(些)规则的URLS
colly.LimitRule{DomainGlob: "*.douban.*", Parallelism: 5},表示限制只抓取域名是douban(域名后缀和二级域名不限制)的地址,当然还支持正则匹配某些符合的 URLS
Limit方法中也限制了并发是5。为什么要控制并发度呢?因为抓取的瓶颈往往来自对方网站的抓取频率的限制,如果在一段时间内达到某个抓取频率很容易被封,所以我们要控制抓取的频率。
另外为了不给对方网站带来额外的压力和资源消耗,也应该控制你的抓取机制。
*/
err := c.session.Limit(&colly.LimitRule{
// Filter domains affected by this rule
// 筛选受此规则影响的域
DomainGlob: "quotes.toscrape.com/*",
// Set a delay between requests to these domains
// 设置对这些域的请求之间的延迟
Delay: 1 * time.Second,
// Add an additional random delay
// 添加额外的随机延迟
RandomDelay: 1 * time.Second,
// 设置并发
Parallelism: 5,
})
if err != nil {
fmt.Println(err)
}
return c.session
}
// 开始爬取
func (c *Session) Visit(url string) error {
// 开始爬取 url
err := c.session.Visit(url)
if err != nil {
fmt.Println(err)
return err
}
return nil
}
// 访问下一页
// OnHTML selector
func (c *Session) getNext2(page *string) error {
// 调用回调函数,获取标签的属性
c.session.OnHTML(".pager .next a", func(e *colly.HTMLElement) {
// 获取属性值
link := e.Attr("href")
*page = link
})
// 错误回调
var err1 error = nil
c.session.OnError(func(_ *colly.Response, err error) {
fmt.Println("Something went wrong:", err)
err1 = err
})
if err1 != nil {
fmt.Println(err1)
return err1
}
// 完成抓取后执行,完成所有工作后执行
// c.session.OnScraped(func(r *colly.Response) {
// fmt.Println("Finished", r.Request.URL)
// })
return nil
}
// htmlquery xpath 推荐
func (c *Session) getNext(page *string) error {
// 完成抓取后执行,完成所有工作后执行
// c.session.OnScraped(func(r *colly.Response) {
// fmt.Println("Finished", r.Request.URL)
// })
// 收到响应后
c.session.OnResponse(func(r *colly.Response) {
doc, err := htmlquery.Parse(strings.NewReader(string(r.Body)))
if err != nil {
log.Fatal(err)
}
link := htmlquery.FindOne(doc, `//ul[@class="pager"]/li[@class="next"]/a/@href`)
*page = htmlquery.InnerText(link)
// fmt.Println(*page)
})
// 错误回调
var err1 error = nil
c.session.OnError(func(_ *colly.Response, err error) {
fmt.Println("Something went wrong:", err)
err1 = err
})
if err1 != nil {
fmt.Println(err1)
return err1
}
return nil
}
// gopkg.in/xmlpath.v2 xpath
func (c *Session) getNext3(page *string) error {
// 完成抓取后执行,完成所有工作后执行
// c.session.OnScraped(func(r *colly.Response) {
// fmt.Println("Finished", r.Request.URL)
// })
// 收到响应后
c.session.OnResponse(func(r *colly.Response) {
doc, err := xmlpath.ParseHTML(strings.NewReader(string(r.Body)))
if err != nil {
log.Fatal(err)
}
path := xmlpath.MustCompile(`//ul[@class="pager"]/li[@class="next"]/a/@href`)
if link, ok := path.String(doc); ok {
*page = link
}
})
// 错误回调
var err1 error = nil
c.session.OnError(func(_ *colly.Response, err error) {
fmt.Println("Something went wrong:", err)
err1 = err
})
if err1 != nil {
fmt.Println(err1)
return err1
}
return nil
}
// 解析页面 OnHTML
func (c *Session) getParse2() {
// 在每个 a 标签 href 属性 调用回调函数
c.session.OnHTML(".row .col-md-8 .quote", func(e *colly.HTMLElement) {
// text
text := e.ChildText("span.text")
// fmt.Println("text: ", text)
// author
author := e.ChildText("span .author")
// fmt.Println("author: ", author)
// tags
var tags []string
e.ForEach(".tags a", func(i int, e *colly.HTMLElement) {
text := e.Text
tags = append(tags, text)
})
// fmt.Println("tags: ", tags)
// 保存
c.save(text, author, tags)
})
// 收到响应后
c.session.OnResponse(func(r *colly.Response) {
if r.StatusCode != 200 {
log.Println(r.StatusCode)
return
}
})
}
// 解析页面 htmlquery xpath
func (c *Session) getParse() {
// 收到响应后
c.session.OnResponse(func(r *colly.Response) {
if r.StatusCode != 200 {
log.Println(r.StatusCode)
return
}
doc, err := htmlquery.Parse(strings.NewReader(string(r.Body)))
if err != nil {
log.Fatal(err)
}
quoteNodes := htmlquery.Find(doc, `//div[@class="row"]/div[@class="col-md-8"]/div[@class="quote"]`)
for _, node := range quoteNodes {
textNode := htmlquery.FindOne(node, `./span[@class="text"]/text()`)
text := htmlquery.InnerText(textNode)
authorNode := htmlquery.FindOne(node, `./span/small[@class="author"]/text()`)
author := htmlquery.InnerText(authorNode)
var tags []string
aNodes := htmlquery.Find(node, `./div[@class="tags"]/a`)
for _, a := range aNodes {
tagNode := htmlquery.FindOne(a, `./text()`)
tag := htmlquery.InnerText(tagNode)
tags = append(tags, tag)
}
fmt.Println(text)
fmt.Println(author)
fmt.Println(tags)
// 保存
c.save(text, author, tags)
}
})
}
// 翻页
func (c *Session) NextPage() {
// 访问地址
url := "http://quotes.toscrape.com"
baseUrl := "http://quotes.toscrape.com"
page := ""
for i := 0; i <= 2; i++ {
fmt.Println("start url: ", url)
// 解析页面
c.getParse()
// 获取下一页地址
err1 := c.getNext(&page)
if err1 != nil {
break
}
err2 := c.Visit(url)
if err2 != nil {
break
}
url = baseUrl + page
}
}
// 保存
func (c *Session) save(text string, author string, tags []string) {
newTags := strings.Join(tags, " ")
// fmt.Println(newTags)
_, _ = c.file.Write([]byte(text + "\n"))
_, _ = c.file.Write([]byte(author + "\n"))
_, _ = c.file.Write([]byte(newTags + "\n"))
_, _ = c.file.Write([]byte(strings.Repeat("*", 20)))
_, _ = c.file.Write([]byte("\n\n"))
}
func main() {
s := &Session{}
// 初始化
s.Init()
// 读写模式打开,写入追加
s.file, _ = os.OpenFile("test.txt", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0777)
defer func() {
if err := s.file.Close(); err != nil {
fmt.Println(err)
}
}()
// 翻页
s.NextPage()
// 采集等待结束
s.session.Wait()
fmt.Println("程序运行结束!")
}