使用 Python 抓取周赛 Guardian 和 Knight 门槛分
8142
2021.08.10
2025.03.20
发布于 未知归属地

计算方式

力扣竞赛 - 勋章及成就规则
简而言之就是
Guardian1600 分以上的所有参与竞赛人员的前 5%
Knight1600 分以上的所有参与竞赛人员的前 5% ~ 25%

代码

import re
import sys
from functools import cache

import requests

url = 'https://leetcode.cn/graphql'

# 分页加载排名列表
@cache
def loadPage(page):
    query = "{\n  localRankingV2(page:" + str(
        page) + ") {\nmyRank {\nattendedContestCount\ncurrentRatingRanking\ndataRegion\nisDeleted\n" \
                "user {\nrealName\nuserAvatar\nuserSlug\n__typename\n}\n__typename\n}\npage\ntotalUsers\nuserPerPage\n" \
                "rankingNodes {\nattendedContestCount\ncurrentRatingRanking\ndataRegion\nisDeleted\n" \
                "user {\nrealName\nuserAvatar\nuserSlug\n__typename\n}\n__typename\n}\n__typename\n  }\n}\n"
    retry = 0
    while retry < 3:
        resp = requests.post(url=url, json={'query': query})
        if resp.status_code == 200:
            nodes = resp.json()['data']['localRankingV2']['rankingNodes']
            return [(int(nd['currentRatingRanking']), nd['user']['userSlug']) for nd in nodes]
        else:
            retry += 1
    return None

# 根据用户名获取其个人主页显示的真实分数,因为四舍五入会导致一部分 1599.xxx 的用户也显示为 1600 分
@cache
def getUserRank(uid):
    operationName = "userContest"
    query = "query userContest($userSlug: String!){\n userContestRanking(userSlug: $userSlug){" \
            "\ncurrentRatingRanking\nratingHistory\n}\n}\n "
    variables = {'userSlug': uid}
    retry = 0
    while retry < 3:
        resp = requests.post(url=url, json={
            'operationName': operationName,
            'query': query,
            'variables': variables
        })
        if resp.status_code == 200:
            ranking = resp.json()['data']['userContestRanking']
            score = None
            if ranking and 'ratingHistory' in ranking:
                s = ranking['ratingHistory']
                mth = re.search(r'(\d+(?:\.\d+)?)(?:, null)*]$', s)
                if mth:
                    score = mth.group(1)
            return (ranking['currentRatingRanking'], float(score)) if score else (None, None)
        else:
            retry += 1
    return None, None

# 使用二分的方式获取1600分以上的人数,并使用 getUserRank 方法校准
def get1600Count() -> int:
    l, r = 1, 3000
    while l < r:
        mid = (l + r + 1) // 2
        page = loadPage(mid)
        print(f'第 {mid} 页:', page)
        if not page:
            return 0
        score = None
        for _, uid in page:
            if not uid:
                continue
            score = getUserRank(uid)[1]
            if score is not None:
                break
        if score < 1600:
            r = mid - 1
        else:
            l = mid
    page = [uid for _, uid in loadPage(l) if uid]
    print('校准中...')
    l, r = 0, len(page) - 1
    while l < r:
        mid = (l + r + 1) // 2
        ranking, score = getUserRank(page[mid])
        if score < 1600:
            r = mid - 1
        else:
            l = mid

    return getUserRank(page[l])[0]


# 获取指定排名的用户, alter: 替补方向,向中间替补
@cache
def getUser(rank, alter):
    while rank:
        if rank <= 0:
            raise Exception('无效的排名')
        p = (rank - 1) // 25 + 1
        off = (rank - 1) % 25
        page = loadPage(p)
        if page[off][1]:
            ranking, score = getUserRank(page[off][1])
            return score, page[off][1]
        else:
            rank += alter
    return '--', '--'


total = get1600Count()
if not total:
    print('网络故障')
    sys.exit()
print(f'1600 分以上共计 {total} 人')

guardian = int(total * 0.05)
knight = int(total * 0.25)
g_first, g_last = getUser(1, 1), getUser(guardian, -1)
print(f'Guardian(top 5%): 共 {guardian} 名,守门员 {g_last[0]} 分(uid: {g_last[1]}),最高 {g_first[0]} 分(uid: {g_first[1]})')
k_first, k_last = getUser(guardian + 1, 1), getUser(knight, -1)
print(f'Knight(top 25%): 共 {knight} 名,守门员 {k_last[0]} 分(uid: {k_last[1]}),最高 {k_first[0]} 分(uid: {k_first[1]})')
评论 (55)