最近想要透過小實作來撰寫筆記,達到做中學的效果,因此就來實作個小爬蟲順便結合前面學到的package做一個小複習。
Go的net/http
package 提供了一個HTTP Client,用來發送各種HTTP請求。
http.Get
:發送GET請求。http.Post
:發送POST請求。http.NewRequest
:建立一個新的HTTP請求。語法如下:
// 發送GET請求
func http.Get(url string) (resp *http.Response, err error)
// 發送POST請求
func http.Post(url, contentType string, body io.Reader) (resp *http.Response, err error)
// 建立一個新的HTTP請求
func http.NewRequest(method, url string, body io.Reader) (*http.Request, error)
常見的Response
可以使用以下欄位
type Response struct {
Status string // e.g. "200 OK"
StatusCode int // e.g. 200
Proto string // e.g. "HTTP/1.0"
Header Header
Body io.ReadCloser
...
}
接著看一下io.ReadCloser
type ReadCloser interface {
Reader
Closer
}
可以看到ReadCloser
是 interface,接著來看一下Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
Reader
也是一個interface,裡面有定義了Read
方法,因此可以推測出resp.Body
(作為一個 io.ReadCloser
)也實現了 io.Reader
interface。
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
// 使用 http.Get 發送GET請求
resp, err := http.Get("https://www.example.com")
if err != nil {
fmt.Println("Error:", err)
return
}
// 拿到Body最後要確保有關閉連線
defer resp.Body.Close()
// 語法為func ReadAll(r io.Reader) ([]byte, error)
// 會回傳[]byte,要透過string package轉為string
body, _ := io.ReadAll(resp.Body)
fmt.Println("status code: ", resp.StatusCode)
fmt.Println("body: ")
fmt.Println(string(body))
}
在上面的範例中,簡單的使用http.Get
取得網頁的相關資訊。接下來試著寫一個簡單的爬蟲,來爬取ptt。
client := &http.Client{}
req, err := http.NewRequest("GET", "https://www.ptt.cc/bbs/Gossiping/index.html", nil)
if err != nil {
log.Fatal(err)
}
因為八卦版會有詢問是否年滿18,因此會需要設定cookie,而要設定cookie則必須使用自定義請求的方式,使用自定義請求則必須自己建立client進行請求的發送。
先去網站上看cookie的name跟value為多少
接著設定對應的參數
// 設定cookie,可
req.AddCookie(&http.Cookie{Name: "over18", Value: "1"})
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
使用剛剛建立的client放入自定義請求就可以完成請求的發送並取得回傳。
在進行這一步之前需要先安裝一個 go package,github.com/PuerkitoBio/goquery
,主要用來解析html的標籤屬性。
go get github.com/PuerkitoBio/goquery
等一下會用到的方法為
func NewDocumentFromReader(r io.Reader) (*Document, error)
而Document是一個struct
type Document struct {
*Selection
}
要根據css selector下去找資料,可以使用Find方法,而當找到匹配的元素時,對這個元素接著使用Each方法取得相關屬性,像是Text或是Attr等
func (s *Selection) Find(selector string) *Selection
func (s *Selection) Each(f func(int, *Selection)) *Selection
接著依照ptt的網站結構查看title的標籤為div,class為title,而裡面還有一層包著超連結,因此要爬取的路徑就可以依照下面的格式撰寫
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
log.Fatal(err)
}
// 提取文章標題
doc.Find("div.title a").Each(func(index int, item *goquery.Selection) {
title := item.Text()
fmt.Println(title)
})
以下為完整程式碼
package main
import (
"fmt"
"log"
"net/http"
"github.com/PuerkitoBio/goquery"
)
func main() {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://www.ptt.cc/bbs/Gossiping/index.html", nil)
if err != nil {
log.Fatal(err)
}
// 設定cookie模擬已滿18歲的使用者
req.AddCookie(&http.Cookie{Name: "over18", Value: "1"})
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
log.Fatal(err)
}
// 提取文章標題
doc.Find("div.title a").Each(func(index int, item *goquery.Selection) {
title := item.Text()
fmt.Println(title)
})
}