咱們即將與 2019 揮手道別,踏入嶄新的 2020。一到年底,各個平臺都在整理數據,出具一份屬於本身平臺的「年度報告」。而對於技術人而言,若是你是一位開源愛好者,GitHub 的年度報告就是你 2019 年的技術總結。html
阮一峯老師曾在科技愛好者週刊中提到「數據的力量」:node
GitHub 我的頁有一個日曆欄目,只要當天有代碼提交,那一天的小方格就會變成綠色。若是這一年,你天天編碼,日曆就全是綠的,不然就會有白色的小方塊。全部人均可以看到這個「編碼日曆」。不少人爲了讓綠色小方格子不要中斷,就會盡可能天天提交代碼。時間一長,真的多作了很多項目。python
所以,此次年度報告我想主要針對這份「編碼日曆」,把你的「編碼日曆」組裝到一張圖片上展現給別人。git
由於前一段時間正好在學習 GraphQL,因此將經過 GitHub 的接口 GitHub GraphQL API v4 來獲取相關的用戶數據。github
這份年度報告涉及到的主要技術:json
在開始 Coding 以前須要先梳理一下需求。生成報告的整個流程大體以下:後端
所以,須要作的事包括:api
由於要經過 GitHub GraphQL API v4 獲取數據,因此先來聊聊 GraphQL。bash
官方對於 GraphQL 的定義是:服務器
一種用於 API 的查詢語言,是一個使用基於類型系統來執行查詢的服務端運行時(類型系統由你的數據定義)。
這樣說很抽象,你們可能對 RESTful 比較熟悉些,那麼咱們就拿 GitHub REST API v3 與 GitHub GraphQL API v4 獲取數據的方式作一個簡單的對比,GraphQL 的特色天然就一目瞭然。
以獲取用戶數據爲例,相關接口文檔:
對於 RESTful 風格而言,天然是要發起一個 GET
請求。因爲咱們要獲取某個指定用戶的數據,因此須要在 PATH 中指定 :username
:
GET /users/:username
複製代碼
請求成功後 GitHub 將會返回如下數據:
{
"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 接口,咱們能夠發起這樣一條請求:
{
user(login: "username") {
avatarUrl
}
}
複製代碼
這樣一來,服務端將根據咱們請求數據的格式,返回給咱們對應的字段,即僅返回 user
下的 avatarUrl
數據:
{
"data":{
"user":{
"avatarUrl":"url"
}
}
}
複製代碼
在 RESTful 中,咱們被迫接收服務端已組裝好的數據,但 GraphQL 給了咱們更多的自由,讓咱們能夠只取所需。
除此以外,RESTful 以資源劃分接口,數據之間相對離散,若是想請求不一樣的資源則須要發起屢次請求。而 GraphQL 的數據更具總體性,資源之間以圖(即 Graph 名稱的由來)的形式彼此關聯,一次請求便可獲取多種資源。
我想要獲取的數據主要有:
根據接口文檔 User 與 ContributionsCollection 可知,這些數據都在 user
中,對應的字段以下:
name
followers.totalCount
contributionsCollection.contributionCalendar
totalContributions
weeks
contributionDays
color
contributionCount
date
所以,能夠構造出以下 query
:
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
發起請求:
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 數據:
{
"data":{
"user":{
"name":"江不知",
"followers":{
"totalCount":71
},
"contributionsCollection":{
"contributionCalendar":{
"totalContributions":2234,
"weeks":[
{
"contributionDays":[
{
"color":"#c6e48b",
"contributionCount":30,
"date":"2019-01-01"
}
]
}
]
}
}
}
}
}
複製代碼
我主要針對 weeks
作了一些簡單的數據統計。主要包括:
contributionCount > 0
)這些數據對 weeks
進行一次遍歷便可得出,在此很少作贅述。
做爲一個後端開發,真的沒有多少設計天賦,說多了都是淚……
整份報告大體分紅三個區域:
反反覆覆改了多版,詢問了不少朋友的意見,最後的結果依舊不是很好看……
報告設計完成之後就能夠把最終要展現的數據拼接到報告上了。
在遍歷 weeks
統計數據的過程當中,能夠順便完成「編碼日曆」的繪製。
「編碼日曆」中的每一天就是一個小方塊,方塊的顏色咱們已經從接口返回數據的 color
字段中獲取到了。我選擇使用 line()
繪製一條顏色爲 color
的直線表明方塊,把直線的 width
加粗,以得到方塊的效果。
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()
在指定位置貼上文字。
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。
設定:當用戶發送信息爲「2019 $github_id」時觸發生成年度報告。
import werobot
robot = werobot.WeRoBot(token='token')
# 回覆包含指定文本的信息
@robot.filter(re.compile("2019(\s)+(.*)?"))
def annual_report(message, session, match):
if match:
# do something...
複製代碼
生成年度報告後,咱們使用微信的新增臨時素材接口上傳報告圖片,並獲取到臨時素材的編號 media_id
:
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']
複製代碼
而後,咱們再將這一圖片信息返回給用戶:
from werobot.replies import ImageReply
# 要返回的圖片數據
reply = ImageReply(message=message, media_id=media_id)
return reply
複製代碼
當用戶在公衆號發送 2019+空格+github_id
時,將返回 github_id
所對應的報告。最終生成的報告以下:
源碼見 GitHub 倉庫:github.com/JalanJiang/…
接入的服務器爲辣雞配置,還請各位大佬手下留情。
整個過程涉及到微信公衆號和 GitHub 接口的調用,用戶從輸入到數據返回須要等待幾秒的時間。爲了不超時的尷尬狀況,這裏只對用戶提交記錄作了簡單的分析。
在完成這個項目的過程當中幾度由於設計出的報告太醜而想要放棄,感謝幾位朋友一直鼓勵我、給我提出修改意見才讓我堅持了下來。
2019 年再見啦,但願 2020 年能嘗試更多有趣的事情。:)