# 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)
})
```

#### 【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屬性
})
```

#### 【CSS Class 名稱】
我想以 `CSS`來抓該 `class`底下的字
```go
c.OnHTML(".qa-list__title--ironman", func(e *colly.HTMLElement) {
fmt.Println(e.Text)
})
```

#### 【CSS ID 唯一識別】
我想以 `CSS`來抓該 `id`底下的字
```go
c.OnHTML("#read_more", func(e *colly.HTMLElement) {
fmt.Println(e.Text)
})
```

---
### 完整程式碼
```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")
}
```