title: golang爬取Instagram內容下載地址
tags: go
author: Clown95
前言
應該是全網首發吧???如果轉載請添加原作者信息。
因爲工作需要,需要獲取一些小姐姐的圖片和視頻,然後就瞄上了ins。雖然可以利用一些插件保存圖片和視頻,但是這種方法不僅效率低,而且繁瑣。因此就想通過程序來實現,搜索了一圈也沒找到什麼好的ins下載器,果斷自己擼一個。
其實ins獲取到它的內容並不難,它就是使用json傳遞了信息。但是如果想要獲得全部內容就比較麻煩,它的內容是通過動態加載的,沒有什麼翻頁的參數,所以想要獲得到全部內容需要動一點腦筋。
分析
首先我們來分析一下如何獲得ins的圖片和視頻地址,前提是你得有一個科學上網的梯子。
json地址參數分析
我們隨機選擇找一個小姐姐的主頁
https://www.instagram.com/angelaqiqi_99/
通過瀏覽器的開發者工具,我們可以看到ins傳遞了一些json信息
我們選擇最後一個json地址,並把這個它解碼一下看看它有什麼信息
https://www.instagram.com/graphql/query/?query_hash=44efc15d3c13342d02df0b5a9fa3d33f&variables={“id”:“436245394”,“first”:12,“after”:“QVFENGZuREJpVFhrakVkdEc0cGpUTmI2XzNzWi1fQ1NJYWM4MTh4M0MzMDNNc3VWM2hzVkRmUEZXMW54REV5ZjZfWl8tMlh4UmkySVpTTEtJcUs0OEVWWg==”}
我們通過這個地址可以看到,它與兩個參數query_hash
和variables
,variables
裏面又包含了三個參數分別是id
、first
和after
,並且通過這個名字我們可以知道,網站數據的變化跟它有關係。。
然後我們滾動網頁,查看下一個json的地址有什麼不一樣的地方
我們還是來看一下解碼後的地址:
https://www.instagram.com/graphql/query/?query_hash=44efc15d3c13342d02df0b5a9fa3d33f&variables={“id”:“436245394”,“first”:12,“after”:“QVFEX3VVWjZEcExrbmxWdUZfQUMzeUEzdnd1UTZPSU1FQllMRVNDSmdBeVJCRVlVVE80eS1rcmdKcmlUT0lwRENsZ0xac2k4cmxEVlR5R0FwZTNiX3ZKYQ==”}
通過對比我們發現,after
這個產生不一樣,其他幾個都一樣。 接下來我們猜測一下這些參數的用途。
query_hash
是查詢數據的hash值(這個值是固定的,ins會隔一段時間更換一次,可能是一個月,也可能是兩個月)id
是博主的賬號idfirst
是每次顯示內容的數量after
根據名字猜測,在…之後,說明它是類似於索引的東西,主要用來控制數據加載的
圖片和視頻地址分析
現在我們已經知道了所有參數的用途,現在我們來具體的看下json的內容,我們以第二個地址爲例
通過圖片我們可以看到, display_url
它存放了一個圖片的地址。is_video
通過意思我們可以知道它用來判斷內容是否爲視頻。
我們在找一段is_video
爲true
驗證一下我們的想法。
果不其然,is_video
爲true
的情況下,多了一個鍵video_url
,不用看都知道這是存放視頻地址的, display_url
這時候是這個視頻的封面圖片。
知道了這三個鍵,那麼我們在進行數據獲取的就可以通過is_video
來屏蔽掉視頻封面的圖片。
內容加載分析
現在我們已經知道了圖片和視頻的存放地址,接下來纔是重點,ins是如何加載更多內容的。前面我們猜測過after
這個參數是控制內容加載的。
現在我們已經知道了after
參數的用途,現在我們就需要找到它是在什麼地方傳遞的。已知第一個after
的值爲QVFENGZuREJpVFhrakVkdEc0cGpUTmI2XzNzWi1fQ1NJYWM4MTh4M0MzMDNNc3VWM2hzVkRmUEZXMW54REV5ZjZfWl8tMlh4UmkySVpTTEtJcUs0OEVWWg=="
我們在源文件裏面搜索它,果然我們在script
裏面搜索到了它,只不過它的鍵名是end_cursor
,看到這麼名字你是不是已經猜到了什麼,結束遊標
,它就是我們要找的那個。
如果你仔細的看了json的內容,那麼你應該對它有印象。
在json中page_info
裏面包含了has_next_page
和end_cursor
。
has_next_page
判斷是否有下一頁end_cursor
當前頁的結束遊標
當然一個json裏面包含了好幾個page_info
,最主要的就是第一個page_info
,這個我已經驗證過。當主要數據到底的時候,只有這個page_info
的end_cursor
內容爲空。
現在我們已經知道了第一頁的end_cursor
在script
裏面,第二頁的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
}
}
}