现在我们知道如何搜索一个区域,其他的部分就简单多了。
如何知道搜索哪个区域
在数据查询的时候,把查询区域发送到一个消息队列里 (AWS SQS),然后 search worker 不断地从队列里读取需要搜索的区域。
如何得到每个区域里需要搜索的坐标点
Google S2 是一个基于 希尔伯特曲线 的区域分割方式。它可以将一个区域分割成相同大小的区域并且用一个 unique id 来表示。我们可以用 Google S2 将区域分割成 100m * 100m 的小正方形,然后取其中心作为用户位置,发送给服务器。
希尔伯特例子
图中的黑线是一个 hilbert curve,而图中的每一个黑线的转折点就是一个搜索点。
系统优化
在实现了最简单的数据采集层之后,就已经可以在网页端看到小精灵了。但是还有一些问题需要优化解决:
如果每个查询地区都进行搜索,搜索服务器的压力会比查询服务器大数十倍,而许多区域是没必要短时间内多次搜索的。
如果使用同一个账号进行搜索,非常容易被封号。
如果每次搜索都需要先登录服务器,会大大增加搜索延迟。
让我们一个个问题来解决:
如何减少搜索次数
这个问题的关键就在于减少相同地区的重复搜索。借用 Data Deduplication 的思想,我们可以用 时间 + 地点 来作为 Deduplication Key,如果区域 A 在 x 秒内搜索过了,就跳过这个区域。
在这里,我用 redis 来储存搜索记录,原因有两个:
Redis 的 setex 功能可以自动添加 ttl (Time to live),在一定时间之后,记录会自动消失。
Redis 作为一种 in memory cache,对于这类不需要高可靠性的数据,可以提供很好的查询速度。
具体实现:
如何避免同一个账户过于频繁地访问 Pokemon Go 服务器
答案很简单。使用多个账号( > 10000 个)。那么如何注册并使用多个账号呢?
由于 Pokemon Go 官网的 注册 不需要验证码,我用 selenium 写了一个批量注册机,自动填写注册信息。
注册邮箱需要验证之后才能使用,如何批量得到邮箱地址呢?
我申请了一个域名,设置通用邮件转发。这样所有在那个域名下的邮箱地址都会转发到我指定的一个邮箱了。我只需要注册一个邮箱,就可以映射到所有在我域名下的邮箱了。大部分的域名提供商都有邮件转发功能。
怎样批量激活账户呢?
设置邮箱的 Pop3,并且写一个小脚本,定期轮询新邮件,并且点击激活地址。在激活之后,储存用户名密码到数据库。
如何降低由于登录服务器引起的搜索延迟
在 tejado/pgoapi 中,为了模拟用户的行为,使用 API 之前,都会进行登录行为。整个流程分为 3 个部分:
从 oauth 服务器获取 account token。这里的 token 既可能是 Pokemon Trainer Club 的,也可能是 Google 的。
从 Load Balance 服务器 ( http:// pgorelease.nianticlabs.com /plfe ) 获取实际的 rpc 服务器地址以及 access token。
使用第二部的 access token 签名 rpc 请求,发送到 rpc 服务器。
在第一步跟第二步获得的 token 都是可以重复使用的,oauth token 有效时间大概是 3 小时,access token 有效时间大概是 30 分钟。我在每次成功登陆之后把 access token 和 rpc 服务器地址存到数据库中,这样就可以在不同的 Scan worker 之间使用了。
在优化了上面三点之后,每条数据爬取的速度就快了一倍。
Pokemon Map 的灾难
这个地图做起来其实并不难,所以有很多同质性的地图网站,最有名的大概就是 pokevision 。在 2016/07/30,Niantic (Pokemon Go的制作公司)将大部分云服务提供商的 ip 地址都封禁了,包括 AWS, Azure, DigitalOcean 等等。由于大部分的 Pokemon Map 都托管在云服务上,这就导致了几乎所有的 Pokemon Map 都无法使用了。比如这篇报道 。
同样,我的 Pokemon Map 也无法获取数据了,所有的 rpc 请求都返回了 nginx 403 错误请求。 为了解决这个问题,我们就需要更改发送请求的ip地址,最简单的方式就是使用一个 proxy 转发所有的通信流量。具体实现也很简单,在 Scan Worker 服务器上跑一个 ssh 链接即可: