1. 程式人生 > 其它 >分享一波GO的爬蟲

分享一波GO的爬蟲

目錄

分享一波GO的爬蟲

我們一起來回顧一下上一次咱們說到的 使用 GOLANG 傳送郵件

Golang+chromedp+goquery 簡單爬取動態資料 |Go主題月

  • 分享了郵件,電子郵件是什麼
  • 郵件協議有哪些
  • 如何使用GOLANG 傳送電子郵件
  • 傳送電子郵件如何攜帶純文字,HTML內容,附件等
  • 傳送郵件,如何抄送,如何密送
  • 如何提高發送郵件的效能

想看看如何使用 GOLANG 傳送郵件的,歡迎檢視文章如何使用 GOLANG 傳送郵件

還記得之前我們簡單分享了一篇golang 爬取網頁動態資料的文章

Golang+chromedp+goquery 簡單爬取動態資料 |Go主題月

要是有朋友感興趣的話,我們可以詳細的研究一下這個chromedp框架的使用方式

今天咱們來分享一下使用 GO 來爬取網頁的靜態資料

啥是靜態網頁和動態網頁呢

什麼是靜態網頁資料呢?

  • 指的是網頁中沒有程式程式碼,只有HTML,也就是隻有超文字標記語言,字尾名一般是.html , htm , xml 等等
  • 靜態網頁還有一個特點就是,使用者可以直接點選開啟,不管任何人任何時間開啟的頁面的內容都是不變的,html程式碼固定,效果就固定了

那麼順便說一下什麼是動態網頁吧

  • 動態網頁是一種網頁程式設計技術

    動態網頁的網頁檔案中除了HTML

    標記以外,還包括一些特定功能的程式程式碼

    這些程式碼主要是用來瀏覽器和伺服器可以互動的,伺服器端可以根據客戶端的不同請求動態的生成網頁內容,很靈活了

也就是說,動態網頁的頁面程式碼雖然沒有變,可是顯示的內容是可以隨著時間的流逝、不同的環境,資料庫的變化而變化的

GO 來爬取網頁的靜態資料

咱們爬取靜態網頁的資料,例如我們爬取這個網站上的靜態資料,爬取網頁上的賬號和密碼資訊

http://www.ucbug.com/jiaocheng/63149.html?_t=1582307696

咱們爬取這個網站的步驟:

  • 指定一個明確需要爬取的網站
  • 通過 HTTP GET的方式拿到資料
  • 將位元組陣列轉換成字串
  • 使用正則表示式匹配出我們期望的內容(這裡很重要,其實爬取靜態網頁,處理資料和篩選資料耗費的時間比較多
  • 篩選資料,去重等操作(這一步根據個人需求,因人而異,因目標網站而異)

咱們來寫一個DEMO,爬上上述網址的賬號和密碼資訊 ,網頁上我們的要的資訊是這樣的,咱們僅僅是供學習使用,切莫用來做一些不好的事情

package main

import (
   "io/ioutil"
   "log"
   "net/http"
   "regexp"
)

const (
   // 正則表示式,匹配出 XL 的賬號密碼
   reAccount = `(賬號|迅雷賬號)(;|:)[0-9:]+(| )密碼:[0-9a-zA-Z]+`
)

// 獲取網站 賬號密碼
func GetAccountAndPwd(url string) {
   // 獲取網站資料
   resp, err := http.Get(url)
   if err !=nil{
      log.Fatal("http.Get error : ",err)
   }
   defer resp.Body.Close()

   // 去讀資料內容為 bytes
   dataBytes, err := ioutil.ReadAll(resp.Body)
   if err !=nil{
      log.Fatal("ioutil.ReadAll error : ",err)
   }

   // 位元組陣列 轉換成 字串
   str := string(dataBytes)

   // 過濾 XL 賬號和密碼
   re := regexp.MustCompile(reAccount)

   // 匹配多少次, -1 預設是全部
   results := re.FindAllStringSubmatch(str, -1)

   // 輸出結果
   for _, result := range results {
      log.Println(result[0])
   }
}

func main() {
   // 簡單設定log 引數
   log.SetFlags(log.Lshortfile | log.LstdFlags)
   // 傳入網站地址,爬取開始爬取資料
   GetAccountAndPwd("http://www.ucbug.com/jiaocheng/63149.html?_t=1582307696")
}

執行上述程式碼的結果如下:

2021/06/xx xx:05:25 main.go:46: 賬號:357451317 密碼:110120a
2021/06/xx xx:05:25 main.go:46: 賬號:907812219 密碼:810303
2021/06/xx xx:05:25 main.go:46: 賬號:797169897 密碼:zxcvbnm132
2021/06/xx xx:05:25 main.go:46: 迅雷賬號:792253782:1密碼:283999
2021/06/xx xx:05:25 main.go:46: 迅雷賬號:147643189:2密碼:344867
2021/06/xx xx:05:25 main.go:46: 迅雷賬號:147643189:1密碼:267297

可以看出,賬號:開頭的資料 和 迅雷賬號:開頭的資料都被我們爬取下來, 其實爬取靜態網頁的內容不難,時間基本上是花在正則表示式匹配和資料處理上面

根據上述爬取網頁的步驟,咱們可以列一下:

  • 訪問網站 http.Get(url)
  • 讀資料內容 ioutil.ReadAll
  • 資料轉換成字串
  • 設定正則匹配的規則 regexp.MustCompile(reAccount)
  • 開始過濾資料,可以設定過濾的數量 re.FindAllStringSubmatch(str, -1)

當然實際工作中,肯定不會那麼簡單,

例如自己爬取的資料在網站上格式不夠統一,特殊字元比較多且比較雜沒有規律,甚至資料是動態的,沒有辦法通過Get的方式拿到

不過上述的問題都是可以解決的,根據不同的問題,設計出不同的方案和資料的處理,相信這一點遇到的朋友一定能夠解決掉,面對問題,咱們要有解決問題的決心

爬取圖片

看了上面的例子,咱們再來試試爬取網頁上的圖片資料吧,例如在某度上搜索柴犬

是這樣的一個頁面

咱們把url位址列的url複製貼上過來,用來爬取資料

https://image.baidu.com/search/index?tn=baiduimage&ps=1&ct=201326592&lm=-1&cl=2&nc=1&ie=utf-8&word=%E6%9F%B4%E7%8A%AC

由於圖片比較多,咱們設定只匹配 2 張圖片的資料

一起來看看 DEMO

  • 順便將Get url 資料,並轉換成 字串的功能,提取出來,封裝成一個小函式
  • GetPic,專門使用正則表示式進行匹配,可以設定匹配的次數,我們這裡就設定 匹配 2
package main

import (
   "io/ioutil"
   "log"
   "net/http"
   "regexp"
)

const (
   // 正則表示式,匹配出XL的賬號密碼
   reAccount = `(賬號|迅雷賬號)(;|:)[0-9:]+(| )密碼:[0-9a-zA-Z]+`
   // 正則表示式,匹配出 圖片
   rePic = `https?://[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`
)

func getStr(url string)string{
   resp, err := http.Get(url)
   if err !=nil{
      log.Fatal("http.Get error : ",err)
   }
   defer resp.Body.Close()

   // 去讀資料內容為 bytes
   dataBytes, err := ioutil.ReadAll(resp.Body)
   if err !=nil{
      log.Fatal("ioutil.ReadAll error : ",err)
   }

   // 位元組陣列 轉換成 字串
   str := string(dataBytes)
   return str
}

// 獲取網站 賬號密碼
func GetAccountAndPwd(url string,n int) {
   str := getStr(url)
   // 過濾 XL 賬號和密碼
   re := regexp.MustCompile(reAccount)

   // 匹配多少次, -1 預設是全部
   results := re.FindAllStringSubmatch(str, n)

   // 輸出結果
   for _, result := range results {
      log.Println(result[0])
   }
}

// 獲取網站 賬號密碼
func GetPic(url string,n int) {

   str := getStr(url)

   // 過濾 圖片
   re := regexp.MustCompile(rePic)

   // 匹配多少次, -1 預設是全部
   results := re.FindAllStringSubmatch(str, n)

   // 輸出結果
   for _, result := range results {
      log.Println(result[0])
   }
}


func main() {
   // 簡單設定l og 引數
   log.SetFlags(log.Lshortfile | log.LstdFlags)
   //GetAccountAndPwd("http://www.ucbug.com/jiaocheng/63149.html?_t=1582307696", -1)
   GetPic("https://image.baidu.com/search/index?tn=baiduimage&ps=1&ct=201326592&lm=-1&cl=2&nc=1&ie=utf-8&word=%E6%9F%B4%E7%8A%AC",2)
}

執行上述程式碼,結果如下(沒有做去重):

2021/06/xx xx:06:39 main.go:63: https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=4246005838,1103140037&fm=26&gp=0.jpg
2021/06/xx xx:06:39 main.go:63: https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=4246005838,1103140037&fm=26&gp=0.jpg

果然是我們想要的東西,可是光是打印出來,爬取的都是圖片連結,肯定不能滿足我們真實爬蟲的需求,肯定還是要將爬取到圖片下載下來,供我們所用,才是我們想要的

咱們這裡為了演示方便,我們就在上述程式碼上面,加上下載檔案的 小功能,咱們就下載第一張

package main

import (
   "fmt"
   "io/ioutil"
   "log"
   "net/http"
   "regexp"
   "strings"
   "time"
)

const (
   // 正則表示式,匹配出 圖片
   rePic = `https?://[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`
)

// 獲取網頁資料,且把資料轉成 字串
func getStr(url string) string {
   resp, err := http.Get(url)
   if err != nil {
      log.Fatal("http.Get error : ", err)
   }
   defer resp.Body.Close()

   // 去讀資料內容為 bytes
   dataBytes, err := ioutil.ReadAll(resp.Body)
   if err != nil {
      log.Fatal("ioutil.ReadAll error : ", err)
   }

   // 位元組陣列 轉換成 字串
   str := string(dataBytes)
   return str
}

// 獲取圖片資料
func GetPic(url string, n int) {

   str := getStr(url)

   // 過濾 圖片
   re := regexp.MustCompile(rePic)

   // 匹配多少次, -1 預設是全部
   results := re.FindAllStringSubmatch(str, n)

   // 輸出結果
   for _, result := range results {
      // 獲取具體的圖片名字
      fileName := GetFilename(result[0])
	 // 下載圖片
      DownloadPic(result[0], fileName)
   }
}

// 獲取到 檔案的 名字
func GetFilename(url string) (filename string) {
   // 找到最後一個 = 的索引
   lastIndex := strings.LastIndex(url, "=")
   // 獲取 / 後的字串 ,這就是原始檔名
   filename = url[lastIndex+1:]

   // 把時間戳 加 在原來名字前,拼一個新的名字
   prefix := fmt.Sprintf("%d",time.Now().Unix())
   filename = prefix + "_" + filename

   return filename
}

func DownloadPic(url string, filename string) {
   resp, err := http.Get(url)
   if err != nil {
      log.Fatal("http.Get error : ", err)
   }
   defer resp.Body.Close()

   bytes, err := ioutil.ReadAll(resp.Body)
   if err != nil {
      log.Fatal("ioutil.ReadAll error : ", err)
   }

   // 檔案存放的路徑
   filename = "./" + filename

   // 寫檔案 並設定檔案許可權
   err = ioutil.WriteFile(filename, bytes, 0666)
   if err != nil {
      log.Fatal("wirte failed !!", err)
   } else {
      log.Println("ioutil.WriteFile successfully , filename = ", filename)
   }
}

func main() {
   // 簡單設定l og 引數
   log.SetFlags(log.Lshortfile | log.LstdFlags)
   GetPic("https://image.baidu.com/search/index?tn=baiduimage&ps=1&ct=201326592&lm=-1&cl=2&nc=1&ie=utf-8&word=%E6%9F%B4%E7%8A%AC", 1)
}

上述程式碼,咱們加了 2 個函式,輔助我們進行圖片名的修改 和 圖片的下載

  • 獲取到檔案的名字,並使用時間戳重新命名, GetFilename
  • 下載具體的圖片,下載到當前目錄下,DownloadPic

執行上述程式碼,可以看到如下效果

2021/06/xx xx:50:04 main.go:91: ioutil.WriteFile successfully , filename =  ./1624377003_0.jpg

且當前目錄下,已經下載成功了一張圖片,名字為 1624377003_0.jpg

如下是具體圖片的形象照

有大兄弟們會說,我一個協程去下載圖片太慢了,可不可以下載快一點,多個協程一起去下載唄

併發爬取咱們的小柴犬

咱們還記之前說過的 GO 通道 和 sync包 嗎?GO通道和 sync 包的分享, 正好可以實踐一下,這個小功能是比較簡單的,說一下大體的思路,大家感興趣的話,可以去實現一波,

  • 咱們讀取上述網址的資料,並轉換成 字串
  • 使用正則匹配出對應的一系列圖片連結
  • 將每一張圖片連結放到一個有緩衝的通道中,暫且設定緩衝區為 100
  • 咱們再開3個協程去併發的讀取通道的資料,並下載到本地,檔名的修改方式可以參考上述的編碼

怎麼樣,大兄弟們,小夥伴們,感興趣的話,可以實踐一波,要是對爬取動態資料有想法的話,咱們可以溝通和探討一下,一起進步

總結

  • 分享靜態網頁和動態網頁的簡要說明
  • GO 爬取靜態網頁簡單資料
  • GO 爬取網頁上的圖片
  • 併發爬取網頁上的資源

歡迎點贊,關注,收藏

朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力

好了,本次就到這裡,下一次 GO中 gjson 的應用和分享

技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是小魔童哪吒,歡迎點贊關注收藏,下次見~