from string import Template import requests import urllib3 urllib3.disable_warnings() class Ranking: def __init__(self, region='CN', retry=3): self.retry = retry self.region = region if region == 'CN': self.url = 'https://leetcode.cn/graphql' self.page_query = Template( "{\n localRankingV2(page:$page) {\nmyRank {\nattendedContestCount\n" "currentRatingRanking\ndataRegion\nisDeleted\nuser {\nrealName\n" "userAvatar\nuserSlug\n__typename\n}\n__typename\n}\npage\ntotalUsers\n" "userPerPage\nrankingNodes {\nattendedContestCount\ncurrentRatingRanking\n" "dataRegion\nisDeleted\nuser {\nrealName\nuserAvatar\nuserSlug\n__typename\n}\n__" "typename\n}\n__typename\n }\n}\n" ) else: self.url = 'https://leetcode.com/graphql' self.page_query = Template( "{\nglobalRanking(page:$page){\ntotalUsers\nuserPerPage\nmyRank{\nranking\n" "currentGlobalRanking\ncurrentRating\ndataRegion\nuser{\nnameColor\nactiveBadge{\n" "displayName\nicon\n__typename\n}\n__typename\n}\n__typename\n}\nrankingNodes{\n" "ranking\ncurrentRating\ncurrentGlobalRanking\ndataRegion\nuser{\nusername\nnameColor\n" "activeBadge{\ndisplayName\nicon\n__typename\n}\nprofile{\nuserAvatar\ncountryCode\n" "countryName\nrealName\n__typename\n}\n__typename\n}\n__typename\n}\n__typename\n}\n}\n" ) def load_page(self, page): query = self.page_query.substitute(page=page) for _ in range(self.retry): resp = requests.post(url=self.url, json={'query': query}, verify=False) if resp.status_code != 200: continue nodes = resp.json()['data'][ 'localRankingV2' if self.region == 'CN' else 'globalRanking' ]['rankingNodes'] if self.region == 'CN': return [ (int(v['currentRatingRanking']), v['user']['userSlug']) for v in nodes ] else: return [ (int(v['currentGlobalRanking']), v['user']['username']) for v in nodes ] return [] def _user_ranking(self, region, uid): if region == 'CN': key = 'userSlug' url = 'https://leetcode.cn/graphql/noj-go/' query = ( "\nquery userContestRankingInfo($userSlug:String!){\nuserContestRanking" "(userSlug:$userSlug){\nattendedContestsCount\nrating\nglobalRanking\nlocalRanking\n" "globalTotalParticipants\nlocalTotalParticipants\ntopPercentage\n}\n" "userContestRankingHistory(userSlug:$userSlug){\nattended\ntotalProblems\n" "trendingDirection\nfinishTimeInSeconds\nrating\nscore\nranking\ncontest{\n" "title\ntitleCn\nstartTime\n}\n}\n}\n" ) else: key = 'username' url = 'https://leetcode.com/graphql' query = ( "\nquery userContestRankingInfo($username:String!){\nuserContestRanking" "(username:$username){\nattendedContestsCount\nrating\nglobalRanking\n" "totalParticipants\ntopPercentage\nbadge{\nname\n}\n}\nuserContestRankingHistory" "(username:$username){\nattended\ntrendDirection\nproblemsSolved\n" "totalProblems\nfinishTimeInSeconds\nrating\nranking\ncontest{\n" "title\nstartTime\n}\n}\n}\n " ) variables = {key: uid} for _ in range(self.retry): resp = requests.post( url=url, json={'query': query, 'variables': variables}, verify=False ) if resp.status_code != 200: continue res = resp.json() if 'errors' in res: break ranking = res['data']['userContestRanking'] if ranking and 'rating' in ranking: score = float(ranking['rating']) if 'localRanking' in ranking: return int(ranking['localRanking']), score if 'globalRanking' in ranking: return int(ranking['globalRanking']), score return None, None def get_user_ranking(self, uid): for region in ['CN', 'US']: # 美国站会有国服用户,这里最多需要查询两次 ranking, score = self._user_ranking(region, uid) if score is not None: return ranking, score return None def get_1600_count(self): left, right = 1, 4000 while left < right: mid = (left + right + 1) >> 1 page = self.load_page(mid) print(f'第 {mid} 页:', page) if not page: return 0 ranking, score = self.get_user_ranking(page[0][1]) if score >= 1600: left = mid else: right = mid - 1 page = [uid for _, uid in self.load_page(left) if uid] print('校准中...') left, right = 0, len(page) - 1 while left < right: mid = (left + right + 1) >> 1 ranking, score = self.get_user_ranking(page[mid]) if score >= 1600: left = mid else: right = mid - 1 return self.get_user_ranking(page[left])[0] def get_user(self, rank): p = (rank - 1) // 25 + 1 offset = (rank - 1) % 25 page = self.load_page(p) _, score = self.get_user_ranking(page[offset][1]) return score, page[offset][1] def fetch_ranking_data(self): total = self.get_1600_count() if not total: return print(f'[{self.region}] 1600 分以上共计 {total} 人') guardian = int(total * 0.05) knight = int(total * 0.25) g_first, g_last = self.get_user(1), self.get_user(guardian) print( f'Guardian(top 5%): 共 {guardian} 名,守门员 {g_last[0]} 分(uid: {g_last[1]}),最高 {g_first[0]} 分(uid: {g_first[1]})' ) k_first, k_last = self.get_user(guardian + 1), self.get_user(knight) print( f'Knight(top 25%): 共 {knight} 名,守门员 {k_last[0]} 分(uid: {k_last[1]}),最高 {k_first[0]} 分(uid: {k_first[1]})' ) # 国服竞赛排名 lk = Ranking(region='CN') lk.fetch_ranking_data() print('\n------------------------------\n') # 全球竞赛排名 gk = Ranking(region='US') gk.fetch_ranking_data()