你知道Github中国区谁最有影响力吗?用github actions每天自动爬数据给你看 !

14,536 阅读7分钟

策划

最近和我们组几位老师茶余饭后都在讨论这个github的中国区的关注者数量排名,第一名尤雨溪:7.4w关注者,第二名阮一峰:6.7w关注者,第三名廖雪峰:3.4w关注者。

另外:尤雨溪大佬是中国区第一,世界第二。世界第一是Linux的创始人Linus大神

市面上虽然已经有不少做这个排名统计的网站,但我总觉得缺点什么,比如:

  • 历史统计

如果时间够长,我们甚至可以恢复出一个中国区开源社区的里程图,只有我自己爬取数据才做得到,不然只能等别人做。

  • 查看自己的排名并生成海报

想要分享自己的排名只能截图,不如我自己生成一个精美的海报。

设计

通过花瓣网或者Dribbble搜索排行榜

image.png

找一个适合的风格,参考着布局设计。作为一个经验老道的前端,看到各种设计的时候,脑海中就已经浮现出css了吧?

爬虫方案选型

本着一贯白嫖的原则,这个爬虫我可不想用自己的服务器,得尽可能的让其在一个免费稳定的云服务中定期运行。

  • 方案1:uniCloud云服务
优点可定时任务,可直接连接云数据库,云存储
缺点超时时间只有60s,而github千条数据的爬虫,需要2分钟左右爬完。并且从国内服务器访问github api比较不稳定。
  • 方案2:github actions
优点可定时任务,用github的服务爬取github数据速度极快
缺点无法直接关联我在uniCloud里的云数据库

由于方案一的超时时间是固定的又比较短,坚持用它虽然也能设计出一个爬取方案,但是太麻烦了。正好借这个机会好好学习下GithubActions,爬取完成将数据传回uniCloud云函数,再通过云函数去录入数据库不就好了吗。

弄它

nodejs里调用github api比较简单,唯一需要注意两点。

  1. 用户搜索最多只能1000条数据
  2. 访问频率

基础授权模式下每分钟只允许个位数的请求,我们要爬取前1000名,这个是不够的,需要我们去Github设置中生成自己的TOKEN,使用TOKEN的请求频率一下子来到每分钟5000次,这下完全足够了。

将自己的token带入请头

async function githubApiGet(url,data){
	if(!data)data={};
	
	let res;
	try{
            res  = await axios.get(url,{
                    headers:{
                            "Authorization":'Token '+token,
                            "Accept":"application/vnd.github.v3+json"
                    },
                    params:data
            })
	}catch(err){
            console.log(err);
	}
	
	return res;
}

由于我要把中国区的排名历史都记录下来,未来这个仓库的文件数量可能无比多。因为每天会生成一个。所以得设计一下目录结构

image.png

把年份,月份拆分成独立目录,这样的话,单目录里文件最多也就31个。

Github Actions

配置项的用途请看代码块中的注释,这里只讲我用到的参数。

# 这个工作流的名称
name: ROCSchedule

on:
  # 定期任务,用的是国际标准时间,0 18这个设定代表中国的凌晨2点
  schedule:
    - cron: '0 18 * * *'
  # 是否允许在仓库面板手动触发工作流,允许。
  workflow_dispatch:

jobs:
  build:
    # 运行的Linux系统
    runs-on: ubuntu-latest

    steps:
      # 从仓库取出代码
      - uses: actions/checkout@v2
      # 设置node环境
      - name: Setup Node.js environment
        uses: actions/setup-node@v2.4.0
      # 执行npm install安装依赖
      - name: Install NPM dependencies
        run: 
          npm install axios
      # 把我们的代码运行起来
      - name: Run
        run:
          # 携带必要的参数运行,这里的MYTOKEN是解决github api请求频率问题的,POSTURL是爬取完成后发送数据的接口URL
          MYTOKEN=${{secrets.MYTOKEN}} POSTURL=${{secrets.POSTURL}} node index.js
          
      - name: Add & Commit
        # 由于我们会在仓库里生成一个json文件,所以还需要把仓库push一下
        uses: EndBug/add-and-commit@v7.3.0
        with:
          github_token: ${{secrets.MYTOKEN}}

Secrets

我的 MYTOKEN和POSTURL 不应该被泄露,但我又在项目中用到了,那怎么开源呢?GithubActions的Secrets变量太好用了,这样我既可以开源整个项目,但又不会泄露自己的隐私数据。每个fork的用户也都可以使用自己的TOKEN来搭建服务

uniCloud云函数接收POST

新建一个uniCloud云函数,开启URL化。

image.png

云函数URL化后接收POST过来的参数都在event.body中,用JSON解析一下就可以用了

if(event.body){
		//github action post
		
		const data = JSON.parse(event.body);
		
		await db.collection('githubroc').add({
			record_date:data.record_date,
			total_users:data.total_users,
			rank_list:data.rank_list
		});
		
		return;
	}

获取云数据库中的排行数据

我们通过当前日期 2021-09-11 这样的格式去查询 数据库 中的匹配项。如果没有匹配到指定日期的话,则取最新一条。这里由于必然存在至少一条,所以我就不做没有的判断了

const date = event.date;

dbRes = await db.collection('githubroc').where({
        record_date:dbCmd.eq(date)
}).get();

if(dbRes.affectedDocs<=0){
        dbRes = await db.collection('githubroc').limit(1).get();
}

return utils.responseData(0,"",dbRes.data[0]);

小程序长列表渲染

由于数据库中的存储结构不适合分页,好在总数只有1000条,我们放到前端来处理吧。不过1000条数据一次性setData的话,也还是会很慢。所以第一次只显示100条,滚动到底部再concat下100条的数据。

this.ranklist = this.ranklist.concat(this.orginlist.slice(this.rankpage*100,(this.rankpage+1)*100));

小程序海报生成

整个案子中最麻烦的部分就是海报了,完全使用canvas在前端绘制,并不难,是特别琐碎,要一点点把布局用canvas api画出来。

image.png

并且这里遇到了一个不太好办的地方,在微信小程序的canvas里绘制图片,必须先要将图片download到本地,而wx.downloadFile又需要在小程序管理后台配置请求白名单,配置时要求被配置的域名具备国内的备案...

而github头像是这么一个地址

https://avatars2.githubusercontent.com/u/499550?s=140

当我把它配置到白名单里,我获得了这样一个提示

image.png

你是不是在坑我!

我上哪儿去给githubusercontent.com整备案!

我一个人从策划,设计,开发搞了一天一夜,前面开发的时候由于不检测URL白名单,所以问题没有在最早被发现。最后海报实现不了?无法发布?我内心一万头草泥马在狂奔!

必须弄它!

云函数URL化来中转头像

这里我思考了两种做法

  1. 拿到github推来的数据时,将所有头像转存云存储,替换原url

  2. 我在自己的云函数中读取头像,并返回图片,无须本地存储,等于利用云函数来做一个中转。

我选择了方案2,就这么干!

方案2要利用云函数的get请求,中转方式如下

云函数url?url=github头像url

云函数中处理http get请求入参

if(event.queryStringParameters){
        //github avatar_url fix

        var qs = event.queryStringParameters;

        var imageRes = await uniCloud.httpclient.request(qs.avatar_url);

        let buff = new Buffer(imageRes.data);
        let base64data = buff.toString('base64');

        return {
                mpserverlessComposedResponse: true, // 使用阿里云返回集成响应是需要此字段为true
                isBase64Encoded: true,
                statusCode: 200,
                headers: {
                        'content-type': 'image/jpeg'
                },
                body: base64data
        }
}

后记

一个人从策划,设计,开发一条龙来完成了这样一个小功能,并且爬了不少坑,思考并解决了一些难点,非常有成就感。

poster.jpg

开源

github.com/ezshine/git…

仓库使用说明视频教程

小程序部分的技术实现并不难,所有技术难点都在文章中说明了,大家可以自行实现,这里我主要开源github action和排行榜的json文件,请朋友们点个follow吧~

我也想冲排名呢! 我也想冲排名呢! 我也想冲排名呢!


设计开发的时间还没有最后整理这篇文章花的时间多,写文章分享不易,还请给与鼓励!