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
		}
	}

}

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