golang爬取Instagram內容下載地址


title: golang爬取Instagram內容下載地址
tags: go
author: Clown95


前言

應該是全網首發吧???如果轉載請添加原作者信息。

因爲工作需要,需要獲取一些小姐姐的圖片和視頻,然後就瞄上了ins。雖然可以利用一些插件保存圖片和視頻,但是這種方法不僅效率低,而且繁瑣。因此就想通過程序來實現,搜索了一圈也沒找到什麼好的ins下載器,果斷自己擼一個。

其實ins獲取到它的內容並不難,它就是使用json傳遞了信息。但是如果想要獲得全部內容就比較麻煩,它的內容是通過動態加載的,沒有什麼翻頁的參數,所以想要獲得到全部內容需要動一點腦筋。

分析

首先我們來分析一下如何獲得ins的圖片和視頻地址,前提是你得有一個科學上網的梯子。

json地址參數分析

我們隨機選擇找一個小姐姐的主頁

https://www.instagram.com/angelaqiqi_99/

通過瀏覽器的開發者工具,我們可以看到ins傳遞了一些json信息
enter description here

我們選擇最後一個json地址,並把這個它解碼一下看看它有什麼信息

https://www.instagram.com/graphql/query/?query_hash=44efc15d3c13342d02df0b5a9fa3d33f&variables={“id”:“436245394”,“first”:12,“after”:“QVFENGZuREJpVFhrakVkdEc0cGpUTmI2XzNzWi1fQ1NJYWM4MTh4M0MzMDNNc3VWM2hzVkRmUEZXMW54REV5ZjZfWl8tMlh4UmkySVpTTEtJcUs0OEVWWg==”}

我們通過這個地址可以看到,它與兩個參數query_hashvariablesvariables裏面又包含了三個參數分別是idfirstafter ,並且通過這個名字我們可以知道,網站數據的變化跟它有關係。。

然後我們滾動網頁,查看下一個json的地址有什麼不一樣的地方
enter description here

我們還是來看一下解碼後的地址:

https://www.instagram.com/graphql/query/?query_hash=44efc15d3c13342d02df0b5a9fa3d33f&variables={“id”:“436245394”,“first”:12,“after”:“QVFEX3VVWjZEcExrbmxWdUZfQUMzeUEzdnd1UTZPSU1FQllMRVNDSmdBeVJCRVlVVE80eS1rcmdKcmlUT0lwRENsZ0xac2k4cmxEVlR5R0FwZTNiX3ZKYQ==”}

通過對比我們發現,after這個產生不一樣,其他幾個都一樣。 接下來我們猜測一下這些參數的用途。

  • query_hash是查詢數據的hash值(這個值是固定的,ins會隔一段時間更換一次,可能是一個月,也可能是兩個月)
  • id是博主的賬號id
  • first是每次顯示內容的數量
  • after根據名字猜測,在…之後,說明它是類似於索引的東西,主要用來控制數據加載的

圖片和視頻地址分析

現在我們已經知道了所有參數的用途,現在我們來具體的看下json的內容,我們以第二個地址爲例

enter description here

通過圖片我們可以看到, display_url它存放了一個圖片的地址。is_video通過意思我們可以知道它用來判斷內容是否爲視頻。

我們在找一段is_videotrue驗證一下我們的想法。

enter description here

果不其然,is_videotrue的情況下,多了一個鍵video_url,不用看都知道這是存放視頻地址的, display_url這時候是這個視頻的封面圖片。

知道了這三個鍵,那麼我們在進行數據獲取的就可以通過is_video來屏蔽掉視頻封面的圖片。

內容加載分析

現在我們已經知道了圖片和視頻的存放地址,接下來纔是重點,ins是如何加載更多內容的。前面我們猜測過after這個參數是控制內容加載的。

現在我們已經知道了after參數的用途,現在我們就需要找到它是在什麼地方傳遞的。已知第一個after的值爲QVFENGZuREJpVFhrakVkdEc0cGpUTmI2XzNzWi1fQ1NJYWM4MTh4M0MzMDNNc3VWM2hzVkRmUEZXMW54REV5ZjZfWl8tMlh4UmkySVpTTEtJcUs0OEVWWg=="

我們在源文件裏面搜索它,果然我們在script裏面搜索到了它,只不過它的鍵名是end_cursor ,看到這麼名字你是不是已經猜到了什麼,結束遊標,它就是我們要找的那個。

如果你仔細的看了json的內容,那麼你應該對它有印象。

enter description here

在json中page_info裏面包含了has_next_pageend_cursor

  • has_next_page 判斷是否有下一頁
  • end_cursor 當前頁的結束遊標

當然一個json裏面包含了好幾個page_info,最主要的就是第一個page_info,這個我已經驗證過。當主要數據到底的時候,只有這個page_infoend_cursor內容爲空。

現在我們已經知道了第一頁的end_cursorscript裏面,第二頁的end_cursor在第一條josn裏面,以此類推,第三頁的end_cursor,在第二條josn裏面。

實現

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"time"
)

type Instargam struct {
	Data struct {
		User struct {
			EdgeOwnerToTimelineMedia struct {
				//Count int `json:"count"`
				Edges []struct {
					Node struct {
						/*
							DashInfo struct {
								IsDashEligible    bool   `json:"is_dash_eligible"`
								NumberOfQualities int  `json:"number_of_qualities"`
								VideoDashManifest string `json:"video_dash_manifest"`
							} `json:"dash_info"`

							EdgeMediaToComment struct {
								PageInfo struct {
									EndCursor string `json:"end_cursor"`
								} `json:"page_info"`
							} `json:"edge_media_to_comment"`
						*/
						DisplayURL            string `json:"display_url"`
						EdgeSidecarToChildren struct {
							Edges []struct {
								Node struct {
									/*
										DashInfo struct {
											VideoDashManifest string `json:"video_dash_manifest"`
										} `json:"dash_info"`

									*/
									DisplayURL string `json:"display_url"`
									IsVideo    bool   `json:"is_video"`
									VideoURL   string `json:"video_url"`
								} `json:"node"`
							} `json:"edges"`
						} `json:"edge_sidecar_to_children"`
						IsVideo  bool   `json:"is_video"`
						VideoURL string `json:"video_url"`
					} `json:"node"`
				} `json:"edges"`
				PageInfo struct {
					EndCursor   string `json:"end_cursor"`
					HasNextPage bool   `json:"has_next_page"`
				} `json:"page_info"`
			} `json:"edge_owner_to_timeline_media"`
		} `json:"user"`
	} `json:"data"`
}

// 獲取網頁源代碼
func GetHtml(Insurl string) (html string) {
	// 解析代理地址
	proxy, err := url.Parse("http://127.0.0.1:1087") //加載本地代理
	//設置網絡傳輸
	netTransport := &http.Transport{
		Proxy:                 http.ProxyURL(proxy),
		MaxIdleConnsPerHost:   10,
		ResponseHeaderTimeout: time.Second * time.Duration(5),
	}
	httpClient := &http.Client{
		Timeout:   time.Second * 10,
		Transport: netTransport,
	}
	request, err := http.NewRequest("GET", Insurl, nil)
	if err != nil {
		log.Println(err)
	}
	request.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36") //模擬瀏覽器User-Agent
	request.Header.Set("Cookie", `ig_did=5448DAB0-E424-48F9-97DF-3A386834D9BE; mid=XsH7PQAEAAG55HTxRZVxBxvZICeY; ds_user_id=25850748707; shbid=2673; shbts=1590030929.7257333; csrftoken=OvLzaajy6C9peOrWQeTfo61GBRY0xLGz; sessionid=25850748707%3ATgB2CXKqw4BFUu%3A23; rur=FTW; urlgen="{\"35.221.222.156\": 15169\054 \"34.92.32.194\": 15169\054 \"34.92.235.238\": 15169}:1jcJdR:nlKH_w6YMze8RyJkjEbQ5Sc9UxA"`)

	res, err := httpClient.Do(request)
	if err != nil {
		log.Println(err)
		return
	}
	defer res.Body.Close()
	//判斷是否成功訪問,如果成功訪問StatusCode應該爲200
	if res.StatusCode != http.StatusOK {
		log.Println(err)
		return
	}
	content, _ := ioutil.ReadAll(res.Body)
	return string(content)
}

// 通過主頁獲取第一個after的值
func GetAfterByHtml(homepage string) string {
	regex := `end_cursor":"(.*?)"},"edges`
	rp := regexp.MustCompile(regex)
	after := rp.FindStringSubmatch(homepage)
	return after[1]
}

// 通過主頁獲得博主的賬號ID
func GetIdByHtml(homepage string) string {
	regex := `{"id":"(.*?)","username`
	rp := regexp.MustCompile(regex)
	id := rp.FindStringSubmatch(homepage)
	return id[1]
}

//獲取博主的賬號名
func GetUserName(homepage string) string {
	l1 := len("https://www.instagram.com/")
	l2 := len(homepage)
	return homepage[l1:l2-1] + ".txt"
}

// 拼接查詢地址
func SetQueryUrl(query_hash, id, first, after string) string {
	url := fmt.Sprintf("https://www.instagram.com/graphql/query/?query_hash=%s&variables={\"id\":\"%s\",\"first\":%s,\"after\":\"%s\"}", query_hash, id, first, after)
	return url
}

//json轉Struct
func Json2Struct(strjson string) Instargam {
	var ins Instargam
	json.Unmarshal([]byte(strjson), &ins)
	return ins
}

//通過json得到After
func GetAfter(ins Instargam) string {
	return ins.Data.User.EdgeOwnerToTimelineMedia.PageInfo.EndCursor
}

// 判斷網站是否加載到底
func IsEnd(ins Instargam) bool {
	return ins.Data.User.EdgeOwnerToTimelineMedia.PageInfo.HasNextPage
}

// 通過json得到圖片和視頻下載地址
func GetDownloadUrl(savefile string, ins Instargam) {
	for _, v := range ins.Data.User.EdgeOwnerToTimelineMedia.Edges {
		var content string
		if v.Node.IsVideo != true {
			fmt.Println(v.Node.DisplayURL)
			content = v.Node.DisplayURL + "\n"
		} else {
			fmt.Println(v.Node.VideoURL)
			content += v.Node.VideoURL + "\n"
		}
		for _, v1 := range v.Node.EdgeSidecarToChildren.Edges {
			if v1.Node.IsVideo != true {
				fmt.Println(v1.Node.DisplayURL)
				content = v1.Node.DisplayURL + "\n"
			} else {
				fmt.Println(v1.Node.VideoURL)
				content += v1.Node.VideoURL + "\n"
			}
		}
		WirteText(savefile, content)
	}
}

// 寫入txt文件
func WirteText(savefile string, txt string) {
	f, err := os.OpenFile(savefile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777)
	if err != nil {
		fmt.Println("os Create error: ", err)
		return
	}
	defer f.Close()
	bw := bufio.NewWriter(f)
	bw.WriteString(txt)
	bw.Flush()
}

func main() {
	var homepage string
	fmt.Scanln(&homepage)
	query_hash := "44efc15d3c13342d02df0b5a9fa3d33f"
	first := 12
	//通過html獲取第一條json地址
	html := GetHtml(homepage)
	after := GetAfterByHtml(html)
	id := GetIdByHtml(html)
	first_query_url := SetQueryUrl(query_hash, id, strconv.Itoa(first), after) //  第一條json地址

	//通過json獲取內容
	jsonconent := GetHtml(first_query_url)
	for {
		ins := Json2Struct(jsonconent)
		next_after := GetAfter(ins) //通過json獲得after的值
		next_query_url := SetQueryUrl(query_hash, id, strconv.Itoa(first), next_after)
		GetDownloadUrl(GetUserName(homepage), ins)
		jsonconent = GetHtml(next_query_url)

		if !IsEnd(ins) { //如果頁面加載到底,結束循環
			break
		}
	}

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章