今天心血來潮,想用GO寫爬蟲,然後在網上看別人的程式碼粗略寫了一個爬蟲。
由於初學GO,沒有系統學習,只是根據自己思路,然後百度 = =!。。。望大家多指教。
思路:
1.從某企鵝視訊網站 xxxxx.com/search.html?act=102&keyWord=XXX 中搜尋某個明星的視訊。
2.在視訊的html程式碼中用 GO 的github.com/PuerkitoBio/goquery 包解析標籤拿到視訊地址(如地址為 XX.COM?XXX&XXX&XX&VID=123, 目標:要拿到123),再根據GO的字串擷取拿到VID。
3.從某企鵝視訊網站的JS檔案中找到視訊詳情的介面(如 XXX.COM?callback=XXX&VID=123),將上一步拿到的VID帶入進去,拿到視訊詳情(jsonp格式資料:包含視訊圖片,播放地址,播放量,播放時長等等資訊)。
4.用GO的字串擷取把jsonp擷取為json資料,使用 json.Unmarshal 解析到提前準備好的STRUCT 上面。
5.定義切片,把拿到的一個個視訊詳情append到切片中。
6.遍歷切片,資料寫入到資料庫(由於GORM不支援批量寫入,得手動拼接SQL寫入資料庫)。
7.肉眼觀察GO和PHP爬相同的視訊所花的時間。
開始:
package models
import (
"bytes"
"encoding/json"
"fmt"
"github.com/PuerkitoBio/goquery"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"video/gin/databases"
)
type SpiderStar struct {
Status string `json:"status"`
Info string `json:"info"`
Data DataList `json:"data"`
}
type DataList struct {
List []Result `json:"list"`
}
type Result struct {
Id int `json:"id"`
Name string `json:"name"`
}
type Spider struct {
DataType string
}
type VideoDetail struct {
Vl Vl `json:"vl"`
Preview int `json:"preview"`
}
type Vl struct {
Vi []Vi `json:"vi"`
}
type Vi struct {
Fn string `json:"fn"`
Fvkey string `json:"fvkey"`
Ul Ui `json:"ul"`
Ti string `json:"ti"`
}
type Ui struct {
Ui []UiData `json:"ui"`
}
type UiData struct {
Url string `json:"url"`
}
func (spider *Spider) SpiderAndInsertVideo() ([]map[string]interface{}, error) {
// 這裡是找的一個明星資料的介面,後面某視訊網站需要用到明星的名字
// 傳送請求,拿到明星的json資料,對映到struct中
starUrl := "https://XXXXXX/S/dex?page=1&pagesize=10"
client := http.Client{}
req, err := http.NewRequest("POST", starUrl, nil)
if err != nil {
log.Printf("http.NewRequest star err:%v", err)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var spiderStar SpiderStar
err = json.Unmarshal([]byte(body), &spiderStar)
if err != nil {
return nil, err
}
// 每次我都會把自己庫的明星資料刪掉,更換從介面拿到的最新的明星資料
// 拼接字串,先刪除資料再批量寫入明星資訊
var buffer bytes.Buffer
sql := "insert into `stars` (`name`, `created_at`, `updated_at`) values "
if _, err := buffer.WriteString(sql); err != nil {
return nil, err
}
for i, item := range spiderStar.Data.List {
if i == len(spiderStar.Data.List)-1 {
buffer.WriteString(fmt.Sprintf("('%s','%s','%s');", item.Name, time.Now().Format("2006-01-02 15:04:05"), time.Now().Format("2006-01-02 15:04:05")))
} else {
buffer.WriteString(fmt.Sprintf("('%s','%s','%s'),", item.Name, time.Now().Format("2006-01-02 15:04:05"), time.Now().Format("2006-01-02 15:04:05")))
}
}
err = databases.DB.Exec("Delete From stars").Debug().Error
if err != nil {
return nil, err
}
err = databases.DB.Exec(buffer.String()).Debug().Error
if err != nil {
return nil, err
}
// 這個是後面需要隨機獲取視訊的播放量,做的假資料
playCount := [4]string{
"3萬+", "5萬+", "7萬+", "10萬+",
}
// 根據明星的名字開始爬視訊,需要urlencode(明星名字)
var allData []map[string]interface{}
for _, item := range spiderStar.Data.List {
spiderListUrl := "https://xxxx.com/s.html?act=102&keyWord=" + url.QueryEscape(item.Name)
req, err := http.NewRequest("GET", spiderListUrl, nil)
if err != nil {
log.Printf("http.NewRequest vid spider err:%v", err)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
document, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
fmt.Println("抓取" + spider.DataType + "失敗")
return []map[string]interface{}{}, nil
}
// 遍歷標籤,獲取視訊ID
document.Find(".search_item_h").EachWithBreak(func(i int, selection *goquery.Selection) bool {
urlString, boolUrl := selection.Find("a").Attr("href")
if boolUrl {
// 字串擷取,獲取vid,如http://xxx.com/page/k/3/q/k3137yyxmsq.html要拿到k3137yyxmsq
start := strings.LastIndex(urlString, "/")
end := strings.LastIndex(urlString, ".")
vid := urlString[start+1 : end]
// 調視訊詳情介面獲取視訊詳情
videoDetailUrl := "https://xxxx.com/getxxxx?callback=xxxxxx&ge=0&t=auto&pe=json&id=ab40fd9fca45e4&id=e8af7e38009=v3010&r=0&ap=3.4.40&st=xxxx.com&t=httpsF%2Fxxx.com%2Fx%l%2Fdeo%2Frn&re=xxxx.com&ss=1&sps=d=" + strconv.Itoa(int(time.Now().Unix())) + "&spm=4&id=" + vid + "e=auo&ch=&show1080p=fals&e=1&clip=4&rc=&f=ao&derc=1&_=C103p%2BIKA10pd%3D&2=jw5b401F2g%3D01953="
req, err := http.NewRequest("GET", videoDetailUrl, nil)
if err != nil {
log.Printf("http.NewRequest detail spider err:%v", err)
return false // 這裡的return false 是跳出迴圈用的
}
resp, err := client.Do(req)
if err != nil {
log.Printf("http.NewRequest detail do spider err:%v", err)
return false
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("http.NewRequest detail read spider err:%v", err)
return false
}
// 得到的是jsonp資料,要轉json
startPosition := strings.Index(string(body), "(")
endPosition := strings.LastIndex(string(body), ")")
jsonString := string(body)[startPosition+1 : endPosition]
var videoDetail VideoDetail
err = json.Unmarshal([]byte(jsonString), &videoDetail)
if err != nil {
log.Printf("http.NewRequest detail unmarshal spider err:%v", err)
return false
}
// 去掉不合法的視訊
title := videoDetail.Vl.Vi[0].Ti
if strings.Contains(title, "抖音") ||
strings.Contains(title, "douyin") ||
strings.Contains(title, "抽菸") ||
strings.Contains(title, "整容") ||
strings.Contains(title, "戀情") ||
strings.Contains(title, "變形計") ||
strings.Contains(title, "孟子義") ||
strings.Contains(title, "袁冰妍") ||
strings.Contains(title, "吸菸") {
return false
}
// 拿到視訊詳情
allData = append(allData, map[string]interface{}{
"date": time.Now().Format("2006-01-02"),
"video_id": vid,
"url": videoDetail.Vl.Vi[0].Ul.Ui[0].Url + videoDetail.Vl.Vi[0].Fn + "?vkey=" + videoDetail.Vl.Vi[0].Fvkey,
"title": title,
"expire_time": time.Now().Unix() + 3600*4,
"thumb": "http://puui.qpic.cn/qqvideo_ori/0/" + vid + "_496_280/0",
"star_id": item.Id,
"play_time": strconv.Itoa(videoDetail.Preview),
"play_count": playCount[rand.Intn(3)],
"source_type": 1,
"source_name": "XX視訊",
"sort": rand.Intn(999),
"status": 1,
"created_at": time.Now().Format("2006-01-02 15:04:05"),
"updated_at": time.Now().Format("2006-01-02 15:04:05"),
})
// 拼接字串,批量寫入
var buffer bytes.Buffer
sql := "insert into `new_star_videos` (`date`,`star_id`,`video_id`, `expire_time`, `title`, `thumb`, `url`, `play_count`, `play_time`, `source_type`, `source_name`, `sort`, `status`, `created_at`, `updated_at`) values"
if _, err := buffer.WriteString(sql); err != nil {
return false
}
for i, item := range allData {
if i == len(allData)-1 {
buffer.WriteString(fmt.Sprintf("('%s', %d,'%s',%d,'%s','%s','%s','%s','%s',%d,'%s',%d,%d,'%s','%s');", item["date"], item["star_id"], item["video_id"], item["expire_time"], item["title"], item["thumb"], item["url"], item["play_count"], item["play_time"], item["source_type"], item["source_name"], item["sort"], item["status"], item["created_at"], item["updated_at"]))
} else {
buffer.WriteString(fmt.Sprintf("('%s', %d,'%s',%d,'%s','%s','%s','%s','%s',%d,'%s',%d,%d,'%s','%s'),", item["date"], item["star_id"], item["video_id"], item["expire_time"], item["title"], item["thumb"], item["url"], item["play_count"], item["play_time"], item["source_type"], item["source_name"], item["sort"], item["status"], item["created_at"], item["updated_at"]))
}
}
err = databases.DB.Exec(buffer.String()).Debug().Error
if err != nil {
return false
}
}
return true;
})
}
return allData, nil
}
注:很多url地址我用XXX代替了…。
我是用gin寫的, 自己建router+controller+models,上面是models裡面的程式碼。router裡面寫的一個路由呼叫這個爬蟲,方便用postman除錯,後面我會學習用定時任務執行以及加代理(目前還不會 = =!,還望大佬指教~)。
本地環境,肉眼看用PHP爬並寫入4000條視訊要11秒左右。
用GO需要9秒左右。
最後貼一個爬到的視訊圖片:
本作品採用《CC 協議》,轉載必須註明作者和本文連結