1
- import sys
1
+ from string import Template
2
2
3
3
import requests
4
4
import urllib3
5
5
6
6
urllib3 .disable_warnings ()
7
7
8
- url = 'https://leetcode.cn/graphql'
9
- global_url = 'https://leetcode.com/graphql'
10
8
11
-
12
- class LocalRanking :
13
- """国内竞赛排名"""
14
-
15
- def __init__ (self ):
16
- self .url = 'https://leetcode.cn/graphql'
17
- self .rank_url = 'https://leetcode.cn/graphql/noj-go/'
9
+ class Ranking :
10
+ def __init__ (self , region = 'CN' , retry = 3 ):
11
+ self .retry = retry
12
+ self .region = region
13
+ if region == 'CN' :
14
+ self .url = 'https://leetcode.cn/graphql'
15
+ self .page_query = Template (
16
+ "{\n localRankingV2(page:$page) {\n myRank {\n attendedContestCount\n "
17
+ "currentRatingRanking\n dataRegion\n isDeleted\n user {\n realName\n "
18
+ "userAvatar\n userSlug\n __typename\n }\n __typename\n }\n page\n totalUsers\n "
19
+ "userPerPage\n rankingNodes {\n attendedContestCount\n currentRatingRanking\n "
20
+ "dataRegion\n isDeleted\n user {\n realName\n userAvatar\n userSlug\n __typename\n }\n __"
21
+ "typename\n }\n __typename\n }\n }\n "
22
+ )
23
+ else :
24
+ self .url = 'https://leetcode.com/graphql'
25
+ self .page_query = Template (
26
+ "{\n globalRanking(page:$page){\n totalUsers\n userPerPage\n myRank{\n ranking\n "
27
+ "currentGlobalRanking\n currentRating\n dataRegion\n user{\n nameColor\n activeBadge{\n "
28
+ "displayName\n icon\n __typename\n }\n __typename\n }\n __typename\n }\n rankingNodes{\n "
29
+ "ranking\n currentRating\n currentGlobalRanking\n dataRegion\n user{\n username\n nameColor\n "
30
+ "activeBadge{\n displayName\n icon\n __typename\n }\n profile{\n userAvatar\n countryCode\n "
31
+ "countryName\n realName\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n }\n "
32
+ )
18
33
19
34
def load_page (self , page ):
20
- """分页加载排名列表"""
21
- query = (
22
- "{\n localRankingV2(page:"
23
- + str (page )
24
- + ") {\n myRank {\n attendedContestCount\n currentRatingRanking\n dataRegion\n isDeleted\n "
25
- "user {\n realName\n userAvatar\n userSlug\n __typename\n }"
26
- "\n __typename\n }\n page\n totalUsers\n userPerPage\n "
27
- "rankingNodes {\n attendedContestCount\n currentRatingRanking\n dataRegion\n isDeleted\n "
28
- "user {\n realName\n userAvatar\n userSlug\n __typename\n }\n __typename\n }\n __typename\n }\n }\n "
29
- )
30
- retry = 0
31
- while retry < 3 :
35
+ query = self .page_query .substitute (page = page )
36
+ for _ in range (self .retry ):
32
37
resp = requests .post (url = self .url , json = {'query' : query }, verify = False )
33
- if resp .status_code == 200 :
34
- nodes = resp .json ()['data' ]['localRankingV2' ]['rankingNodes' ]
38
+ if resp .status_code != 200 :
39
+ continue
40
+ nodes = resp .json ()['data' ][
41
+ 'localRankingV2' if self .region == 'CN' else 'globalRanking'
42
+ ]['rankingNodes' ]
43
+ if self .region == 'CN' :
35
44
return [
36
- (int (nd ['currentRatingRanking' ]), nd ['user' ]['userSlug' ])
37
- for nd in nodes
45
+ (int (v ['currentRatingRanking' ]), v ['user' ]['userSlug' ])
46
+ for v in nodes
38
47
]
39
48
else :
40
- retry += 1
41
- return None
42
-
43
- def get_user_rank (self , uid ):
44
- """根据用户名获取其个人主页显示的真实分数,因为四舍五入会导致一部分 1599.xxx 的用户也显示为 1600 分"""
45
- query = (
46
- "\n query userContestRankingInfo($userSlug:String!){\n userContestRanking(userSlug:$userSlug){\n "
47
- "attendedContestsCount\n rating\n globalRanking\n localRanking\n globalTotalParticipants\n "
48
- "localTotalParticipants\n topPercentage\n }\n userContestRankingHistory(userSlug:$userSlug){\n "
49
- "attended\n totalProblems\n trendingDirection\n finishTimeInSeconds\n rating\n score\n ranking\n contest{\n "
50
- "title\n titleCn\n startTime\n }\n }\n }\n "
51
- )
52
- variables = {'userSlug' : uid }
53
- retry = 0
54
- while retry < 3 :
55
- resp = requests .post (
56
- url = self .rank_url ,
57
- json = {'query' : query , 'variables' : variables },
58
- verify = False ,
59
- )
60
- if resp .status_code == 200 :
61
- ranking = resp .json ()['data' ]['userContestRanking' ]
62
- if ranking and 'localRanking' in ranking and 'rating' in ranking :
63
- return int (ranking ['localRanking' ]), float (ranking ['rating' ])
64
- return None , None
65
- else :
66
- retry += 1
67
- return None , None
68
-
69
- def get_1600_count (self ):
70
- """使用二分的方式获取1600分以上的人数,并使用 get_user_rank 方法校准"""
71
- l , r = 1 , 1000
72
- while l < r :
73
- mid = (l + r + 1 ) >> 1
74
- page = self .load_page (mid )
75
- print (f'第 { mid } 页:' , page )
76
- if not page :
77
- return 0
78
- x , score = self .get_user_rank (page [0 ][1 ])
79
- if score < 1600 :
80
- r = mid - 1
81
- else :
82
- l = mid
83
- page = self .load_page (l )
84
- print ('校准中...' )
85
- l , r = 0 , len (page )
86
- while l < r :
87
- mid = (l + r + 1 ) >> 1
88
- _ , score = self .get_user_rank (page [mid ][1 ])
89
- if score < 1600 :
90
- r = mid - 1
91
- else :
92
- l = mid
93
-
94
- return self .get_user_rank (page [l ][1 ])[0 ]
95
-
96
- def get_user (self , rank ):
97
- """获取指定排名的用户"""
98
- if rank <= 0 :
99
- raise Exception ('无效的排名' )
100
- p = (rank - 1 ) // 25 + 1
101
- off = (rank - 1 ) % 25
102
- page = self .load_page (p )
103
- _ , score = self .get_user_rank (page [off ][1 ])
104
- return score , page [off ][1 ]
105
-
106
- def fetch_ranking_data (self ):
107
- """获取排名数据"""
108
- total = self .get_1600_count ()
109
- if not total :
110
- print ('网络故障' )
111
- sys .exit ()
112
- print (f'1600 分以上共计 { total } 人' )
113
-
114
- guardian = int (total * 0.05 )
115
- knight = int (total * 0.25 )
116
- g_first , g_last = self .get_user (1 ), self .get_user (guardian )
117
- print (
118
- f'Guardian(top 5%): 共 { guardian } 名,守门员 { g_last [0 ]} 分(uid: { g_last [1 ]} ),最高 { g_first [0 ]} 分(uid: { g_first [1 ]} )'
119
- )
120
- k_first , k_last = self .get_user (guardian + 1 ), self .get_user (knight )
121
- print (
122
- f'Knight(top 25%): 共 { knight } 名,守门员 { k_last [0 ]} 分(uid: { k_last [1 ]} ),最高 { k_first [0 ]} 分(uid: { k_first [1 ]} )'
123
- )
124
-
125
-
126
- class GlobalRanking :
127
- """全球竞赛排名"""
128
-
129
- def __init__ (self ):
130
- self .url = 'https://leetcode.com/graphql'
131
- self .lk = LocalRanking ()
132
-
133
- def load_page (self , page ):
134
- """分页加载排名列表"""
135
- query = (
136
- "{\n globalRanking(page:"
137
- + str (page )
138
- + "){\n totalUsers\n userPerPage\n myRank{\n ranking\n currentGlobalRanking\n currentRating\n dataRegion\n "
139
- "user{\n nameColor\n activeBadge{\n displayName\n icon\n __typename\n }\n __typename\n }\n __typename\n }\n "
140
- "rankingNodes{\n ranking\n currentRating\n currentGlobalRanking\n dataRegion\n user{\n "
141
- "username\n nameColor\n "
142
- "activeBadge{\n displayName\n icon\n __typename\n }\n profile{\n userAvatar\n countryCode\n countryName\n "
143
- "realName\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n }\n "
144
- )
145
- retry = 0
146
- while retry < 3 :
147
- resp = requests .post (url = self .url , json = {'query' : query }, verify = False )
148
- if resp .status_code == 200 :
149
- nodes = resp .json ()['data' ]['globalRanking' ]['rankingNodes' ]
150
49
return [
151
- (int (nd ['currentGlobalRanking' ]), nd ['user' ]['username' ])
152
- for nd in nodes
50
+ (int (v ['currentGlobalRanking' ]), v ['user' ]['username' ])
51
+ for v in nodes
153
52
]
154
- else :
155
- retry += 1
156
- return None
53
+ return []
54
+
55
+ def _user_ranking (self , region , uid ):
56
+ if region == 'CN' :
57
+ key = 'userSlug'
58
+ url = 'https://leetcode.cn/graphql/noj-go/'
59
+ query = (
60
+ "\n query userContestRankingInfo($userSlug:String!){\n userContestRanking"
61
+ "(userSlug:$userSlug){\n attendedContestsCount\n rating\n globalRanking\n localRanking\n "
62
+ "globalTotalParticipants\n localTotalParticipants\n topPercentage\n }\n "
63
+ "userContestRankingHistory(userSlug:$userSlug){\n attended\n totalProblems\n "
64
+ "trendingDirection\n finishTimeInSeconds\n rating\n score\n ranking\n contest{\n "
65
+ "title\n titleCn\n startTime\n }\n }\n }\n "
66
+ )
67
+ else :
68
+ key = 'username'
69
+ url = 'https://leetcode.com/graphql'
70
+ query = (
71
+ "\n query userContestRankingInfo($username:String!){\n userContestRanking"
72
+ "(username:$username){\n attendedContestsCount\n rating\n globalRanking\n "
73
+ "totalParticipants\n topPercentage\n badge{\n name\n }\n }\n userContestRankingHistory"
74
+ "(username:$username){\n attended\n trendDirection\n problemsSolved\n "
75
+ "totalProblems\n finishTimeInSeconds\n rating\n ranking\n contest{\n "
76
+ "title\n startTime\n }\n }\n }\n "
77
+ )
157
78
158
- def get_user_rank (self , uid ):
159
- """根据用户名获取其个人主页显示的真实分数,因为四舍五入会导致一部分 1599.xxx 的用户也显示为 1600 分"""
160
- query = (
161
- "\n query userContestRankingInfo($username:String!){\n userContestRanking(username:$username){\n "
162
- "attendedContestsCount\n rating\n globalRanking\n totalParticipants\n topPercentage\n badge{\n "
163
- "name\n }\n }\n userContestRankingHistory(username:$username){\n attended\n trendDirection\n "
164
- "problemsSolved\n totalProblems\n finishTimeInSeconds\n rating\n ranking\n contest{\n "
165
- "title\n startTime\n }\n }\n }\n "
166
- )
167
- variables = {'username' : uid }
168
- retry = 0
169
- while retry < 3 :
79
+ variables = {key : uid }
80
+ for _ in range (self .retry ):
170
81
resp = requests .post (
171
- url = self .url ,
172
- json = {'query' : query , 'variables' : variables },
173
- verify = False ,
82
+ url = url , json = {'query' : query , 'variables' : variables }, verify = False
174
83
)
175
- if resp .status_code == 200 :
176
- ranking = resp .json ()['data' ]['userContestRanking' ]
177
- if ranking and 'globalRanking' in ranking and 'rating' in ranking :
178
- score = float (ranking ['rating' ])
84
+ if resp .status_code != 200 :
85
+ continue
86
+ res = resp .json ()
87
+ if 'errors' in res :
88
+ break
89
+ ranking = res ['data' ]['userContestRanking' ]
90
+ if ranking and 'rating' in ranking :
91
+ score = float (ranking ['rating' ])
92
+ if 'localRanking' in ranking :
93
+ return int (ranking ['localRanking' ]), score
94
+ if 'globalRanking' in ranking :
179
95
return int (ranking ['globalRanking' ]), score
180
- return None , None
181
- else :
182
- retry += 1
183
- return None , None
96
+ return None , None
97
+
98
+ def get_user_ranking (self , uid ):
99
+ for region in ['CN' , 'US' ]:
100
+ # 美国站会有国服用户,这里最多需要查询两次
101
+ ranking , score = self ._user_ranking (region , uid )
102
+ if score is not None :
103
+ return ranking , score
104
+ return None
184
105
185
106
def get_1600_count (self ):
186
- """使用二分的方式获取1600分以上的人数,并使用 get_user_rank 方法校准"""
187
- l , r = 1 , 3000
188
- while l < r :
189
- mid = (l + r + 1 ) >> 1
107
+ left , right = 1 , 1000 if self .region == 'CN' else 3000
108
+ while left < right :
109
+ mid = (left + right + 1 ) >> 1
190
110
page = self .load_page (mid )
191
111
print (f'第 { mid } 页:' , page )
192
112
if not page :
193
113
return 0
194
- x , score = self .get_user_rank (page [0 ][1 ])
195
- if score is None :
196
- x , score = self .lk .get_user_rank (page [0 ][1 ])
197
- if score < 1600 :
198
- r = mid - 1
114
+ ranking , score = self .get_user_ranking (page [0 ][1 ])
115
+ if score >= 1600 :
116
+ left = mid
199
117
else :
200
- l = mid
201
- page = self .load_page (l )
118
+ right = mid - 1
119
+ page = self .load_page (left )
202
120
print ('校准中...' )
203
- l , r = 0 , len (page )
204
- while l < r :
205
- mid = (l + r + 1 ) >> 1
206
- _ , score = self .get_user_rank (page [mid ][1 ])
207
- if score is None :
208
- x , score = self .lk .get_user_rank (page [mid ][1 ])
209
- if score < 1600 :
210
- r = mid - 1
121
+ left , right = 0 , len (page )
122
+ while left < right :
123
+ mid = (left + right + 1 ) >> 1
124
+ ranking , score = self .get_user_ranking (page [mid ][1 ])
125
+ if score >= 1600 :
126
+ left = mid
211
127
else :
212
- l = mid
213
-
214
- return self .get_user_rank (page [l ][1 ])[0 ] or self .lk .get_user_rank (page [l ][1 ])[0 ]
128
+ right = mid - 1
129
+ return self .get_user_ranking (page [left ][1 ])[0 ]
215
130
216
131
def get_user (self , rank ):
217
- """获取指定排名的用户"""
218
- if rank <= 0 :
219
- raise Exception ('无效的排名' )
220
132
p = (rank - 1 ) // 25 + 1
221
- off = (rank - 1 ) % 25
133
+ offset = (rank - 1 ) % 25
222
134
page = self .load_page (p )
223
- _ , score = self .get_user_rank (page [off ][1 ])
224
- if score is None :
225
- _ , score = self .lk .get_user_rank (page [off ][1 ])
226
- return score , page [off ][1 ]
135
+ _ , score = self .get_user_ranking (page [offset ][1 ])
136
+ return score , page [offset ][1 ]
227
137
228
138
def fetch_ranking_data (self ):
229
- """获取排名数据"""
230
139
total = self .get_1600_count ()
231
140
if not total :
232
- print ('网络故障' )
233
- sys .exit ()
234
- print (f'1600 分以上共计 { total } 人' )
141
+ return
142
+ print (f'[{ self .region } ] 1600 分以上共计 { total } 人' )
235
143
236
144
guardian = int (total * 0.05 )
237
145
knight = int (total * 0.25 )
@@ -245,8 +153,12 @@ def fetch_ranking_data(self):
245
153
)
246
154
247
155
248
- local = LocalRanking ()
249
- local .fetch_ranking_data ()
156
+ # 国服竞赛排名
157
+ lk = Ranking (region = 'CN' )
158
+ lk .fetch_ranking_data ()
159
+
160
+ print ('\n ------------------------------\n ' )
250
161
251
- gk = GlobalRanking ()
162
+ # 全球竞赛排名
163
+ gk = Ranking (region = 'US' )
252
164
gk .fetch_ranking_data ()
0 commit comments