iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 24
3
Software Development

Go繁不及備載系列 第 24

# Day24 Golang 爬蟲框架 colly 來爬鐵人賽文吧

Day24 Golang 爬蟲框架 colly 來爬鐵人賽文吧

接續昨天colly的內容,今天來更深入探討colly的機制吧!

colly函式作用順序(Call order of callbacks)

  1. OnRequest
    在發起請求之前,可以預先對Header的參數進行設定

  2. OnError
    如果在請求的時候發生錯誤

  3. OnResponseHeaders
    收到響應的標頭時

  4. OnResponse
    收到響應回復的時候

  5. OnHTML
    收到的響應是HTML格式時(時間點比 OnResponse還晚),進行goquerySelector篩選

  6. OnXML
    收到的響應是XML格式時(時間點比 OnHTML還晚),進行xpathQuery篩選

  7. OnScraped
    抓取網頁(與OnResponse相仿),但在最後才進行調用

有漏掉什麼嗎?應該沒有吧!

package main

import (
	"fmt"
	"github.com/gocolly/colly/v2"
)

func main() {
	var url = "https://member.ithome.com.tw/login"
	c := colly.NewCollector()

	c.OnRequest(func(r *colly.Request) {
		fmt.Println("1")
	})

	c.OnError(func(_ *colly.Response, err error) {
		fmt.Println("2")
	})

	c.OnResponseHeaders(func(r *colly.Response) {
		fmt.Println("3")
	})

	c.OnResponse(func(r *colly.Response) {
		fmt.Println("4")
	})

	c.OnHTML("body", func(e *colly.HTMLElement) {
		fmt.Println("5")
	})

	c.OnXML("//footer", func(e *colly.XMLElement) {
		fmt.Println("6")
	})

	c.OnScraped(func(r *colly.Response) {
		fmt.Println("7")
	})

	c.Visit(url)
}

把以上會用到的函式設定好之後,再進行網頁訪問Visit (不然先發起Visit 再進行設定就來不及了)


用colly來爬鐵人賽文

今天的目標是要爬 Go繁不及備載 此一系列好文。

伸手之前

開始來爬文

我想自動爬我自己寫的文章,
雖然看似有點沒意義,但人生有時候就是想沒意義一下,
到底該怎麼實作餒?

先打開開發者工具找到鐵人賽文章的標題。
開發者工具

首要任務是找到進到我各個文章的連結的tag

哦,找到了,
class="qa-list__title-link" 決定是你了,就從這個標籤開始下手。

package main

import (
	"fmt"
	"github.com/gocolly/colly"
	"strings"
)

func main() {
	c := colly.NewCollector()

	c.OnHTML(".qa-list__title-link", func(e *colly.HTMLElement) {
		fmt.Println(e.Text, e.Attr("href"))
		// e.Text 印出 <a> tag 裡面的文字,也就是文章標題
		// e.Attr("href") 則是找到 <a> tag裡面的 href元素
	})

	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=1")
	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=2")
	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=3")
}

文章連結

有了<a>中的網址連結字串,想辦法點進去每個連結裡面。

func main() {
	c := colly.NewCollector()

	c.OnHTML(".qa-list__title-link", func(e *colly.HTMLElement) {
		// fmt.Println(e.Text, e.Attr("href"))
		// e.Text 印出 <a> tag 裡面的文字,也就是文章標題
		// e.Attr("href") 則是找到 <a> tag裡面的 href元素

		linksStr := e.Attr("href")
		linksStr = strings.Replace(linksStr, " ", "", -1) // 把空白以""取代掉
		links := strings.Split(linksStr, "\n")            // 以換行符號(\n)做為分隔來切割字串

		for _, link := range links {
			c.OnResponse(func(r *colly.Response) {
				fmt.Println(string(r.Body)) // 印出所有返回的Response物件r.Body
			})

			c.Visit(link)
		}
	})

	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=1")
	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=2")
	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=3")
}

但我想抓的並不是整個 Go繁不及備載 鐵人賽30天文章的原始碼,
而是想要這位作者的精華 的內文。

再稍微修改一下,就完成哩!

func main() {
	c := colly.NewCollector()

	c.OnHTML(".qa-list__title-link", func(e *colly.HTMLElement) {
		// fmt.Println(e.Text, e.Attr("href"))
		// e.Text 印出 <a> tag 裡面的文字,也就是文章標題
		// e.Attr("href") 則是找到 <a> tag裡面的 href元素

		linksStr := e.Attr("href")
		linksStr = strings.Replace(linksStr, " ", "", -1) // 把空白以""取代掉
		links := strings.Split(linksStr, "\n")            // 以換行符號(\n)做為分隔來切割字串

		for _, link := range links {
			c2 := colly.NewCollector()	// 這邊要在迴圈一開始再宣告一個 Collector,才不會與原本的混合到
			c2.OnHTML(".qa-markdown", func(e2 *colly.HTMLElement) {
				fmt.Println(e2.Text) // 印出 qa-markdown class中的文字(Go繁不及備載 文章的內文)
			})
			c2.Visit(link) // 找到<a>連結網址後,點進去訪問
		}
	})

	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=1")
	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=2")
	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=3")
}

爬蟲結果(擷取回傳結果的一小小部分)
成果展示

天哪,我的鐵人30天文章居然被一覽無遺!
豪害羞阿

另外可以像以下這樣再做字數計算,
爬下來就能方便知道自己的文章總共佔了多少字數哩。

package main

import (
	"fmt"
	"strings"

	"github.com/gocolly/colly"
)

var wordCount = 0
var chineseCount = 0

func main() {
	c := colly.NewCollector()

	c.OnRequest(func(r *colly.Request) { // iT邦幫忙需要寫這一段 User-Agent才給爬
		r.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36")
	})

	c.OnHTML(".qa-list__title-link", func(e *colly.HTMLElement) {
		// fmt.Println(e.Text, e.Attr("href"))
		// e.Text 印出 <a> tag 裡面的文字,也就是文章標題
		// e.Attr("href") 則是找到 <a> tag裡面的 href元素

		linksStr := e.Attr("href")
		linksStr = strings.Replace(linksStr, " ", "", -1) // 把空白以""取代掉
		links := strings.Split(linksStr, "\n")            // 以換行符號(\n)做為分隔來切割字串

		for _, link := range links {
			c2 := colly.NewCollector() // 這邊要在迴圈一開始再宣告一個 Collector,才不會與原本的混合到
			c2.OnHTML(".qa-markdown", func(e2 *colly.HTMLElement) {
				fmt.Println(e2.Text) // 印出 qa-markdown class中的文字(Go繁不及備載 文章的內文)

				countWord(e2.Text)

			})
			c2.OnRequest(func(r *colly.Request) { // iT邦幫忙需要寫這一段 User-Agent才給爬
				r.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36")
			})
			c2.Visit(link) // 找到<a>連結網址後,點進去訪問
		}
	})

	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=1")
	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=2")
	c.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155?page=3")

	fmt.Println("英文+中文+數字 共", wordCount, "字")
	fmt.Println("純中文字 共", chineseCount, "字")
}

func countWord(input string) {
	for _, word := range input {
		if word != 32 && word != 10 { // 計算有多少非空白(space)以及換行(\n)的字數
			wordCount++
		}
		if word > 256 { // 計算有多少中文字數(編碼比ASCII大的字)
			chineseCount++
		}
	}
}
有些HTML元素在會員登入後才會顯示,像是右上角的使用者名稱。這部分就需要帶值(帳號密碼)做模擬登入,會比較搞剛。

上一篇
# Day23 Golang 爬蟲框架 colly 摳哩
下一篇
# Day25 Golang 爬蟲框架 colly 模擬使用者登入
系列文
Go繁不及備載35

尚未有邦友留言

立即登入留言