前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你有一份 2019 年 GitHub 年度报告待领取

你有一份 2019 年 GitHub 年度报告待领取

作者头像
江不知
发布2020-01-22 09:41:25
6000
发布2020-01-22 09:41:25
举报
文章被收录于专栏:编程拯救世界编程拯救世界
前言

我们即将与 2019 挥手作别,踏入崭新的 2020。一到年末,各个平台都在整理数据,出具一份属于自己平台的「年度报告」。而对于技术人而言,如果你是一位开源爱好者,GitHub 的年度报告就是你 2019 年的技术总结。

阮一峰老师曾在科技爱好者周刊[1]中提到「数据的力量」:

GitHub 个人页有一个日历栏目,只要当天有代码提交,那一天的小方格就会变成绿色。如果这一年,你每天编码,日历就全是绿的,否则就会有白色的小方块。所有人都可以看到这个「编码日历」。很多人为了让绿色小方格子不要中断,就会尽量每天提交代码。时间一长,真的多做了不少项目。

因此,这次年度报告我想主要针对这份「编码日历」,把你的「编码日历」组装到一张图片上展示给别人。

因为前一段时间正好在学习 GraphQL[2],所以这次将通过 GitHub 的接口 GitHub GraphQL API v4[3] 来获取相关的用户数据。

这份年度报告涉及到的主要技术有:

  • GraphQL
  • Python
    • requests[4](发起请求)
    • PIL[5]: Image/ImageDraw/ImageFont(图片处理)
    • werobot(接入微信公众号)

需求确立

在开始 Coding 之前需要先梳理一下需求。生成报告的整个流程大致如下:

项目流程图

因此,需要做的事包括:

  1. 调通 GitHub GraphQL API v4,获取到需要的数据
  2. 对数据进行统计整理
  3. 设计一份年度报告
  4. 结合整理后的数据生成报告,并将最终报告返回给用户
  5. 接入微信公众平台,走通整个流程

数据获取

何为 GraphQL?

因为要通过 GitHub GraphQL API v4 获取数据,所以先来聊聊 GraphQL。

官方对于 GraphQL 的定义是:

一种用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。

这样说很抽象,大家可能对 RESTful[6] 比较熟悉些,那么我们就拿 GitHub REST API v3[7] 与 GitHub GraphQL API v4 获取数据的方式做一个简单的对比,GraphQL 的特点自然就一目了然。

以获取用户数据为例,相关接口文档:

  • REST API v3: Users:https://developer.github.com/v3/users/
  • GraphQL API v4: User:https://developer.github.com/v4/object/user/

对于 RESTful 风格而言,自然是要发起一个 GET 请求。由于我们要获取某个指定用户的数据,所以需要在 PATH 中指定 :username

代码语言:javascript
复制
GET /users/:username

请求成功后 GitHub 将会返回以下数据:

代码语言:javascript
复制
{
  "login": "octocat",
  "id": 1,
  "node_id": "MDQ6VXNlcjE=",
  "avatar_url": "https://github.com/images/error/octocat_happy.gif",
  "gravatar_id": "",
  "url": "https://api.github.com/users/octocat",
  "html_url": "https://github.com/octocat",
  "followers_url": "https://api.github.com/users/octocat/followers",
  "following_url": "https://api.github.com/users/octocat/following{/other_user}",
  "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
  "organizations_url": "https://api.github.com/users/octocat/orgs",
  "repos_url": "https://api.github.com/users/octocat/repos",
  "events_url": "https://api.github.com/users/octocat/events{/privacy}",
  "received_events_url": "https://api.github.com/users/octocat/received_events",
  "type": "User",
  "site_admin": false,
  "name": "monalisa octocat",
  "company": "GitHub",
  "blog": "https://github.com/blog",
  "location": "San Francisco",
  "email": "octocat@github.com",
  "hireable": false,
  "bio": "There once was...",
  "public_repos": 2,
  "public_gists": 1,
  "followers": 20,
  "following": 0,
  "created_at": "2008-01-14T04:33:35Z",
  "updated_at": "2008-01-14T04:33:35Z"
}

但有时我们不需要这么多的数据,我们可能只想获取用户的头像地址。在 RESTful 风格的接口下,我们无法只获取某一条数据,但对于 GraphQL 接口,我们可以发起这样一条请求:

代码语言:javascript
复制
{
    user(login: "username") {
        avatarUrl
    }
}

这样一来,服务端将根据我们请求数据的格式,返回给我们对应的字段,即仅返回 user 下的 avatarUrl 数据:

代码语言:javascript
复制
{
    "data":{
        "user":{
            "avatarUrl":"url"
        }
    }
}

在 RESTful 中,我们被迫接受服务端已组装好的数据,但 GraphQL 给了我们更多的自由,让我们可以只取所需。

除此之外,RESTful 以资源划分接口,数据之间相对离散,如果想请求不同的资源则需要发起多次请求。而 GraphQL 的数据更具整体性,资源之间以(即 Graph 名称的由来)的形式彼此关联,一次请求即可获取多种资源。

构造 GraphQL 请求

我想要获取的数据主要有:

  1. 用户名
  2. 用户在 2019 年每日的贡献情况
  3. 用户 Followers 数量

根据接口文档 User[8]ContributionsCollection[9] 可知,这些数据都在 user 中,对应的字段如下:

  • 用户昵称:name
  • Followers 数量:followers.totalCount
  • 编码日历:contributionsCollection.contributionCalendar
    • 每日贡献情况:contributionDays
    • 当天日历颜色:color
    • 当天贡献数:contributionCount
    • 当天日期:date
    • 总贡献数量:totalContributions
    • 每周贡献情况:weeks

因此,可以构造出如下 query

代码语言:javascript
复制
query = """
{
    user(login: "%s") {
        followers {
            totalCount
        }
        name
        contributionsCollection(
            from: "%s",
            to: "%s"
        ) {
            contributionCalendar {
                totalContributions
                weeks {
                    contributionDays {
                        color
                        contributionCount
                        date
                    }
                }
            }
        }
    }
}
"""% (github_id, begin, end)

构造好 query 后,我们使用 requests 发起请求:

代码语言:javascript
复制
import requests


access_token = "xxx"

# 请求 headers 带上 access_token
headers = {"Authorization": "bearer %s" % access_token}

# 发起请求
response = requests.post(
    "https://api.github.com/graphql",
    headers=headers,
    json={'query': query}
)

若请求成功,GitHub 会返回如下格式的 JSON 数据:

代码语言:javascript
复制
{
    "data":{
        "user":{
            "name":"江不知",
            "followers":{
                "totalCount":71
            },
            "contributionsCollection":{
                "contributionCalendar":{
                    "totalContributions":2234,
                    "weeks":[
                        {
                            "contributionDays":[
                                {
                                    "color":"#c6e48b",
                                    "contributionCount":30,
                                    "date":"2019-01-01"
                                }
                            ]
                        }
                    ]
                }
            }
        }
    }
}

数据统计

我主要针对 weeks 做了一些简单的数据统计。主要包括:

  • 有提交代码的天数(contributionCount > 0
  • 连续提交代码的最大天数
  • 完成贡献次数最多的日期

这些数据对 weeks 进行一次遍历即可得出,在此不多做赘述。

设计报告

作为一个后端开发,真的没有多少设计天赋,说多了都是泪……

整份报告大致分成三个区域:

  1. 头部 Title
  2. Title 下的「编码日历」
  3. 中间部分显示一些分析数据
  4. 底部宣示主权

反反复复改了多版,询问了很多朋友的意见,最后的结果依旧不是很好看……

年度报告设计最终版

数据拼接

报告设计完成以后就可以把最终要展示的数据拼接到报告上了。

绘制「编码日历」

在遍历 weeks 统计数据的过程中,可以顺便完成「编码日历」的绘制。

「编码日历」中的每一天就是一个小方块,方块的颜色我们已经从接口返回数据的 color 字段中获取到了。我选择使用 line() 绘制一条颜色为 color 的直线代表方块,把直线的 width 加粗,以获得方块的效果。

代码语言:javascript
复制
from PIL import Image, ImageDraw

# 打开图片
f = open(self.IMAGE_FILE_PATH, 'rb')
image = Image.open(f)
# 创建一个 draw 实例
drawImage = ImageDraw.Draw(image)

# 遍历每周数据
for week in weeks:
    # 遍历每日数据
    for day in week['contributionDays']:
        # 取出当天的颜色
        color = day['color']
        # 绘制直线
        drawImage.line([(x_point, y_point), (x_point + square_width, y_point)], fill=color, width=square_width)
        # 改变下一个方格的 y 坐标
        y_point += move_width
    # 改变下一个方格的 x 坐标
    x_point += move_width
    # 下一周开始,y 坐标恢复原处
    y_point = y_begin

粘贴文字

报告的其他部分就主要是文字内容了,设置好字体、颜色等,使用 text() 在指定位置贴上文字。

代码语言:javascript
复制
from PIL import ImageFont

font_size = 60
# 设置字体与字号
font = ImageFont.truetype("./font/fzlt.ttf", font_size)
font_color = "#F7FFF7"

# 设置坐标
x, y = 0

# 在图片写上文字
draImage.text((x, y), "要显示的文字", fill=font_color, font=font)

接入公众号

公众号方面直接使用了开发框架 WeRoBot[10]

设定:当用户发送信息为「2019 $github_id」时触发生成年度报告。

代码语言:javascript
复制
import werobot

robot = werobot.WeRoBot(token='token')

# 回复包含指定文本的信息
@robot.filter(re.compile("2019(\s)+(.*)?"))
def annual_report(message, session, match):
    if match:
        # do something...

生成年度报告后,我们使用微信的新增临时素材[11]接口上传报告图片,并获取到临时素材的编号 media_id

代码语言:javascript
复制
from werobot.client import Client

config = {
    "APP_ID": "app_id",
    "APP_SECRET": "app_secret"
}

client = Client(config)
# 上传临时素材
response = client.upload_media('image', image) # image 为生成的报告图片
# 获取临时素材 ID
media_id = response['media_id']

然后,我们再将这一图片信息返回给用户:

代码语言:javascript
复制
from werobot.replies import ImageReply

# 要返回的图片数据
reply = ImageReply(message=message, media_id=media_id)
return reply

结果展示

当用户在公众号发送 2019+空格+github_id 时,将返回 github_id 所对应的报告。最终生成的报告如下:

我的 2019 GitHub 年度报告

源码见 GitHub 仓库:https://github.com/JalanJiang/2019-github-annual-report

接入的服务器为辣鸡配置,还请各位大佬手下留情。

总结

整个过程涉及到微信公众号和 GitHub 接口的调用,用户从输入到数据返回需要等待几秒的时间。为了避免超时的尴尬情况,这里只对用户提交记录做了简单的分析。

在完成这个项目的过程中几度因为设计出的报告太丑而想要放弃,感谢几位朋友一直鼓励我、给我提出修改意见才让我坚持了下来。

2019 年再见啦,希望 2020 年能尝试更多有趣的事。:)

参考资料

[1]

科技爱好者周刊: https://www.ruanyifeng.com/blog/2019/09/weekly-issue-73.html

[2]

GraphQL: https://graphql.cn/

[3]

GitHub GraphQL API v4: https://developer.github.com/v4/

[4]

requests: https://2.python-requests.org//zh_CN/latest/user/quickstart.html

[5]

PIL: https://github.com/python-pillow/Pillow

[6]

RESTful: https://www.ruanyifeng.com/blog/2011/09/restful.html

[7]

GitHub REST API v3: https://developer.github.com/v3/

[8]

User: https://developer.github.com/v4/object/user/

[9]

ContributionsCollection: https://developer.github.com/v4/object/contributionscollection/

[10]

WeRoBot: https://github.com/offu/WeRoBot

[11]

新增临时素材: https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程拯救世界 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求确立
  • 数据获取
    • 何为 GraphQL?
      • 构造 GraphQL 请求
      • 数据统计
      • 设计报告
      • 数据拼接
        • 绘制「编码日历」
          • 粘贴文字
          • 接入公众号
          • 结果展示
          • 总结
            • 参考资料
            相关产品与服务
            图片处理
            图片处理(Image Processing,IP)是由腾讯云数据万象提供的丰富的图片处理服务,广泛应用于腾讯内部各产品。支持对腾讯云对象存储 COS 或第三方源的图片进行处理,提供基础处理能力(图片裁剪、转格式、缩放、打水印等)、图片瘦身能力(Guetzli 压缩、AVIF 转码压缩)、盲水印版权保护能力,同时支持先进的图像 AI 功能(图像增强、图像标签、图像评分、图像修复、商品抠图等),满足多种业务场景下的图片处理需求。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档