# Day23 Golang 爬蟲框架 colly 摳哩 ## 爬蟲是什麼 `網路爬蟲(Web rawler)`或叫 `網路蜘蛛(Spider)`,是一套能搜集、解析網路上的訊息、資料,自動獲取該網域網站的摘要、重點甚至內容 的機器人。 看不太懂對不對? 沒關係,google一下就有了。 有沒有想過為什麼打開`google搜尋引擎`,鍵入關鍵字、按一下Enter,就能搜尋相關的資料? ~~因為這就是Google這外星科技公司厲害的地方啊~~ 因為Google就是個世界上最大之一的網路爬蟲啊 簡單地說,爬蟲是**擷取網頁的重點內容,擷取並剖析**。 而Google搜尋引擎就是**到各個IP、各個網站擷取、解析資料並存進他們的資料庫**,而使用者在下關鍵字時等同是進Google的引擎資料庫搜尋相關資料、跑出搜尋結果,點擊結果時會再導到該網站去。 其實我們所寫的爬蟲程式並不完全地叫作爬蟲,畢竟個人所寫的程式只能解析特定網站的格式,充其量只能叫做網頁剖析而已,很難做到公司規模等級的自動網路爬蟲。 寫爬蟲通常會用一套已經有完整工具的框架, 而這裡介紹的`colly`是golang中爬蟲的主流框架。 ## 安裝 colly `Colly`為什麼叫摳哩,因為就是要摳東西啊! [**Colly**](https://github.com/gocolly/colly)這個套件 使用到 [**GoQuery selector**](https://github.com/PuerkitoBio/goquery)來做`html parser`剖析網頁的語法。 以下路徑最末端要加上`v2`才會指到最新版本(2.x.x):`github.com/gocolly/colly/v2` #### go get 全域安裝 go get -u github.com/gocolly/colly #### glide 區域安裝 ```yaml package: . import: - package: github.com/gocolly/colly version: ~2.1.0 ``` 在專案底下 放colly1.go程式 ```go package main import ( "github.com/gocolly/colly" ) func main() { } ``` #### 【colly OnResponse】 僅用到以下短短三行程式碼,就能看到我在 [**iT邦幫忙鐵人賽**](https://ithelp.ithome.com.tw/users/20125192/ironman/3155) 文章列表的網頁原始碼哩。 ```go package main import ( "fmt" "github.com/gocolly/colly" ) func main() { c := colly.NewCollector() // 在colly中使用 Collector 這類物件 來做事情 c.OnResponse(func(r *colly.Response) { // 當Visit訪問網頁後,網頁響應(Response)時候執行的事情 fmt.Println(string(r.Body)) // 返回的Response物件r.Body 是[]Byte格式,要再轉成字串 }) 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.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155") // Visit 要放最後 } ``` 來,摳好哩! 原始碼有了,距離所謂的`自製爬蟲` 就只差網頁剖析了, 也就是要解析在原始碼中出現的標籤`html tag`。 #### 【colly OnHTML 小坑注意】 搜尋`qa-list__title-link`這個`class`就能找到列表中第一頁的文章標題了。 透過`count`這個變數來讓我們更了解`colly.OnHTML`的運作。 ```go var count = 0 func main() { c := colly.NewCollector() // 當Visit訪問網頁後,在網頁響應(Response)之後、發現這是HTML格式 執行的事情 c.OnHTML(".qa-list__title-link", func(e *colly.HTMLElement) { // 每找到一個符合 goquerySelector字樣的結果,便會進這個OnHTML一次 fmt.Println(e.Text) count++ }) 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.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155") fmt.Println(count) // count值為10,代表原始碼中 有10個符合規則的結果,總共進了OnHTML func 10次 } ``` --- ## 爬蟲選取器 query Selector 在 **colly OnHTML** 的參數`goquerySelector` 中可以用條件來篩選所要的HTML內容。 可以依照`tag`、`attr`、`class`當作搜尋的條件來選取。 會依照以下網頁中的其中**這些元素**來做範例。 ```html <title>...</title> <meta name="..." content="..."> <h3 class="qa-list__title qa-list__title--ironman">Go繁不及備載<span> 系列</span></h3> <a href="/users/20125192" id="account" data-account="gjlmotea">我的主頁</a> ``` #### 【Tag 標籤】 我想抓 文章標題 `title`的tag 直接在`OnHTML`中輸入tag名稱 ```go c.OnHTML("title", func(e *colly.HTMLElement) { fmt.Println(e.Text) }) ``` ![抓Title](https://i.imgur.com/79ZTdAy.png) #### 【Attr 屬性】 我想抓 `meta` tag中,有 `name` 這個屬性的相關訊息 ```go c.OnHTML("meta[name]", func(e *colly.HTMLElement) { fmt.Println(e) }) ``` #### 【AttrVal 屬性值】 我想抓圖片中的文字,`name="description"`這串屬性的content ```go c.OnHTML("meta[name='description']", func(e *colly.HTMLElement) { fmt.Println(e.Attr("content")) // 抓此Tag中的name屬性 來找出此Tag,再印此Tag中的content屬性 }) ``` ![抓meta name](https://i.imgur.com/hGSUNyr.png) #### 【CSS Class 名稱】 我想以 `CSS`來抓該 `class`底下的字 ```go c.OnHTML(".qa-list__title--ironman", func(e *colly.HTMLElement) { fmt.Println(e.Text) }) ``` ![抓CSS class](https://i.imgur.com/k5FNQlH.png) #### 【CSS ID 唯一識別】 我想以 `CSS`來抓該 `id`底下的字 ```go c.OnHTML("#read_more", func(e *colly.HTMLElement) { fmt.Println(e.Text) }) ``` ![抓CSS ID](https://i.imgur.com/IAfrg9T.png) --- ### 完整程式碼 ```go package main import ( "fmt" "github.com/gocolly/colly" ) func main() { c := colly.NewCollector() // 抓標籤Tag c.OnHTML("title", func(e *colly.HTMLElement) { fmt.Println(e.Text) }) // 抓屬性值 AttrVal c.OnHTML("meta[name='description']", func(e *colly.HTMLElement) { fmt.Println(e.Attr("content")) // 抓此Tag中的name屬性 來找出此Tag,再印此Tag中的content屬性 }) // 抓類別Class 名稱 c.OnHTML(".qa-list__title--ironman", func(e *colly.HTMLElement) { fmt.Println(e.Text) }) // 抓唯一識別 ID c.OnHTML("#read_more", func(e *colly.HTMLElement) { fmt.Println(e.Text) }) 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.Visit("https://ithelp.ithome.com.tw/users/20125192/ironman/3155") } ```