获取 Instagram 用户所有图片手记

我是一个 Instagram 重度用户,关注了很多有意思的 po 主,因此经常需要将这些 po 主的所有图片打包下载。(国外非常注重版权,下载图片只能个人使用,商业用途请严格遵循版权保护流程。)

ig 的账户分为公开账户和私有账户。这里只讨论公开账户的图片的获取方式。

我们知道在 ig 用户的 profile 业务可以看到用户发布的所有图片,例如:https://www.instagram.com/instagram/,即格式为:

很久以前,我们通过 https://www.instagram.com/{username}/?__a=1 的方式可以获取该用户的所有图片。但是这个方法被大家玩烂了,ig做了一些限制,先是下线了这个私有接口,后来又恢复了这个接口,但是需要用户处于登录状态。考虑到这个接口被下线过,继续使用该接口不确定性较大,且让帐户处于登录状态去调用这种私有接口被封号的概率是很大的。于是,尝试使用其他方式。

前端无秘密,ig 的接口其实还是比较奔放的。在用户 profile 页面转了一圈,发现其使用了一个通用的 graphql 接口来获取用户图片:

那么接下来的事情简单了,确定其请求的参数就可以模型请求获取用户图片了。

query_hash 参数

query_hash 在接口中的作用类似于让服务器知道客户端的版本,这个参数被 hard-coding 在前端的一个 js 文件中。在我的网络环境下,该 js 文件是 b55cb2cfaa46.js,值为 f2405b236d85e8296cf30347c9f08c2a.

该参数获取非常容易,唯一不太确定的是其更新的频率。从实际使用情况来看,这个值已经一个多月没有更新过了。因此,一周自动检测更新一次应该是没有问题的。

query 参数中的 variables 都是业务参数,看一眼就明白。但是,实际测试你会发现,所有的query参数都正确的情况下,服务器依然会返回 403. 二分排除了一下,发现是 header 中的 x-instagram-gis 是一个请求签名验证参数。只有该参数通过了验证,才能获得预期的返回。

x-instagram-gis 参数

签名的生成算法是:signature = md5({rhxGis}:{queryVariables}). 对应 js 实现:

rhxGis 参数可以通过用户页面的全局变量 window._sharedData 获取到:

queryVariables 对应上诉 variables 参数。

至此,关键参数获取方式都已搞定。可以开心的在 ig 上逛图啦。

扩展阅读

How to perform unauthenticated Instagram web scraping in response to recent private API changes?

Go 1.12 TLS 1.3 简单测试

《TLS 1.3 当前(2018.10)支持与部署之现状》中,我们提到 Go 将在 1.12 中支持 TLS 1.3. 作为一个 Gopher, 终于在前几天盼来了 golang 1.12 的发布。

但是从 release 日志看,本次对选择性的部分支持 TLS 1.3, 且默认处于关闭状态:

Go 1.12 adds opt-in support for TLS 1.3 in the crypto/tls package as specified by RFC 8446. It can be enabled by adding the value tls13=1 to the GODEBUG environment variable. It will be enabled by default in Go 1.13.

如果要开启 TLS 1.3, 需要设置环境变量:GODEBUG=tls13=1.

本次发布的 TLS 1.3 的 cipher suite 无法配置,也不支持 0-RTT 模式:

TLS 1.3 cipher suites are not configurable. All supported cipher suites are safe, and if PreferServerCipherSuites is set in Config the preference order is based on the available hardware.

Early data (also called “0-RTT mode”) is not currently supported as a client or server.

要知道,没有 0-RTT 的 TLS 1.3 是没有灵魂的,对本次版本的失望那是肯定的。但是依然在第一时间升级了 Go 版本,简单测试了一下国内网络环境下 TLS 1.3 与 1.2 的握手延迟。如果对 TLS 1.3 握手延迟还不太熟悉,可以参见拙文《TLS1.3/QUIC 是怎样做到 0-RTT 的》 以及 TLS 1.3 Handshake Protocol.

测试代码

需要说明的是,这个测试是不严谨,里面没有考虑 cipher suite 以及 early data 的差异。测试结果定性意义大于定量意义。

测试结果

测试使用的目标服务器地址是 blog.cloudflare.com:443, 我的网络环境下,ping 延迟为 226 ms (1-RTT).

从结果看,有如下结论:

  1. TLS 1.3 平均比 TLS 1.2 建立连接的延迟低约 1-RTT, 跟理论分析是吻合的。但是,
  2. 从 max 项可以看出,部分时候国内到目标服务器网络不稳定带来的波动比 TLS 本身协议优化的 RTT 大的多。因此,稳定高延迟的网络链路有时候比低延迟高抖动的网络更有实际意义。
  3. TLS 建立在 TCP 基础上,TCP 的握手延迟在 TLS 层面是优化不掉的,或者说不是 TLS 的管辖范围,因此,在允许的情况下,尽量复用连接。

你需要关注的 Java Enum 枚举的几个细节

枚举是一个非常古老的语言特性,用来实现具名的有限集合,在 C/C++ 中使用广泛。而 Java 在 Java SE5 才引入枚举。也许语言设计者觉得既然是后引入该特性,那么一定要在这个特性上支持比其他语言更多的特性。这些特性的确让 Java 的枚举功能看起来更加“成熟”,同时也引入了一些复杂性,需要开发者关注。

枚举是一个不能继承的常规类

定义一个一周七天的枚举类型:

编译成 class 文件后反编译查看:

从反编译结果可知:

  1. 枚举类型的关键字 enum 其实只是一个语法糖,编译器最终把它转化为一个final类,因此枚举是不可继承的。
  2. 枚举类型都继承自 java.lang.Enum 类。
  3. 枚举的每一个取值被编译器传化为了一个个 static final 属性。
  4. 本质上,这就是一个普通类,因此你可以在枚举是添加各种方法,甚至是main方法。

神奇的 values() 方法

从上面我们可以看出枚举类型被添加了一个静态的 values() 方法,但是 java.lang.Enum 并没有该方法。其实,这个方法是编译器添加的。通过这个方法可以获取到该枚举类型的所有取值。这个方法在需要遍历枚举取值,进行判断筛选的场景非常有用,可参考下例的 getByZhName 方法。

在枚举中保存其他信息

在 C 中,枚举可以简单的理解为具名的整型子集。Java 扩展了这个属性,使得可以在枚举中保存其他信息。

定义一个水果枚举类,并包含中文信息:

使用这种方式定义枚举的方式需要注意:该枚举必须含有一个构造函数,且该构造函数必须是私有的。因为枚举就是常规类,而枚举对象就是具体的枚举实例,因此枚举有多少个取值,该构造函数就会被调用多少次:

使用 EnumSet 和 EnumMap 提供性能

如果要在把枚举使用在 Set、Map 等集合场景,请使用 EnumSetEnumMap。 EnumSet 使用了 bit vector 来标记元素,EnumMap 内部将 Map 实现简化为了数组,因此可以获得更好的性能。

小结

Java 的枚举语言特性作为一个后来者,的确带来了更加“成熟”和“丰富”的实现。但是,这些丰富的特性是否一定要在日常的项目中使用,我个人是不推荐的。就我个人理解,枚举最大的优点是类型和有限集合的约束,从而增强代码的一致性。因此,我提倡在项目代码中用 C 的枚举风格来使用 Java 枚举。此外,枚举并不是编程语言必须支持的特性,比如近段时间如日中天的 Golang 是不支持枚举的。既然是一个可有可无的语言特性,那就 use is as simple as possible 吧。

扩展阅读

Java 语言中 Enum 类型的使用介绍
Java中的枚举与values()方法