Skip to content

Commit 89f47de

Browse files
committed
init
1 parent cbd192c commit 89f47de

File tree

4 files changed

+273
-1
lines changed

4 files changed

+273
-1
lines changed

Diff for: .gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,6 @@ venv.bak/
102102

103103
# mypy
104104
.mypy_cache/
105+
106+
# idea
107+
.idea/

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# Script
2-
GitHub 的脚本集合
2+
基于 python3.7 的 GitHub 的脚本集合

Diff for: generate_starchart.py

+267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
#!/usr/bin/env python
2+
# -*- coding:utf-8 -*-
3+
#
4+
# Author : XueWeiHan
5+
6+
# Date : 2019-09-18 22:45
7+
# Desc : 生成星图脚本
8+
import time
9+
import datetime
10+
from collections import OrderedDict
11+
12+
import pygal
13+
from pygal.style import Style
14+
import requests
15+
16+
17+
class GitHubRepositories(object):
18+
def __init__(self, name, token, per_page=100, max_star_limit=10000,
19+
min_star_limit=10, create_days_limit=10, mode='count',
20+
time_node_count=1):
21+
self.name = name
22+
self.token = token
23+
self.limit_remaining = 0
24+
self.reset_limit_time = None
25+
self.per_page = per_page
26+
self.info = None
27+
self.stargazers = set()
28+
self.max_star_limit = max_star_limit
29+
self.min_star_limit = min_star_limit
30+
self.create_days_limit = create_days_limit
31+
self.star_map = OrderedDict()
32+
self.days_count = None
33+
self.mode = mode # 展示star总和('count'), 还是star的增量('change')
34+
self.time_node_count = time_node_count
35+
self.x_node_list = None # x轴为时间
36+
self.y_node_list = None # y轴为数量
37+
# 默认为当前时间
38+
self.max_date_str = datetime.datetime.now().strftime('%Y-%m-%d')
39+
# 项目创建时间
40+
self.min_date_str = None
41+
# 停止请求数据
42+
self.end_request = False
43+
self.interval_days = None # 显示x轴的时间两个点应相隔几天
44+
45+
@property
46+
def headers(self):
47+
# token 授权
48+
return {
49+
'Authorization': 'token {}'.format(self.token)
50+
}
51+
52+
@staticmethod
53+
def fix_time(time_str, rtype='time'):
54+
# github 返回的时间是 ISO 8601 规则的时间(0时区),下面转化成北京时间(+8时区)
55+
epoch_time = datetime.datetime.strptime(time_str,
56+
'%Y-%m-%dT%H:%M:%SZ')
57+
epoch_time = epoch_time + datetime.timedelta(hours=8)
58+
if rtype == 'time':
59+
epoch_time_str = epoch_time.date().strftime('%Y-%m-%d %H:%M:%S')
60+
else:
61+
epoch_time_str = epoch_time.date().strftime('%Y-%m-%d')
62+
return epoch_time_str
63+
64+
def get_repo_info(self):
65+
url = 'https://api.github.com/repos/{}'.format(self.name)
66+
response = requests.get(url, headers=self.headers)
67+
if response.status_code == 200:
68+
self.limit_remaining = int(response.headers['X-RateLimit-Remaining'])
69+
self.reset_limit_time = datetime.datetime.fromtimestamp(
70+
int(response.headers['X-RateLimit-Reset'])).strftime(
71+
"%Y-%m-%d %H:%M:%S")
72+
self.info = response.json()
73+
# 项目名字标准化
74+
self.name = self.info['full_name']
75+
# 把info中的时间转化成北京时间
76+
self.min_date_str = self.fix_time(self.info['created_at'],
77+
rtype='date')
78+
self.info['created_at'] = self.fix_time(self.info['created_at'])
79+
self.info['updated_at'] = self.fix_time(self.info['updated_at'])
80+
self.info['pushed_at'] = self.fix_time(self.info['pushed_at'])
81+
82+
def generate_all_stargazers_urls(self):
83+
"""
84+
请求所有的 stargazers api url
85+
"""
86+
all_stargazers_urls = []
87+
stargazers_count = self.info['stargazers_count']
88+
stargazers_url_format = 'https://api.github.com/repos/{}/stargazers?' \
89+
'page={}&per_page={}'
90+
page_count = int(stargazers_count / self.per_page) + 1
91+
for i in range(1, page_count + 1):
92+
all_stargazers_urls.append(stargazers_url_format.format(
93+
self.name, i, self.per_page))
94+
return all_stargazers_urls
95+
96+
def parse_stargazers_data(self, stargazers_data):
97+
for fi_data in stargazers_data:
98+
# 根据 star 时间数据聚合到天
99+
epoch_date_str = self.fix_time(fi_data['starred_at'], 'date')
100+
epoch_date = datetime.datetime.strptime(epoch_date_str, '%Y-%m-%d')
101+
max_date = datetime.datetime.strptime(self.max_date_str, '%Y-%m-%d')
102+
if epoch_date > max_date:
103+
self.end_request = True
104+
break
105+
self.stargazers.add(fi_data['user']['id'])
106+
if epoch_date_str in self.star_map:
107+
self.star_map[epoch_date_str] += 1
108+
else:
109+
self.star_map[epoch_date_str] = 1
110+
111+
def get_stargazer(self, req_s, url, headers):
112+
rs = req_s.get(url, headers=headers)
113+
self.parse_stargazers_data(rs.json())
114+
115+
def get_all_stargazers(self):
116+
"""
117+
请求所有的 stargazers api url
118+
"""
119+
all_stargazers_urls = self.generate_all_stargazers_urls()
120+
if self.limit_remaining < len(all_stargazers_urls):
121+
msg = '当前时次请求数量超出'
122+
print(msg)
123+
raise Exception(msg)
124+
else:
125+
self.limit_remaining -= len(all_stargazers_urls)
126+
headers = self.headers
127+
headers['Accept'] = 'application/vnd.github.v3.star+json'
128+
s = requests.session()
129+
for stargazers_url in all_stargazers_urls:
130+
if not self.end_request:
131+
print(stargazers_url, self.end_request)
132+
self.get_stargazer(s, stargazers_url, headers)
133+
# lock = threading.Lock()
134+
# threads = [threading.Thread(target=self.get_stargazer,
135+
# args=(s, stargazers_url, headers, lock))
136+
# for stargazers_url in all_stargazers_urls]
137+
# [thread.start() for thread in threads]
138+
# [thread.join() for thread in threads]
139+
# del s
140+
print('request limit remaining: {}'.format(self.limit_remaining))
141+
142+
def make_time_node(self):
143+
max_date = datetime.datetime.strptime(self.max_date_str, '%Y-%m-%d')
144+
min_date = datetime.datetime.strptime(self.min_date_str, '%Y-%m-%d')
145+
date_list = [self.min_date_str]
146+
self.days_count = (max_date - min_date).days # 该项目共创建了多少天
147+
self.interval_days = self.days_count / self.time_node_count
148+
flag_date = max_date - datetime.timedelta(days=self.interval_days)
149+
tmp_date = min_date
150+
# 生成所有的 x_node(时间节点)
151+
while tmp_date < flag_date:
152+
date_node = tmp_date + datetime.timedelta(days=self.interval_days)
153+
tmp_date = date_node
154+
date_list.append(date_node.strftime('%Y-%m-%d'))
155+
date_list.append(self.max_date_str)
156+
self.x_node_list = date_list
157+
158+
def make_value_node(self):
159+
min_date = datetime.datetime.strptime(self.min_date_str, '%Y-%m-%d')
160+
# 初始化结果dict,防止某段时间的数为空造成异常
161+
result_data = {date_node: 0 for date_node in self.x_node_list}
162+
for k, v in self.star_map.items():
163+
starred_datetime = datetime.datetime.strptime(k, '%Y-%m-%d')
164+
# 把原始数据的日期对应到node轴,并把value累加(原始数据聚合到x轴的量级)
165+
date_index = int((starred_datetime - min_date).days / self.interval_days)
166+
if (starred_datetime - min_date).days % self.interval_days:
167+
date_index += 1
168+
result_k = self.x_node_list[date_index]
169+
result_data[result_k] += v
170+
171+
if self.mode == 'count':
172+
# 某x轴上日期时的star总数
173+
for index, x_node in enumerate(self.x_node_list):
174+
if index != 0:
175+
result_data[x_node] += result_data[
176+
self.x_node_list[index - 1]]
177+
178+
self.y_node_list = [result_data[fi_x_node] for fi_x_node in
179+
self.x_node_list]
180+
181+
def init(self):
182+
start_time = time.time()
183+
self.get_repo_info()
184+
error_msg = '项目名称:{},生成失败原因:'.format(self.name)
185+
if self.info:
186+
created_datetime = datetime.datetime.strptime(
187+
self.info['created_at'], '%Y-%m-%d %H:%M:%S')
188+
created_days = (datetime.datetime.now() - created_datetime).days
189+
if created_days < self.create_days_limit:
190+
error_msg += '不支持创建天数小于 {} 天的项目'.format(self.create_days_limit)
191+
raise Exception(error_msg)
192+
elif self.info['stargazers_count'] > self.max_star_limit:
193+
error_msg += '暂不支持 star 数量大于 {} 的项目'.format(self.max_star_limit)
194+
raise Exception(error_msg)
195+
elif self.info['stargazers_count'] < self.min_star_limit:
196+
error_msg += '暂不支持 star 数量小于 {} 的项目'.format(self.min_star_limit)
197+
raise Exception(error_msg)
198+
elif not self.limit_remaining:
199+
error_msg += '(测试阶段)请求数达到限制次数'
200+
raise Exception(error_msg)
201+
else:
202+
try:
203+
self.get_all_stargazers()
204+
self.make_time_node()
205+
self.make_value_node()
206+
except Exception as e:
207+
raise e
208+
else:
209+
error_msg += '未找到项目'
210+
raise Exception(error_msg)
211+
print('Get {} data speed time: {}'.format(
212+
self.name, time.time() - start_time))
213+
214+
215+
class GenerateStarSVG(object):
216+
@staticmethod
217+
def make_svg(svg_type, x_nodes, y_nodes):
218+
# 参数分别修改:x轴描述旋转角度、圆角、颜色(Python 蓝)
219+
line_chart = pygal.Line(x_label_rotation=20, tooltip_border_radius=10,
220+
background='transparent',
221+
plot_background='transparent',
222+
style=Style(colors=('#376fa0',)))
223+
line_chart.x_labels = x_nodes
224+
line_chart.add("Star", y_nodes)
225+
if svg_type == 'uri':
226+
return line_chart.render_data_uri()
227+
elif svg_type == 'svg':
228+
line_chart.render_to_file('local.svg')
229+
230+
231+
def main(config, repo_name='521xueweihan/HelloGitHub'):
232+
_g = GitHubRepositories(repo_name, config['token'],
233+
per_page=config['per_page'],
234+
max_star_limit=config['max_star_limit'],
235+
min_star_limit=config['min_star_limit'],
236+
create_days_limit=config['create_days_limit'])
237+
_g.init()
238+
starchart = {
239+
'repo_name': _g.info['full_name'],
240+
'repo_url': _g.info['html_url'],
241+
'repo_created_at': _g.info['created_at'],
242+
'days_count': _g.days_count,
243+
'star_count': len(_g.stargazers),
244+
'y_nodes': _g.y_node_list,
245+
'x_nodes': _g.x_node_list,
246+
'create_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
247+
}
248+
return starchart
249+
250+
251+
if __name__ == '__main__':
252+
config = {
253+
'token': 'xxxx',
254+
'per_page': 100,
255+
'max_star_limit': 200000,
256+
'min_star_limit': 10,
257+
'create_days_limit': 10,
258+
}
259+
g = GitHubRepositories('username/project_name', config['token'],
260+
per_page=config['per_page'],
261+
max_star_limit=config['max_star_limit'],
262+
min_star_limit=config['min_star_limit'],
263+
create_days_limit=config['create_days_limit'])
264+
g.init()
265+
svg = GenerateStarSVG()
266+
svg.make_svg('svg', g.x_node_list, g.y_node_list)
267+

Diff for: requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pygal
2+
requests

0 commit comments

Comments
 (0)