写于2018-12-27
因为需要做一个Ins相关的app,需要爬取ins的数据,总共试验了一个多礼拜最后宣告失败。。。
2019-1-8已成功
2019-5-20日更新,目前在shareData里已经没有
rhx_gis
这个字段了,暂时的解决方式是传入一个空字符串,后续待观察,估计接口会大改。
2019-6-20日更新:1、获取ProfilePageContainer.js方法变了,现在它是在一个script标签中2、目前请求头中不需要添加x-instagram-gis的加密字段
2020-06-20日更新:除了query接口,ins相关接口已经需要登录才能访问,暂时只能通过请求时加入cookie(有个字段sessionid),因此只能后端去请求数据了,纯前端无法解决。你可以使用instagram-web-api,instagram-private-api,单这些库都是nodejs环境的,游览器下无法使用。我们是把项目转成混合开发了,可以通过Instagram api 去登录,才可以使用这些接口
特此记录一下
前言
某天领导说,咱们app默认只能获取用户前12张照片,我查了一下,因为默认爬取首页,里面的照片数据确实只有12条。我把原因balalal一说,领导立刻很机智的回答我:小宣,你是不是会node,你来弄一下吧。
好吧,我会node
开始观察
首先打开Instagram的首页,选上我喜欢的霉霉。F12,看network.
提示:需要科学上网
这里出现了2个请求,咱们主要看第二个,点开preview,没问题数据出来了,其中display_url就是咱们需要的啦
获取初始数据
我大概说一下如何获取用户资料以及前12张照片,方法很简单,直接get请求用户的首页,找到其中的shareData即可,这里几乎网上大部分人都能找到,给大家一个示例代码。这里可以直接用前端去get,也可以放在后端。 下述代码是在前端直接获取的
let username='taylorswift'
let url='https://www.instagram.com/' + username + '/'
axios.get(url)
.then(res=>{
let dataStr = JSON.stringify(res)
let datarep = dataStr.replace(/(\r\n)|(\n)/g, "")
let n = datarep.match(/(window._sharedData\s?)(=\s?)(.*?);<\/script>/)[3]
let shareData = JSON.parse(n.replace(/\\/g, ""))
})
这里的shareData大概是这样
这里面比较有用的就是
rhx_gis
大部分用户信息都在entry_data.ProfilePage[0].graphql.user
下
其中图片等数据在edge_owner_to_timeline_media.edges
下,page_info
下主要是分页接口需要的数据(重要)
分页接口
当你请求完首页时你会发现,edges下只有12张照片,我之前之所以没发现这个问题,是因为我爬我自己的账号,上面总共还没12张照片
这时候再打开network就会发现,你就会很轻松的发现有一个query的请求,这就是分页接口
首先寻找queryHash,把它复制,然后在所有页面以及js中寻找,最后发现它在
ProfilePageContainer.js/68f09467caf1.js
文件中,
但是你不能写死地址直接访问,因为:这tm是个不固定的网址,它会根据国家等信息变化。
但是这怎么能逃过我的火眼金睛,在element中找到一个link标签
当然从前端是无法处理的,当你想给请求头上加上cookie这些东西时候,就会被拦截,于是转而用node访问。
那就先写第一步的代码吧。
const cheerio = require("cheerio")
const rp = require('request-promise')
const requestOptions = {
resolveWithFullResponse: true,
proxy: 'http://localhost:1080', //这里加个代理,科学上网
headers: {
"user-agent": userAgent,//这里放上你从游览器复制的userAgent
"x-requested-with": "XMLHttpRequest"
}
}
async function (username){
const res = await rp.get('https://www.instagram.com/' + username, requestOptions)
const $ = cheerio.load(res.body)
let datas = '', jsFilesUrl= ''
//这里需要先获取shareData中的一些信息备用
$('script').each(function(indexInArray, valueOfElement) {
let htmlStr=$('script').eq(indexInArray).html()
if (htmlStr.indexOf("window._sharedData") == 0) {
datas = $('script').eq(indexInArray).html()
datas = datas.substring(20).replace(/\;/g, "");
}
let src=$('script').eq(indexInArray).prop('src')
if(src&&src.indexOf('ProfilePageContainer')){
jsFiles = 'https://www.instagram.com' + src
}
})
const jsContent = await rp.get(jsFiles, requestOptions)
let queryHashList = jsContent.body.match(/(queryId:\s?)(\S*),/g)
let result = queryHashList.map(element => {
return element = element.substr(9, 32)
})
//获取参数
let queryHash = result[2],
PageInfo = datas.entry_data.ProfilePage[0].graphql.user.edge_owner_to_timeline_media.page_info,
userId = datas.entry_data.ProfilePage[0].graphql.user.id,
gisCode = datas.rhx_gis,
csrftoken = datas.config['csrf_token'],
userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/71.0.3578.98 Safari/537.36'
//开始请求分页接口
let BASE_URL = 'https://www.instagram.com/graphql/query/?'
let options = {
resolveWithFullResponse: true,
proxy: 'http://localhost:1080',
headers: {
"x-instagram-gis": gisCode,
"x-requested-with": "XMLHttpRequest",
"user-agent": userAgent,
}
}
let variables = {
id: userId,
first: 12,
after: PageInfo['end_cursor']
}
let str = escape(JSON.stringify(variables))
let url = `${BASE_URL}query_hash=${queryHash}&variables=${str}`
const nextPage = await rp.get(url, options)
console.log(nextPage)
}
```
>现实依旧无情的打击了我,返回结果居然是403。我仿佛感觉到了facebook嘲笑的目光

## 最新进展
我试遍了几乎github上的所有库,目前找到能用的只有一个[instagram-web-api](https://github.com/jlobos/instagram-web-api)
但是需要登录才能获取分页,结合express,核心代码如下:
<br>
const Instagram = require('instagram-web-api')
const accounts = require('./accountList.json')
app.get('/api/ins/morepic', async(req, res) => {
let after = req.query.after,
name = req.query.username
if ((name && name === '') || (after && after === '')) {
res.sendStatus(400)
}
let len = accounts.length
let randIdx = parseInt(Math.random() * len)
let account = accounts[randIdx]
let client = new Instagram({
username: account.username,
password: account.password
}, { proxy: 'http://127.0.0.1:1080' })
await client.login()
let photoData = await client.getPhotosByUsername({ username: name, after: after })
res.json(photoData)
});
我注册了3个账号,然后随机登录,但是这样肯定是不行的,一旦访问量过大,肯定会触发Instagram的安全机制,所以我还在努力
更新2018-12-29
同事提出一个新思路,首先服务启动的时候,就登陆一个账号,取得一个session,然后通过websocket实时接收和发送数据。这样的话就相当于一个账号一直登录去查询数据。测试过确实也可以,但是如果出现错误,这个进程就会down掉(nodejs是单进程),这时候需要一个子进程去维护,记录错误,重启主进程。虽然有pm2这种工具,但是还是不太会用,需要继续研究
更新2019-1-3
目前发现ins的接口是需要MD5加密的,我使用了blueimp-md5这个包,示例代码如下
const md5 = require('blueimp-md5')
let variables = {
id: userId,
first: 12,
after: PageInfo['end_cursor']
},
gisCode=datas.rhx_gis//datas是第一次访问是爬取的shareData
let needcrpyto = `${gisCode}:${JSON.stringify(variables)}`
let skey = md5(needcrpyto)
//然后将这个skey放入header的x-instagram-gis
现在有出现了一个问题,我第一次直接访问获取到的rhx_gis好像是错的,我把游览器中rhx_gis赋值,确实可以访问到,但是用爬取到的rhx_gis就是不行,估计和平台有关。通过游览器直接访问,加密算法是MD5,如果被断定为后台访问,加密算法就会变。
更新2019-1-8
目前发现是因为我第一次请求时请求头写错了,导致
rhx_gis
出错,所以以上代码基本上可以实现爬取分页数据。上面的代码已经修改正确,在此就不再赘述一遍了。
结语
通过这次爬虫,让我对nodejs的request了解更深了,也对网站前后端访问,以及如何进行加密验证也有了一点了解。大概总结一下爬取ins接口的过程。
-
第一步,你需要get请求用户首页,获取
window._shareData
,需要保存的数据主要有rhx_gis
,图片列表在edge_owner_to_timeline_media.edges
下,page_info
下主要是分页接口需要的数据(重要),这一步可以放在前端访问 -
第二步,获取ProfilePageContainer.js路径下的那个js文件的内容,查找出queryId,这个值就是访问分页接口时的query_hash, 这一步其实也是前后端皆可,我是放到后端了,因为nodejs的cheerio比较好用
-
第三步,组合数据,加密,你需要按照network里的格式组装
组装完成后,按照如下格式
${gisCode}:${JSON.stringify(variables)}
,使用MD5加密,格式为32位小写。最后放入请求头的x-instagram-gis中。 -
最后一步,请求分页接口即可,地址为
'https://www.instagram.com/graphql/query/?'
其实整个接口最主要的就是user-agent,只要你填写游览器的useragent,一般不会有问题。cookie不是必须的,csrftoken也是一个烟雾弹而已。
谢谢大家。