如何用 Go 實作一個簡單的 PTT 爬蟲

閱讀時間約 9 分鐘


raw-image

👨‍💻 簡介

最近想要透過小實作來撰寫筆記,達到做中學的效果,因此就來實作個小爬蟲順便結合前面學到的package做一個小複習。

建立HTTP Client

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。

爬取PTT八卦版標題

1. 建立http client以及自定義請求

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進行請求的發送。

2. 設定cookie

先去網站上看cookie的name跟value為多少

raw-image

接著設定對應的參數

// 設定cookie,可
req.AddCookie(&http.Cookie{Name: "over18", Value: "1"})

3. 發送請求

resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()

使用剛剛建立的client放入自定義請求就可以完成請求的發送並取得回傳。

4. 解析回傳資訊

在進行這一步之前需要先安裝一個 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)
})
}

📚Reference

avatar-img
17會員
83內容數
golang
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Alan的開發者天地 的其他內容
👨‍💻簡介 今天來介紹一個自己開發後端蠻常用的一個 package,promptui,拿來做menu真的很方便,promptui有兩個主要的輸入模式: Prompt:跳出單行使用者輸入。 Select:提供一個選項列表供使用者選擇。
👨‍💻 簡介 在處理string時,正則表達式是一個非常有用的工具。Go語言的regexp package 可以使用正則表達式,用來執行如檢查string是否匹配某個模式、提取匹配的subString等操作。
👨‍💻 簡介 一開始介紹基本資料型別時有稍微提到一點string的處理,今天介紹string的一些操作,像是檢查的功能、修改的功能、比較的功能等等。
👨‍💻 簡介 昨天講到 os package,今天繼續補充 os package底下的 exec package,這個package主要用來執行外部指令和處理指令的輸入和輸出,包括如何設定指令、執行指令以及處理輸出等等。
👨‍💻 簡介 今天快速介紹一下對檔案的操作所使用的package os,包括檔案和資料夾操作等。 檔案和資料夾操作 os package 可以執行各種檔案和資料夾操作,如建立、讀取、寫入、刪除檔案,以及取得資料夾內容等。
👨‍💻簡介 在 Go 語言中,reflect package是用來檢查和操作變數的type、value和struct。常見用法有檢察 type、調用方法,以及修改變數的value。今天簡單介紹 reflect package的主要功能、使用方法和常見用法。
👨‍💻簡介 今天來介紹一個自己開發後端蠻常用的一個 package,promptui,拿來做menu真的很方便,promptui有兩個主要的輸入模式: Prompt:跳出單行使用者輸入。 Select:提供一個選項列表供使用者選擇。
👨‍💻 簡介 在處理string時,正則表達式是一個非常有用的工具。Go語言的regexp package 可以使用正則表達式,用來執行如檢查string是否匹配某個模式、提取匹配的subString等操作。
👨‍💻 簡介 一開始介紹基本資料型別時有稍微提到一點string的處理,今天介紹string的一些操作,像是檢查的功能、修改的功能、比較的功能等等。
👨‍💻 簡介 昨天講到 os package,今天繼續補充 os package底下的 exec package,這個package主要用來執行外部指令和處理指令的輸入和輸出,包括如何設定指令、執行指令以及處理輸出等等。
👨‍💻 簡介 今天快速介紹一下對檔案的操作所使用的package os,包括檔案和資料夾操作等。 檔案和資料夾操作 os package 可以執行各種檔案和資料夾操作,如建立、讀取、寫入、刪除檔案,以及取得資料夾內容等。
👨‍💻簡介 在 Go 語言中,reflect package是用來檢查和操作變數的type、value和struct。常見用法有檢察 type、調用方法,以及修改變數的value。今天簡單介紹 reflect package的主要功能、使用方法和常見用法。
你可能也想看
Google News 追蹤
Thumbnail
GOB Go官方有提供net/rpc的RPC套件。此套件提供GOB的編/解碼,且支援TCP或HTTP傳輸方式。它可以在伺服器端註冊多個不同類型物件。 遠端存取的要求條件 方法的類型可輸出 方法的本體可輸出 方法必須要有兩個參數是輸出或內建 方法的第二個參數是指標型 方法的返回類型為
xhr 在下面的例子裡,我們首先建立了一個 XMLHttpRequest 物件,並使用 .open() 開啟一個 URL,最後使用 .send() 發出 request。 具體來說步驟有四個: 建立XMLHttpReque 開啟一個請求。 送出請求。 拿到回應後去處理畫面要如何呈現。
※ 什麼是 RESTful API? 這種運用 HTTP 來表達語義的路由設計風格稱為 RESTful API,它描述了如何實現 Web API 的架構。所謂的 API 是應用程式介面 (application programming interface),網址也是一種應用程式的「介面」,故稱為
Thumbnail
之前在【什麼是網路請求(HTTP response)】筆記裡有提到,網路請求遇到 CORS 跨域問題,在開發時可以透過 vite 的反向代理來解決,那麼什麼是反向代理,有反向代理的話是不是也有正向代理呢?
Thumbnail
在開發前後端分離架構時,使用兩個不同網域所遇到跨域請求問題。特別是在POST請求時行為差異大,揭示了「簡單請求」與「預檢請求」的關鍵差異。簡單請求不需預檢,但application/json會觸發預檢請求,需透過特定設定解決。分享這篇文章希望幫助開發者有效處理跨域問題。
Thumbnail
先前幾篇筆記介紹了網路請求,瀏覽器儲存資料的方式,那麼實務上,前端最常需要發送網路請求的時候,就是透過呼叫 API,去向後端工程師發送/請求資料,所以今天來記錄什麼是 API吧!
Thumbnail
gRPC是一款跨平台、高性能的RPC框架,他可以在任何環境下執行,主要用於後端為服務開發。在用戶端應用程式中,可以像本地物件那樣呼叫遠端伺服器的方法,因此可以創建出分散式應用。 使用 到https://github.com/protocolbuffers/protobuf/releases下
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
Thumbnail
HTTP伺服器端 package main import ( "net/http" ) type Refer struct { handler http.Handler refer string } //實現中介軟體邏輯​ func (this *Refer) ServeHTTP(
Thumbnail
GOB Go官方有提供net/rpc的RPC套件。此套件提供GOB的編/解碼,且支援TCP或HTTP傳輸方式。它可以在伺服器端註冊多個不同類型物件。 遠端存取的要求條件 方法的類型可輸出 方法的本體可輸出 方法必須要有兩個參數是輸出或內建 方法的第二個參數是指標型 方法的返回類型為
xhr 在下面的例子裡,我們首先建立了一個 XMLHttpRequest 物件,並使用 .open() 開啟一個 URL,最後使用 .send() 發出 request。 具體來說步驟有四個: 建立XMLHttpReque 開啟一個請求。 送出請求。 拿到回應後去處理畫面要如何呈現。
※ 什麼是 RESTful API? 這種運用 HTTP 來表達語義的路由設計風格稱為 RESTful API,它描述了如何實現 Web API 的架構。所謂的 API 是應用程式介面 (application programming interface),網址也是一種應用程式的「介面」,故稱為
Thumbnail
之前在【什麼是網路請求(HTTP response)】筆記裡有提到,網路請求遇到 CORS 跨域問題,在開發時可以透過 vite 的反向代理來解決,那麼什麼是反向代理,有反向代理的話是不是也有正向代理呢?
Thumbnail
在開發前後端分離架構時,使用兩個不同網域所遇到跨域請求問題。特別是在POST請求時行為差異大,揭示了「簡單請求」與「預檢請求」的關鍵差異。簡單請求不需預檢,但application/json會觸發預檢請求,需透過特定設定解決。分享這篇文章希望幫助開發者有效處理跨域問題。
Thumbnail
先前幾篇筆記介紹了網路請求,瀏覽器儲存資料的方式,那麼實務上,前端最常需要發送網路請求的時候,就是透過呼叫 API,去向後端工程師發送/請求資料,所以今天來記錄什麼是 API吧!
Thumbnail
gRPC是一款跨平台、高性能的RPC框架,他可以在任何環境下執行,主要用於後端為服務開發。在用戶端應用程式中,可以像本地物件那樣呼叫遠端伺服器的方法,因此可以創建出分散式應用。 使用 到https://github.com/protocolbuffers/protobuf/releases下
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
Thumbnail
HTTP伺服器端 package main import ( "net/http" ) type Refer struct { handler http.Handler refer string } //實現中介軟體邏輯​ func (this *Refer) ServeHTTP(