golang爬虫colly框架

68   /   0   /   0   /   0   /   发布于 1年前
## 1 前言 ### 1.1 Go Colly 爬虫介绍 [爬虫框架](https://so.csdn.net/so/search?q=%E7%88%AC%E8%99%AB%E6%A1%86%E6%9E%B6&spm=1001.2101.3001.7020)中,各中流行的编程语言都有自己热门框架,python中的**selenium**、[Scrapy](https://so.csdn.net/so/search?q=Scrapy&spm=1001.2101.3001.7020)、PySpider等,Java中的Nutch、Crawler4j、WebMagic、WebCollector等。golang中colly使用Go语言编写的功能强大的爬虫框架,api简洁、性能强大、并发性高,github star 接近20K。 ### 1.2 安装 ```bash go init colly.demo go get -u github.com/gocolly/colly/v2 ``` ### 1.3 hello word 入门程序:抓取百度首页的按钮文字:“百度一下” ```go package main import ( "fmt" "log" "github.com/gocolly/colly/v2" ) func main() { // 创建收集器 c := colly.NewCollector() // 获取 "百度一下" 按钮文本 c.OnHTML("#su", func(e *colly.HTMLElement) { baiduBtn := e.Attr("value") fmt.Println(baiduBtn) }) // 开始访问 err := c.Visit("http://www.baidu.com/") if err != nil { log.Fatalln(err) return } } ``` ```bash PS D:\dev\go\workspace\go-colly-demo> go run .\demo1.go 百度一下 ``` ## 2 使用介绍 ### 2.1 核心生命周期函数介绍 * **NewCollector** 创建收集器对象 * **Visit** 开始访问 * **OnRequest** 请求发起时回调,一般用来设置请求头等 * **OnHTML** 匹配指定元素后回调 * **OnXML** 和OnHTML类似,用于匹配xpath解析 * **OnScraped** 在所有OnHTML之后执行,可以用来做一些回收操作 * **OnError** 请求发生错误回调,比如404 ```go package main import ( "fmt" "log" "github.com/gocolly/colly/v2" ) func main() { // 创建收集器 c := colly.NewCollector() // 匹配指定元素后回调 c.OnHTML("#su", func(e *colly.HTMLElement) { baiduBtn := e.Attr("value") fmt.Println("匹配到目标元素ID su:", baiduBtn) }) // 可以多次 OnHTML 不同的页面元素 c.OnHTML("#kw", func(e *colly.HTMLElement) { maxlength := e.Attr("maxlength") fmt.Println("匹配到目标元素ID kw, 最多允许输入:", maxlength) }) // 请求发起时回调,一般用来设置请求头等 c.OnRequest(func(request *colly.Request) { fmt.Println("----> 开始请求了") }) // 请求完成后回调 c.OnResponse(func(response *colly.Response) { fmt.Println("----> 开始返回了") }) //请求发生错误回调 c.OnError(func(response *colly.Response, err error) { fmt.Printf("发生错误了:%v", err) }) // 在所有OnHTML之后执行,可以用来做一些回收操作 c.OnScraped(func(response *colly.Response) { fmt.Println("----> 所有匹配已完成") }) // 和OnHTML类似,用于匹配xpath解析 // 匹配:百度首页左上方链接 c.OnXML("//div[@id='s-top-left']/a", func(element *colly.XMLElement) { text := element.Text href := element.Attr("href") fmt.Printf("名称:%s -> 连接:%s\n", text, href) }) // 开始访问 err := c.Visit("http://www.baidu.com/") if err != nil { log.Fatalln(err) return } } ``` ```bash PS D:\dev\go\workspace\go-colly-demo> go run .\demo2.go ----> 开始请求了 ----> 有结果返回了 匹配到目标元素ID su: 百度一下 匹配到目标元素ID kw, 最多允许输入: 255 名称:新闻 -c> 连接:http://news.baidu.com 名称:hao123 -c> 连接:https://www.hao123.com?src=from_pc 名称:地图 -c> 连接:http://map.baidu.com 名称:贴吧 -c> 连接:http://tieba.baidu.com/ 名称:视频 -c> 连接:https://haokan.baidu.com/?sfrom=baidu-top 名称:图片 -c> 连接:http://image.baidu.com/ 名称:网盘 -c> 连接:https://pan.baidu.com?from=1026962h ----> 所有匹配已完成 ``` ### 2.2 goquerySelector 语法 `OnHTML`方法的第一个参数是选择器,goquery选择器语法类似**jquery**,可以认为它是jquery的go版本实现。这里简单介绍常用的选择器,具体可以参考jquery选择器使用。 * ID选择器: #id-name 根据元素id属性进行选择 * 类选择器:.class-name 根据class名称选择 * 标签选择器:div 根据标签名字选择 * 子元素选择器:parent>child 筛选parent这个父元素下,符合child这个条件的最直接的子元素,不会匹配孙子元素 * 子孙选择器:parent son 空格隔开代表可以匹配非直接子元素也就包括子孙元素了 * prev+next相邻选择器:选择相邻的元素 * … ### 2.3 **xpath**语法 `OnXML` 的第一个参数为xpath选择器,用的相对少一些,可以自行搜索语法学期,这里不再赘述,余下代码实例都用 `goquery`选择器 [XPath 语法 (w3school.com.cn)](https://www.w3school.com.cn/xpath/xpath_syntax.asp) ### 2.4 \*colly.HTMLElement常用方法 ```go c.OnHTML("#s-top-left", func(e *colly.HTMLElement) { // }) ``` #### 2.4.1 常用属性 **Name**:获取元素名称 **Text**:获取元素文本内容,包括子元素的内容 **DOM**:返回goquery dom对象,可以调用goquery的方法进行操作:Find、Has、After 等 ```go c.OnHTML("#s-top-left", func(e *colly.HTMLElement) { fmt.Println(e.Name) fmt.Println(e.Text) }) ``` ```bash PS D:\dev\go\workspace\go-colly-demo> go run .\demo3.go div 新闻hao123地图贴吧视频图片网盘更多翻译学术文库百科知道健康营销推广直播音乐查看全部百度产品 > ``` #### 2.4.2 常用的方法 **Attr**:Attr(k string) string 获取元素指定属性 ```go cs := e.Attr("class") ``` **ForEach**:ForEach(goquerySelector string, callback func(int, \*HTMLElement)) 在当前元素的遍历所有符合指定条件的元素 ```go c.OnHTML("#s-top-left", func(e *colly.HTMLElement) { e.ForEach("a", func(i int, element *colly.HTMLElement) { fmt.Printf("第%d超链接\t %s :: %s\n", i, element.Text, element.Attr("href")) }) }) ``` ```bash PS D:\dev\go\workspace\go-colly-demo> go run .\demo3.go 第0超链接 新闻 :: http://news.baidu.com 第1超链接 hao123 :: https://www.hao123.com?src=from_pc 第2超链接 地图 :: http://map.baidu.com 第3超链接 贴吧 :: http://tieba.baidu.com/ 第4超链接 视频 :: https://haokan.baidu.com/?sfrom=baidu-top 第5超链接 图片 :: http://image.baidu.com/ 第6超链接 网盘 :: https://pan.baidu.com?from=1026962h 第7超链接 更多 :: http://www.baidu.com/more/ 第8超链接 翻译 :: http://fanyi.baidu.com/ 第9超链接 学术 :: http://xueshu.baidu.com/ 第10超链接 文库 :: https://wenku.baidu.com 第11超链接 百科 :: https://baike.baidu.com 第12超链接 知道 :: https://zhidao.baidu.com 第13超链接 健康 :: https://jiankang.baidu.com/widescreen/home 第14超链接 营销推广 :: http://e.baidu.com/ebaidu/home?refer=887 第15超链接 直播 :: https://live.baidu.com/ 第16超链接 音乐 :: http://music.taihe.com 第17超链接 查看全部百度产品 > :: http://www.baidu.com/more/ ``` **ForEachWithBreak**:和ForEach类似,但是可以根据指定返回true false控制是否终止循环 ```go c.OnHTML("#s-top-left", func(e *colly.HTMLElement) { e.ForEachWithBreak("a", func(i int, element *colly.HTMLElement) bool { fmt.Printf("第%d超链接\t %s :: %s\n", i, element.Text, element.Attr("href")) return i < 3 }) }) ``` ```bash PS D:\dev\go\workspace\go-colly-demo> go run .\demo3.go 第0超链接 新闻 :: http://news.baidu.com 第1超链接 hao123 :: https://www.hao123.com?src=from_pc 第2超链接 地图 :: http://map.baidu.com 第3超链接 贴吧 :: http://tieba.baidu.com/ ``` **ChildAttrs**:(goquerySelector, attrName string) []string 在当前元素下匹配指定元素,并返回指定的属性值 ```go c.OnHTML("#s-top-left", func(e *colly.HTMLElement) { attrs := e.ChildAttrs("a", "href") for i, attr := range attrs { fmt.Printf("第%d个a标签的href属性: %s\n", i, attr) } }) ``` ```bash PS D:\dev\go\workspace\go-colly-demo> go run .\demo3.go 第0个a标签的href属性: http://news.baidu.com 第1个a标签的href属性: https://www.hao123.com?src=from_pc 第2个a标签的href属性: http://map.baidu.com 第3个a标签的href属性: http://tieba.baidu.com/ 第4个a标签的href属性: https://haokan.baidu.com/?sfrom=baidu-top 第5个a标签的href属性: http://image.baidu.com/ 第6个a标签的href属性: https://pan.baidu.com?from=1026962h 第7个a标签的href属性: http://www.baidu.com/more/ 第8个a标签的href属性: http://fanyi.baidu.com/ 第9个a标签的href属性: http://xueshu.baidu.com/ 第10个a标签的href属性: https://wenku.baidu.com 第11个a标签的href属性: https://baike.baidu.com 第12个a标签的href属性: https://zhidao.baidu.com 第13个a标签的href属性: https://jiankang.baidu.com/widescreen/home 第14个a标签的href属性: http://e.baidu.com/ebaidu/home?refer=887 第15个a标签的href属性: https://live.baidu.com/ 第16个a标签的href属性: http://music.taihe.com 第17个a标签的href属性: http://www.baidu.com/more/ ``` **ChildTexts**:ChildTexts(goquerySelector string) []string 在当前元素下匹配指定元素,并返回指定元素的text内容 ```go c.OnHTML("#s-top-left", func(e *colly.HTMLElement) { texts := e.ChildTexts("a") fmt.Println(strings.Join(texts, ", ")) }) ``` ```bash PS D:\dev\go\workspace\go-colly-demo> go run .\demo3.go 新闻, hao123, 地图, 贴吧, 视频, 图片, 网盘, 更多, 翻译, 学术, 文库, 百科, 知道, 健康, 营销推广, 直播, 音乐, 查看全部百度产品 > ``` ## 3 高级配置 ### 3.1 收集器配置-UserAgent 用来设置浏览器UA头 ```go c := colly.NewCollector( colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42"), ) c.OnRequest(func(request *colly.Request) { fmt.Println("User-Agent:", request.Headers.Get("User-Agent")) }) ``` ### 3.2 收集器配置-Async 设置网络请求异步处理,异步之后需要调用c.Wait() ```go package main import ( "fmt" "github.com/gocolly/colly/v2" "time" ) func main() { // Instantiate default collector c := colly.NewCollector( colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42"), colly.Async(), colly.MaxDepth(2), ) c.OnRequest(func(request *colly.Request) { fmt.Println("开始请求:", request.URL) }) c.OnHTML("#s-top-left a", func(e *colly.HTMLElement) { link := e.Attr("href") e.Request.Visit(link) }) now := time.Now() c.Visit("https://baidu.com/") // Wait until threads are finished c.Wait() fmt.Println("耗时:", time.Now().Sub(now).Milliseconds()) } ``` 测试结果 关闭异步:5\~6s 开启异步:1\~2s ### 3.3 收集器配置-MaxDepth 限制访问的URL的递归深度 ### 3.4 收集器配置-AllowedDomains 设置允许请求的域名主机,可以是多个,`Visit`只会发起这些域名下的请求 ```go c := colly.NewCollector( colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42"), colly.Async(true), colly.AllowedDomains("www.baidu.com", "wiki.hackerspaces.org"), ) ``` ### 3.5 收集器配置-IgnoreRobotsTxt 设置忽略robots协议 > robots协议也称爬虫协议、爬虫规则等,是指网站可建立一个robots.txt文件来告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取,而搜索引擎则通过读取robots.txt文件来识别这个页面是否允许被抓取。但是,这个robots协议不是防火墙,也没有强制执行力,搜索引擎完全可以忽视robots.txt文件去抓取网页的快照。 ```go c := colly.NewCollector( colly.IgnoreRobotsTxt(), ) ``` ### 3.6 收集器配置-CacheDir 设置GET请求本地缓存文件夹 ```go c := colly.NewCollector( colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42"), colly.Async(true), colly.AllowedDomains("www.baidu.com", "wiki.hackerspaces.org"), colly.IgnoreRobotsTxt(), colly.CacheDir("cache-dir"), ) ``` ### 3.7 收集器配置-AllowURLRevisit 允许同一个收集器多次访问同一个地址 ```go colly.AllowURLRevisit(), ``` ### 3.8 并发请求设置 ```go c.Limit(&colly.LimitRule{ DomainGlob: "*baidu.*", // 匹配URL包含baidu的 Parallelism: 10, // 并发请求10 RandomDelay: 5 * time.Second, // 设置发起请求随机延时0-5 }) ``` 例子: ```go package main import ( "fmt" "github.com/gocolly/colly/v2" "time" ) func main() { // Instantiate default collector c := colly.NewCollector( colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42"), colly.Async(), colly.MaxDepth(2), colly.AllowURLRevisit(), ) c.Limit(&colly.LimitRule{ DomainGlob: "*baidu.*", Parallelism: 10, RandomDelay: 5 * time.Second, }) //c.Limit(&colly.LimitRule{DomainGlob: "*", Parallelism: 2}) c.OnRequest(func(request *colly.Request) { fmt.Println("开始请求:", request.URL) }) c.OnResponse(func(response *colly.Response) { fmt.Println("请求结束:", response.Request.URL) fmt.Println("------------") }) c.OnHTML("#s-top-left a", func(e *colly.HTMLElement) { link := e.Attr("href") e.Request.Visit(link) }) now := time.Now() c.Visit("https://baidu.com/") // Wait until threads are finished c.Wait() fmt.Println("耗时:", time.Now().Sub(now).Milliseconds()) } ``` ### 3.9 配置代理 频繁访问网站,会面临IP被封,需要进行代理池设置,colly支持http https socks5的代理 这里使用"快代理"免费代理池测试;[国内高匿HTTP免费代理IP - 快代理 (kuaidaili.com)](https://www.kuaidaili.com/free/) 免费的极其不稳定,测试过程中-\_-碰运气 ```go // 设置代理:http https socks5 proxyPool, err := proxy.RoundRobinProxySwitcher("http://111.3.102.207:30001", "http://183.247.211.41:30001") if err != nil { fmt.Println("设置代理失败", err) return } c.SetProxyFunc(proxyPool) ``` ### 3.10 上传multipart文件 colly的收集器,提供了 `PostMultipart`方法用来上传文件,这里贴出一个官方的小demo ```go package main import ( "fmt" "io/ioutil" "net/http" "os" "time" "github.com/gocolly/colly/v2" ) func generateFormData() map[string][]byte { f, _ := os.Open("gocolly.jpg") defer f.Close() imgData, _ := ioutil.ReadAll(f) return map[string][]byte{ "firstname": []byte("one"), "lastname": []byte("two"), "email": []byte("onetwo@example.com"), "file": imgData, } } func setupServer() { var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { fmt.Println("received request") err := r.ParseMultipartForm(10000000) if err != nil { fmt.Println("server: Error") w.WriteHeader(500) w.Write([]byte("<html><body>Internal Server Error</body></html>")) return } w.WriteHeader(200) fmt.Println("server: OK") w.Write([]byte("<html><body>Success</body></html>")) } go http.ListenAndServe(":8080", handler) } func main() { // Start a single route http server to post an image to. setupServer() c := colly.NewCollector(colly.AllowURLRevisit(), colly.MaxDepth(5)) // On every a element which has href attribute call callback c.OnHTML("html", func(e *colly.HTMLElement) { fmt.Println(e.Text) time.Sleep(1 * time.Second) e.Request.PostMultipart("http://localhost:8080/", generateFormData()) }) // Before making a request print "Visiting ..." c.OnRequest(func(r *colly.Request) { fmt.Println("Posting gocolly.jpg to", r.URL.String()) }) // Start scraping c.PostMultipart("http://localhost:8080/", generateFormData()) c.Wait() } ``` ### 3.11 Cookies处理 请求中难免会遇到cookies处理的情况: * **cookiejar** go 语言net包提供的:`net/http/cookiejar`用来存储cookies colly 收集器提供了两个方法:SetCookieJar和SetCookies来设置请求 ```go cjar, err := cookiejar.New(nil) if err != nil { fmt.Println("创建cookiejar失败", err) } // 设置cookiejar c.SetCookieJar(cjar) // 请求发起时候打印cookies c.OnRequest(func(request *colly.Request) { cookies := cjar.Cookies(request.URL) fmt.Println(cookies) fmt.Println("开始请求:", request.URL, ", cookies:", cookies) }) ``` ## 4 综合实战 ### 4.1 抓取笔趣阁小说 网站地址:http://www.bqg5200.net/16/16705/ 伏天氏小说章节抓取 代码实现: ```go package main import ( "bufio" "fmt" "github.com/PuerkitoBio/goquery" "github.com/gocolly/colly/v2" "github.com/gocolly/colly/v2/extensions" "log" "os" "strings" "time" ) type PageChapter struct { Name string `json:"name"` Url string `json:"url"` Index int `json:"index"` } func GetNewCollector() *colly.Collector { collector := colly.NewCollector( func(collector *colly.Collector) { // 设置随机ua extensions.RandomUserAgent(collector) }, func(collector *colly.Collector) { collector.OnRequest(func(request *colly.Request) { log.Println(request.URL, ", User-Agent:", request.Headers.Get("User-Agent")) }) }, ) collector.SetRequestTimeout(time.Second * 60) return collector } func BqgChapterParse(url string) ([]PageChapter, error) { var cha []PageChapter collector := GetNewCollector() collector.OnHTML(".listmain dl", func(element *colly.HTMLElement) { element.DOM.Children().Each(func(i int, selection *goquery.Selection) { selection = selection.ChildrenFiltered("a") link, _ := selection.Attr("href") name := strings.TrimSpace(selection.Text()) cha = append(cha, PageChapter{ Index: i, Url: element.Request.AbsoluteURL(link), Name: name, }) }) }) err := collector.Visit(url) return cha, err } func writeTxt(fileName string, chapters []PageChapter) { f, err := os.Create(fileName) if err != nil { fmt.Println("创建文件失败,", err) return } defer f.Close() w := bufio.NewWriter(f) for _, chapter := range chapters { w.WriteString(fmt.Sprintf("%d %s %s\n", chapter.Index, chapter.Name, chapter.Url)) } w.Flush() } func main() { log.Println("开始抓取") parse, err := BqgChapterParse("http://www.bqg5200.net/16/16705/") if err != nil { log.Fatalf("解析章节错误:%v", err) } log.Println("抓取结束") writeTxt("16705.txt", parse) log.Println("写入文件结束") } ``` ``` 0 http://www.bqg5200.net/16/16705/ 1 第一章 此间少年 http://www.bqg5200.net/16/16705/4845336.html 2 第二章 三年聚气 http://www.bqg5200.net/16/16705/4845337.html 3 第三章 一日三境 http://www.bqg5200.net/16/16705/4845338.html 4 第四章 太阳命魂 http://www.bqg5200.net/16/16705/4845339.html 5 第五章 风晴雪的决定 http://www.bqg5200.net/16/16705/4845340.html 6 第六章 有龙出没 http://www.bqg5200.net/16/16705/4845341.html 7 第七章 刻箓师 http://www.bqg5200.net/16/16705/4845342.html 8 第八章 万众瞩目 http://www.bqg5200.net/16/16705/4845343.html 9 第九章 文试 http://www.bqg5200.net/16/16705/4845344.html 10 第十章 一鸣惊人 http://www.bqg5200.net/16/16705/4845345.html 11 第十一章 妖精害我 http://www.bqg5200.net/16/16705/4845346.html 12 第十二章 师兄弟 http://www.bqg5200.net/16/16705/4845347.html 13 第十三章 一日功成 http://www.bqg5200.net/16/16705/4845348.html 14 第十四章 论战 http://www.bqg5200.net/16/16705/4845349.html 15 第十五章 太嚣张了 http://www.bqg5200.net/16/16705/4845350.html 16 第十六章 年少轻狂 http://www.bqg5200.net/16/16705/4845351.html 17 第十七章 我不服 http://www.bqg5200.net/16/16705/4845352.html 。。。 。。。 ``` ### 4.2 抓取百度热搜 地址:[百度热搜 (baidu.com)](https://top.baidu.com/board?tab=realtime) 代码实现: ```go package main import ( "fmt" "github.com/gocolly/colly/v2" "github.com/gocolly/colly/v2/extensions" "log" "time" ) type HotItem struct { Link string Img string Title string Desc string Hot int } func main() { collector := colly.NewCollector( func(collector *colly.Collector) { // 设置随机ua extensions.RandomUserAgent(collector) }, func(collector *colly.Collector) { collector.OnRequest(func(request *colly.Request) { log.Println(request.URL, ", User-Agent:", request.Headers.Get("User-Agent")) }) }, ) collector.SetRequestTimeout(time.Second * 60) data := []HotItem{} collector.OnHTML(".container-bg_lQ801", func(element *colly.HTMLElement) { element.ForEach(".category-wrap_iQLoo", func(i int, element *colly.HTMLElement) { aLink := element.DOM.ChildrenFiltered("a") jumpLink, _ := aLink.Attr("href") imgLink, _ := aLink.ChildrenFiltered("img").Attr("src") title := element.ChildText(".content_1YWBm .c-single-text-ellipsis") desc := element.ChildText(".content_1YWBm .large_nSuFU ") data = append(data, HotItem{ Link: jumpLink, Img: imgLink, Title: title, Desc: desc, }) }) }) err := collector.Visit("https://top.baidu.com/board?tab=realtime") if err != nil { log.Fatalf("%v", err) } for i := range data { fmt.Printf("[%d]标题:%s, 链接:%s, 图片地址:%s, 描述:%s\n", i, data[i].Title, data[i].Link, data[i].Img, data[i].Desc) } } ``` https://blog.csdn.net/small_to_large/article/details/130791204
  • 共 0 条回复
  • 需要登录 后方可回复, 如果你还没有账号请点击这里注册
梦初醒 茅塞开
  • 不经他人苦,莫劝他人善。
  • 能量足,心态稳,温和坚定可以忍。
  • 辛苦决定不了收入,真正决定收入的只有一个,就是不可替代性。
  • 要么忙于生存,要么赶紧去死!
  • 内心强大到混蛋,比什么都好!
  • 规范流程比制定制度更重要!
  • 立志需要高远,但不能急功近利;
    行动需要迅速,却不可贪图速成。
  • 不要强求人品,要设计高效的机制。
  • 你弱的时候,身边都是鸡零狗碎;
    你强的时候,身边都是风和日丽。
  • 机制比人品更可靠,契约比感情更可靠。
  • 合作不意味着没有冲突,却是控制冲突的最好方法。
  • 误解是人生常态,理解本是稀缺的例外。
  • 成功和不成功之间,只差一次坚持!
  • 祁连卧北雪,大漠壮雄关。
  • 利益顺序,过程公开,机会均等,付出回报。