接續昨天colly
的內容,今天來更深入探討colly的機制吧!
OnRequest
在發起請求之前,可以預先對Header的參數進行設定
OnError
如果在請求的時候發生錯誤
OnResponseHeaders
收到響應的標頭時
OnResponse
收到響應回復的時候
OnHTML
收到的響應是HTML格式時(時間點比 OnResponse還晚),進行goquerySelector篩選
OnXML
收到的響應是XML格式時(時間點比 OnHTML還晚),進行xpathQuery篩選
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 再進行設定就來不及了)
今天的目標是要爬 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++
}
}
}
使用者名稱
。這部分就需要帶值(帳號密碼)做模擬登入,會比較搞剛。