1.介绍
CollyGolangWebcookie&session
2.安装
// 下载最新版本
go get -u github.com/gocolly/colly/v2
3. 快速入门
3.1 语法模板
// 简单使用模板示例
func collyUseTemplate() {
// 创建采集器对象
collector := colly.NewCollector()
// 发起请求之前调用
collector.OnRequest(func(request *colly.Request) {
fmt.Println("发起请求之前调用...")
})
// 请求期间发生错误,则调用
collector.OnError(func(response *colly.Response, err error) {
fmt.Println("请求期间发生错误,则调用:",err)
})
// 收到响应后调用
collector.OnResponse(func(response *colly.Response) {
fmt.Println("收到响应后调用:",response.Request.URL)
})
//OnResponse如果收到的内容是HTML ,则在之后调用
collector.OnHTML("#position_shares table", func(element *colly.HTMLElement) {
// todo 解析html内容
})
// url:请求具体的地址
err := collector.Visit("请求具体的地址")
if err != nil {
fmt.Println("具体错误:",err)
}
}
3.2 回调函数说明
OnResponseCalled after OnXML callbacks
3.3 回调函数注册顺序
3.4 使用示例
需求: 抓取豆瓣小说榜单数据
1. 分析网页
2. 逻辑分析
ululli
3. 代码实现
4. 运行结果
4. 配置采集器
4.1 配置预览
4.2 部分配置说明
AllowedDomainsForbidden domainAllowURLRevisitAsyncWait()DebuggerMaxDepthUserAgentMaxBodySizeIgnoreRobotsTxtrobots.txt
4.3 创建采集器
collector := colly.NewCollector(
colly.AllowedDomains("www.baidu.com",".baidu.com"),//白名单域名
colly.AllowURLRevisit(),//允许对同一 URL 进行多次下载
colly.Async(true),//设置为异步请求
colly.Debugger(&debug.LogDebugger{}),// 开启debug
colly.MaxDepth(2),//爬取页面深度,最多为两层
colly.MaxBodySize(1024 * 1024),//响应正文最大字节数
colly.UserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"),
colly.IgnoreRobotsTxt(),//忽略目标机器中的`robots.txt`声明
)
5. 常用解析函数
collyHtmlcolly.HTMLElementgoquery.Selection
// colly.HTMLElement结构体
type HTMLElement struct {
Name string
Text string
attributes []html.Attribute
Request *Request
Response *Response
// 对goquery封装
DOM *goquery.Selection
Index int
}
5.1 Attr
1. 函数说明
//返回当前元素的属性
func (h *HTMLElement) Attr(k string) string
2. 使用示例
// 返回当前元素的属性
func TestUseAttr(t *testing.T) {
collector := colly.NewCollector()
// 定位到div[class='nav-logo'] > a标签元素
collector.OnHTML("div[class='nav-logo'] > a", func(element *colly.HTMLElement) {
fmt.Printf("href:%v\n",element.Attr("href"))
})
_ = collector.Visit("https://book.douban.com/tag/小说")
}
/**输出
=== RUN TestUseAttr
href:https://book.douban.com
--- PASS: TestUseAttr (0.66s)
PASS
*/
5.2 ChildAttr&ChildAttrs
1. 函数说明
// 返回`goquerySelector`选择的第一个子元素的`attrName`属性;
func (h *HTMLElement) ChildAttr(goquerySelector, attrName string) string
// 返回`goquerySelector`选择的所有子元素的`attrName`属性,以`[]string`返回;
func (h *HTMLElement) ChildAttrs(goquerySelector, attrName string) []string
2. 使用示例
func TestChildAttrMethod(t *testing.T) {
collector := colly.NewCollector()
collector.OnError(func(response *colly.Response, err error) {
fmt.Println("OnError",err)
})
// 解析Html
collector.OnHTML("body", func(element *colly.HTMLElement) {
// 获取第一个子元素(div)的class属性
fmt.Printf("ChildAttr:%v\n",element.ChildAttr("div","class"))
// 获取所有子元素(div)的class属性
fmt.Printf("ChildAttrs:%v\n",element.ChildAttrs("div","class"))
})
err := collector.Visit("https://liuqh.icu/a.html")
if err != nil {
fmt.Println("err",err)
}
}
/**输出
=== RUN TestChildAttrMethod
ChildAttr:div1
ChildAttrs:[div1 sub1 div2 div3]
--- PASS: TestChildAttrMethod (0.29s)
PASS
*/
5.3 ChildText & ChildTexts
1. 函数说明
// 拼接goquerySelector选择的子元素的文本内容并返回
func (h *HTMLElement) ChildText(goquerySelector string) string
// 返回goquerySelector选择的子元素的文本内容组成的切片,以[]string返回。
func (h *HTMLElement) ChildTexts(goquerySelector string) []string
2. 使用示例
a. 待解析的Html:
<html>
<head>
<title>测试</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div class="div1">
<div class="sub1">内容1</div>
</div>
<div class="div2">内容2</div>
<div class="div3">内容3</div>
</body>
</html>
b. 解析代码:
// 测试使用ChildText和ChildTexts
func TestChildTextMethod(t *testing.T) {
collector := colly.NewCollector()
collector.OnError(func(response *colly.Response, err error) {
fmt.Println("OnError",err)
})
//
collector.OnHTML("body", func(element *colly.HTMLElement) {
// 获取第一个子元素(div)的class属性
fmt.Printf("ChildText:%v\n",element.ChildText("div"))
// 获取所有子元素(div)的class属性
fmt.Printf("ChildTexts:%v\n",element.ChildTexts("div"))
})
err := collector.Visit("https://liuqh.icu/a.html")
if err != nil {
fmt.Println("err",err)
}
}
/**输出
=== RUN TestChildTextMethod
ChildText:内容1
内容1内容2内容3
ChildTexts:[内容1 内容1 内容2 内容3]
--- PASS: TestChildTextMethod (0.28s)
PASS
*/
5.4 ForEach
1. 函数说明
//对每个`goquerySelector`选择的子元素执行回调`callback`
func (h *HTMLElement) ForEach(goquerySelector string, callback func(int, *HTMLElement))
2. 使用示例
a. 待解析Html:
<html>
<head>
<title>测试</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<ul class="demo">
<li>
<span class="name">张三</span>
<span class="age">18</span>
<span class="home">北京</span>
</li>
<li>
<span class="name">李四</span>
<span class="age">22</span>
<span class="home">南京</span>
</li>
<li>
<span class="name">王五</span>
<span class="age">29</span>
<span class="home">天津</span>
</li>
</ul>
</body>
</html>
b. 解析代码:
func TestForeach(t *testing.T) {
collector := colly.NewCollector()
collector.OnHTML("ul[class='demo']", func(element *colly.HTMLElement) {
element.ForEach("li", func(_ int, el *colly.HTMLElement) {
name := el.ChildText("span[class='name']")
age := el.ChildText("span[class='age']")
home := el.ChildText("span[class='home']")
fmt.Printf("姓名: %s 年龄:%s 住址: %s \n",name,age,home)
})
})
_ = collector.Visit("https://liuqh.icu/a.html")
}
/**输出
=== RUN TestForeach
姓名: 张三 年龄:18 住址: 北京
姓名: 李四 年龄:22 住址: 南京
姓名: 王五 年龄:29 住址: 天津
--- PASS: TestForeach (0.36s)
PASS
*/
5.5 Unmarshal
1. 函数说明
// 通过给结构体字段指定 goquerySelector 格式的 tag,可以将HTMLElement对象Unmarshal 到一个结构体实例中
func (h *HTMLElement) Unmarshal(v interface{}) error
2. 使用示例
a. 待解析Html
<html>
<head>
<title>测试</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div class="book">
<span class="title"><a href="https://liuqh.icu">红楼梦</a></span>
<span class="autor">曹雪芹 </span>
<ul class="category">
<li>四大名著</li>
<li>文学著作</li>
<li>古典长篇章回小说</li>
<li>四大小说名著之首</li>
</ul>
<span class="price">59.70元</span>
</div>
</body>
</html>
b. 解析代码:
// 定义结构体
type Book struct {
Name string `selector:"span.title"`
Link string `selector:"span > a" attr:"href"`
Author string `selector:"span.autor"`
Reviews []string `selector:"ul.category > li"`
Price string `selector:"span.price"`
}
func TestUnmarshal(t *testing.T) {
// 声明结构体
var book Book
collector := colly.NewCollector()
collector.OnHTML("body", func(element *colly.HTMLElement) {
err := element.Unmarshal(&book)
if err != nil {
fmt.Println("解析失败:",err)
}
fmt.Printf("结果:%+v\n",book)
})
_ = collector.Visit("https://liuqh.icu/a.html")
}
/***输出
=== RUN TestUnmarshal
结果:{Name:红楼梦 Link:https://liuqh.icu Author:曹雪芹 Reviews:[四大名著 文学著作 古典长篇章回小说 四大小说名著之首] Price:59.70元}
--- PASS: TestUnmarshal (0.27s)
PASS
*/
6.常用选择器
6.1 html内容
<html>
<head>
<title>测试</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<!-- 演示ID选择器 -->
<div id="title">标题ABC</div>
<!-- 演示class选择器 -->
<div class="desc">这是一段描述</div>
<!-- 演示相邻选择器 -->
<span>好好学习!</span>
<!-- 演示父子选择器 -->
<div class="parent">
<!-- 演示兄弟选择器 -->
<p class="childA">老大</p>
<p class="childB">老二</p>
</div>
<!-- 演示同时筛选多个选择器 -->
<span class="context1">武松上山</span>
<span class="context2">打老虎</span>
</body>
</html>
6.2 使用示例
// 常用选择器使用
func TestSelector(t *testing.T) {
collector := colly.NewCollector()
collector.OnHTML("body", func(element *colly.HTMLElement) {
// ID属性选择器,使用#
fmt.Printf("ID选择器使用: %v \n",element.ChildText("#title"))
// Class属性选择器,使用
fmt.Printf("class选择器使用1: %v \n",element.ChildText("div[class='desc']"))
fmt.Printf("class选择器使用2: %v \n",element.ChildText(".desc"))
// 相邻选择器 prev + next: 提取 <span>好好学习!</span>
fmt.Printf("相邻选择器: %v \n",element.ChildText("div[class='desc'] + span"))
// 父子选择器: parent > child,提取:<div class="parent">下所有子元素
fmt.Printf("父子选择器: %v \n",element.ChildText("div[class='parent'] > p"))
// 兄弟选择器 prev ~ next , 提取:<p class="childB">老二</p>
fmt.Printf("兄弟选择器: %v \n",element.ChildText("p[class='childA'] ~ p"))
// 同时选中多个,用,
fmt.Printf("同时选中多个1: %v \n",element.ChildText("span[class='context1'],span[class='context2']"))
fmt.Printf("同时选中多个2: %v \n",element.ChildText(".context1,.context2"))
})
_ = collector.Visit("https://liuqh.icu/a.html")
}
/**输出
=== RUN TestSelector
ID选择器使用: 标题ABC
class选择器使用1: 这是一段描述
class选择器使用2: 这是一段描述
相邻选择器: 好好学习!
父子选择器: 老大老二
兄弟选择器: 老二
同时选中多个1: 武松上山打老虎
同时选中多个2: 武松上山打老虎
--- PASS: TestSelector (0.31s)
PASS
*/
7. 过滤器
first-child & first-of-type
1. 过滤器说明
:first-child:first-of-type
2. html内容
<html>
<head>
<title>测试</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div class="parent">
<!--因为这里的p不是第一个子元素,不会被first-child筛选到-->
<span>第一个不是p标签</span>
<p>老大</p>
<p>老二</p>
</div>
<div class="name">
<p>张三</p>
<p>小米</p>
</div>
</body>
</html>
3. 代码实现
// 常用过滤器使用first-child & first-of-type
func TestFilterFirstChild(t *testing.T) {
collector := colly.NewCollector()
collector.OnHTML("body", func(element *colly.HTMLElement) {
// 只会筛选父元素下第一个子元素是<p>..</p>
fmt.Printf("first-child: %v \n",element.ChildText("p:first-child"))
// 会筛选父元素下第一个子元素类型是<p>..</p>
fmt.Printf("first-of-type: %v \n",element.ChildText("p:first-of-type"))
})
_ = collector.Visit("https://liuqh.icu/a.html")
}
/**输出
=== RUN TestFilterFirstChild
first-child: 张三
first-of-type: 老大张三
--- PASS: TestFilterFirstChild (0.51s)
PASS
*/
last-child & last-of-type
1. 过滤器说明
:last-child:last-of-type
筛选第一个子元素
nth-child & nth-of-type
1. 过滤器说明
nth-child(n)n1:nth-child(1) = :first-childnth-of-type(n)nth-child:nth-of-type(1) = :first-of-typenth-last-child(n)nth-child(n)nth-last-of-type(n)nth-of-type(n)
2. 使用示例
html内容和上面(7.1中)的html内容一样。
// 过滤器第x个元素
func TestFilterNth(t *testing.T) {
collector := colly.NewCollector()
collector.OnHTML("body", func(element *colly.HTMLElement) {
//<div class="parent">下的第一个子元素
nthChild := element.ChildText("div[class='parent'] > :nth-child(1)")
fmt.Printf("nth-child(1): %v \n",nthChild)
//<div class="parent">下的第一个p子元素
nthOfType := element.ChildText("div[class='parent'] > p:nth-of-type(1)")
fmt.Printf("nth-of-type(1): %v \n",nthOfType)
// div class="parent">下的最后一个子元素
nthLastChild := element.ChildText("div[class='parent'] > :nth-last-child(1)")
fmt.Printf("nth-last-child(1): %v \n",nthLastChild)
//<div class="parent">下的最后一个p子元素
nthLastOfType := element.ChildText("div[class='parent'] > p:nth-last-of-type(1)")
fmt.Printf("nth-last-of-type(1): %v \n",nthLastOfType)
})
_ = collector.Visit("https://liuqh.icu/a.html")
}
/**输出
=== RUN TestFilterNth
nth-child(1): 第一个不是p标签
nth-of-type(1): 老大
nth-last-child(1): 老二
nth-last-of-type(1): 老二
--- PASS: TestFilterNth (0.29s)
PASS
*/
only-child & only-of-type
1. 过滤器说明
:only-child:on-of-type
2. html内容
<html>
<head>
<title>测试</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div class="parent">
<span>我是span标签</span>
</div>
<div class="name">
<p>我是p标签</p>
</div>
</body>
</html>
3. 使用示例
// 过滤器只有一个元素
func TestFilterOnly(t *testing.T) {
collector := colly.NewCollector()
collector.OnHTML("body", func(element *colly.HTMLElement) {
// 匹配其子元素:有且只有一个标签的
onlyChild := element.ChildTexts("div > :only-child")
fmt.Printf("onlyChild: %v \n",onlyChild)
// 匹配其子元素:有且只有一个 p 标签的
nthOfType := element.ChildTexts("div > p:only-of-type")
fmt.Printf("nth-of-type(1): %v \n",nthOfType)
})
_ = collector.Visit("https://liuqh.icu/a.html")
}
/**输出
=== RUN TestFilterOnly
onlyChild: [我是span标签 我是p标签]
nth-of-type(1): [我是p标签]
--- PASS: TestFilterOnly (0.29s)
PASS
*/
contains
1. html内容
<html>
<head>
<title>测试</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<a href="https://www.baidu.com">百度</a>
<a href="https://cn.bing.com">必应</a>
</body>
</html>
2. 使用示例
func TestFilterContext(t *testing.T) {
collector := colly.NewCollector()
collector.OnHTML("body", func(element *colly.HTMLElement) {
// 内容匹配
attr1 := element.ChildAttr("a:contains(百度)", "href")
attr2 := element.ChildAttr("a:contains(必应)", "href")
fmt.Printf("百度: %v \n",attr1)
fmt.Printf("必应: %v \n",attr2)
})
_ = collector.Visit("https://liuqh.icu/a.html")
}
/**输出
=== RUN TestFilterContext
百度: https://www.baidu.com
必应: https://cn.bing.com
--- PASS: TestFilterContext (0.31s)
PASS
*/