├── .DS_Store ├── .gitignore ├── README-EN.md ├── README.md ├── configuration └── config-example.json ├── doc ├── demo.gif └── todos.md ├── main.py ├── query ├── query_download_submission └── query_update_problem_sets ├── requirements.txt └── src ├── crawler.py ├── leetcode_client.py ├── logger.py └── utils.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiayangWu/LeetCodeCN-Submissions-Crawler/2be690bff1fc6c8235963fa457e29f1f7f0f3fd3/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | __pycache__/ 3 | output 4 | /configuration/config.json -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | # LeetCodeCN-Submissions-Crawler 2 | 3 | This project is a crawler used to crawl the code submitted to Leetcode-CN by programmers. 4 | 5 | # Inspiration 6 | **With three months of hard work,** 7 | 8 | **I solved four hundred questions.** 9 | 10 | **A green leetcode dashboard is nice,** 11 | 12 | **but my github is just white.** 13 | 14 | Manual uploading is impossible, and I would never do that in my whole life. 15 | 16 | After some search, I only found the submission crawler of LeetCode. So I had to build wheels by myself. 17 | 18 | My generated folder: https://github.com/JiayangWu/LeetCode-Python 19 | 20 | My problem solution blog(in Chinese): https://blog.csdn.net/qq_32424059 21 | 22 | # Instructions 23 | 1. `clone` or `download` to local environment 24 | 2. Install the dependency library `pip install -r requirements.txt` 25 | 3. Configure the `config.json` file, ~~username, password~~, cookie, local storage address, time control (days) 26 | 4. Run `python3 main.py` on the command line or use IDE to compile and run 27 | 28 | # Project Demo 29 | ![image](https://github.com/JiayangWu/LeetCodeCN-Submissions-Crawler/blob/master/doc/demo.gif) 30 | This GIF is generated by LICEcap V1.28, [download link](https://www.cockos.com/licecap/) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeetCodeCN-Submissions-Crawler 2 | [English introduction](https://github.com/JiayangWu/LeetCodeCN-Submissions-Crawler/blob/master/README-EN.md#leetcodecn-submissions-crawler) 3 | 4 | 一句话简介:本项目是一个用来爬取力扣中国上**个人提交**的代码的爬虫。 5 | 6 | 注意:是爬取【个人】也就是【你自己的账号】提交的代码,不是爬取【他人】的代码,更不是爬取【官方代码】!!! 7 | 8 | # 灵感 9 | **辛辛苦苦三个月,勤勤恳恳四百题,leetcode一片绿,github万里白。** 10 | 11 | 手动上传是不可能手动上传的,这辈子也懒得手动上传。 12 | 13 | 找了一圈只能找到LeetCode的提交爬虫,没有力扣中国的,所以只能自己造轮子了。 14 | 15 | 学了两天爬虫鼓捣了这么个东西出来,我用的蛮顺手的,希望你们也能用的顺手。 16 | 17 | 我的生成文件夹可以参考:https://github.com/JiayangWu/LeetCode-Python 18 | 19 | 我的题解博客可以参考:https://blog.csdn.net/qq_32424059 20 | 21 | # 使用方法 22 | 1. `clone`或者`download`到本地 23 | 2. 安装依赖库 `pip install -r requirements.txt` 24 | 3. 配置`configuration/config.json`文件,用户名,密码,本地存储地址,时间控制(天),是否覆盖已有的题解 25 | 4. 在命令行下运行`python3 main.py`或者使用IDE编译运行 26 | 5. 如果想传入命令行指令,请查看main.py中的相关实现 27 | 6. CML example: python main.py -id -pw -o -O(输入这个参数以覆写输出) 28 | 7. 如果你配置了`configuration/config.json`那么所有的参数都是可选的,重复配置的参数将以命令行为准(除了OverWrite) 29 | 30 | # 项目演示 31 | ![image](https://github.com/JiayangWu/LeetCodeCN-Submissions-Crawler/blob/master/doc/demo.gif) 32 | 这个GIF是由LICEcap V1.28生成的,[下载地址](https://www.cockos.com/licecap/) 33 | 34 | # 一些说明 35 | 1. 目前支持的语言有:`{"cpp": ".cpp", "python3": ".py", "python": ".py", "mysql": ".sql", "golang": ".go", "java": ".java", 36 | "c": ".c", "javascript": ".js", "TypeScript": ".ts", "php": ".php", "csharp": ".cs", "ruby": ".rb", "swift": ".swift", 37 | "scala": ".scl", "kotlin": ".kt", "rust": ".rs"}` 38 | 2. 致谢@fyears, 本脚本的`login`函数来自https://gist.github.com/fyears/487fc702ba814f0da367a17a2379e8ba 39 | 3. `config.json`里的`day`代表爬多少天之内的`submission`,比如我每天爬今天提交的题解,就是设置为`0.8`就好了,如果第一次使用需要爬所有的题解,就设一个大一点的数比如`1000`之类的。 40 | 4. `config.json`里的`overwrite`代表是否覆盖之前的题解。如果是`True`就代表如果你隔一段时间`AC`了一道题两次,第二次的题解会覆盖第一次的题解。但是你第一次的题解依然可以在`commit`里找到记录。(现在更改为在命令行中输入`-O`开启) 41 | 5. 爬虫教程可以看https://blog.csdn.net/c406495762/column/info/15321 42 | 43 | # 版本介绍 44 | 当前版本V3.3,于2023/07/09上传 45 | 1. 重构crawler代码 46 | 2. 从GraphQL中获取problem frontend id,不再依赖于Mapping 47 | 3. 更合理的temporary problem提交的处理机制 48 | 4. 减少不必要的命令行参数 49 | 50 | 历史版本V3.2,于2023/07/09上传 51 | 1. 将GraphQL code提取到单独的文件夹增加可读性 52 | 2. 改进GraphQL code, 更快,少流量 53 | 3. 等待更新Problem sets时,输出字符以鉴别是否卡死 54 | 55 | 历史版本V3.1,于2023/07/08上传 56 | 1. 添加命令行支持 57 | 58 | 历史版本V3.0,于2023/07/07上传 59 | 1. 重构代码 60 | 2. 如果输出目录不存在,即使有中间文件夹,也会创建完整路径 61 | 3. 未安装git时,跳过git相关操作 62 | 63 | 64 | 历史版本V2.4,于2023/07/07上传 65 | 1. 输出Log level和时间戳 66 | 2. 修复在Windows上在获取problem set时,不能正确decode的问题 67 | 68 | 69 | 历史版本V2.3,于2023/06/11上传 70 | 1. 实现了当一道题的题号从暂时题号变更为永久题号时,自动覆盖已经写到本地的记录的功能。之前可能会同时存在暂时题号和永久题号的两条记录。 71 | 72 | 历史版本V2.2,于2023/05/22上传 73 | 1. 实现了自动更新题号,以后可以直接从官网下载题号和题目标题,并存储在`mapping.json`里 74 | 2. 代码重构 75 | 3. 感谢[frallisland](https://github.com/frallisland) 76 | 77 | 历史版本V2.1,于2023/05/13上传 78 | 1. 更新`ProblemList`至题号2688,新的题号需要在`ProblemList`里手动添加 79 | 80 | 历史版本V2.0,于2023/05/05上传 81 | 1. 更新`ProblemList`至题号2668,新的题号需要在`ProblemList`里手动添加 82 | 2. 更新登录网址 83 | 3. 优化获取`ProblemList`的方法。现在只需要在显示有全部题目的网页上通过网页审查元素复制`html`代码并存储到同一文件夹下的[LeetCode.html](https://github.com/JiayangWu/LeetCode-Crawler-Sample-HTML),然后运行`Python ProblemListGenerator.py`即可获取最近的所有题目和题号的对应。 84 | 4. 感谢[frallisland](https://github.com/frallisland) 85 | 86 | 历史版本V1.9,于2021/04/30上传 87 | 1. 更新`ProblemList`至题号1841,新的题号需要在`ProblemList`里手动添加 88 | 89 | 历史版本V1.8,于2021/02/10上传 90 | 1. 更新`ProblemList`至题号1755,新的题号需要在`ProblemList`里手动添加 91 | 92 | 历史版本V1.7,于2020/08/27上传 93 | 1. 修复未知原因造成的无法爬取代码的bug 94 | 2. 更新`ProblemList`至题号1563,新的题号需要在`ProblemList`里手动添加 95 | 96 | 历史版本V1.6,于2020/07/27上传 97 | 1. 修复因LC-CN数据存储方式变更,导致的无法爬取代码的bug 98 | 2. 更新`ProblemList`至题号1531,新的题号需要在`ProblemList`里手动添加 99 | 100 | 历史版本V1.5,于2020/06/14上传 101 | 1. 优化`main.py`,感谢[@zxMrlc](https://github.com/zxmrlc) 102 | 2. 更新`ProblemList`至题号1473,新的题号需要在`ProblemList`里手动添加 103 | 104 | 历史版本V1.4,于2020/03/01上传 105 | 1. 更新`ProblemList`至题号1368,新的题号需要在`ProblemList`里手动添加 106 | 2. 新增对面试题的爬虫支持 107 | 108 | 历史版本V1.3,于2019/12/09上传 109 | 1. 修改爬虫逻辑并优化路径设置,感谢[@VirgilChen97](https://github.com/VirgilChen97) 110 | 3. 修复`git push`时双引号不匹配的报错 111 | 4. 修复`write`函数只能接受一个参数的报错 112 | 113 | 历史版本V1.2,于2019/11/13上传 114 | 1. 由于力扣网站登录方式变动,需要解决登录无限失败的问题,小改了`login`函数 115 | 2. 更新`ProblemList`至题号1255,新的题号需要在`ProblemList`里手动添加 116 | 3. 修复`ProbelmListGenerator.py`读取txt文件时,文件编码gbk报错的bug 117 | 118 | 历史版本V1.1,于2019/8/8上传 119 | 1. 由于力扣网站登录方式变动,需要解决登录无限失败的问题,小改了`login`函数 120 | 2. 更新`ProblemList`至题号1147, 新的题号需要在`ProblemList`里手动添加 121 | 3. 新增一个`ProblemListGenerator`函数,用于生成新的`ProblemList` 122 | 123 | 历史版本V1.0,于2019/5/24上传 124 | 1. 目前支持爬取力扣中国(leetcode-cn.com)上的个人提交的代码 125 | 2. 支持时间控制,即可以自由选择爬取**前多少天之内**的代码,比如30天内,2天内 126 | 3. 一键上传Github,注意本功能需要**手动init** 127 | 4. 在`config.json`里调整参数 128 | 5. **注意保护个人用户名及密码** 129 | 6. 目前支持到题号1044,新的题号需要在ProblemList里手动添加 130 | 131 | -------------------------------------------------------------------------------- /configuration/config-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "output_dir": "", 3 | "cookie": "", 4 | "day": 1000 5 | } -------------------------------------------------------------------------------- /doc/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiayangWu/LeetCodeCN-Submissions-Crawler/2be690bff1fc6c8235963fa457e29f1f7f0f3fd3/doc/demo.gif -------------------------------------------------------------------------------- /doc/todos.md: -------------------------------------------------------------------------------- 1 | - [x] 审查代码问题 2 | - [x] 自动化更新列表,使用 json 文件存储列表 3 | - [ ] 重写 README 4 | - [x] 审查 NoneType 错误和 避免目录不存的问题 5 | - [ ] 支持命令行启动代码 6 | - [ ] 实现按类型下载题目 7 | - [ ] 有些竞赛题目无法下载 8 | - [ ] 还会缺少部分题目 9 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # /usr/bin/env python3 3 | 4 | """ 5 | 爬取力扣中国(https://leetcode.cn)的个人AC代码,并提交到github仓库中。 6 | 在 config.json 中设置:用户名、密码、代码存储目录、最大爬取天数。 7 | 致谢 @fyears 的 login 函数来自 https://gist.github.com/fyears/487fc702ba814f0da367a17a2379e8ba 8 | 本代码原仓库地址: https://github.com/JiayangWu/LeetCodeCN-Submissions-Crawler 9 | 如爬虫失效,可在仓库中提出issue 10 | """ 11 | 12 | 13 | import requests 14 | import argparse 15 | from src.crawler import Crawler 16 | 17 | parser = argparse.ArgumentParser( 18 | prog='LeetCode-submissions-crawler', 19 | description='Get all your submissions!') 20 | 21 | parser.add_argument('-c', '--cookie', type=str, help="Your cookie for login") 22 | parser.add_argument('-o', '--output', type=str, help="Output path") 23 | parser.add_argument('-d', '--day', type=int, help="Fetching codes in 'day'") 24 | parser.add_argument('-O', '--overwrite', action='store_true', help="Flag to enable overwrite", default=False) 25 | 26 | if __name__ == '__main__': 27 | args = parser.parse_args() 28 | Crawler(args).execute() 29 | -------------------------------------------------------------------------------- /query/query_download_submission: -------------------------------------------------------------------------------- 1 | query mySubmissionDetail($id: ID!) { 2 | submissionDetail(submissionId: $id) { 3 | code 4 | question { 5 | questionFrontendId 6 | translatedTitle 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /query/query_update_problem_sets: -------------------------------------------------------------------------------- 1 | query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) { 2 | problemsetQuestionList( 3 | categorySlug: $categorySlug 4 | limit: $limit 5 | skip: $skip 6 | filters: $filters 7 | ) { 8 | questions { 9 | frontendQuestionId 10 | titleCn 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.31.0 2 | beautifulsoup4==4.7.1 3 | -------------------------------------------------------------------------------- /src/crawler.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import time 4 | import requests 5 | from src.leetcode_client import LeetcodeClient 6 | 7 | from src.utils import generatePath, gitPush 8 | 9 | from src.logger import logger 10 | 11 | TEMP_FILE_PATH = "./temp_problemset.txt" 12 | CONFIG_PATH = "./configuration/config.json" 13 | LIMIT = 40 14 | PAGE_TIME = 3 15 | START_PAGE = 0 16 | 17 | 18 | class Crawler: 19 | def __init__(self, args) -> None: 20 | with open(CONFIG_PATH, "r") as f: 21 | config = json.loads(f.read()) 22 | self.COOKIE = args.cookie if args.cookie else config['cookie'] 23 | self.OUTPUT_DIR = args.output if args.output else config['output_dir'] 24 | self.TIME_CONTROL = 3600 * 24 * \ 25 | (args.day if args.day else config['day']) 26 | self.OVERWRITE = args.overwrite 27 | self.c = 0 28 | self.visited = {} 29 | self.problems_to_be_reprocessed = [] 30 | 31 | if not os.path.exists(self.OUTPUT_DIR): 32 | os.makedirs(self.OUTPUT_DIR) 33 | 34 | self.lc = LeetcodeClient( 35 | self.COOKIE, 36 | logger=logger 37 | ) 38 | 39 | def isExpired(self, submission): 40 | cur_time = time.time() 41 | return cur_time - submission['timestamp'] > self.TIME_CONTROL 42 | 43 | def process_submissions(self, submissions): 44 | fail_count = 0 45 | for submission in submissions: 46 | if submission['status_display'] != "Accepted": 47 | continue 48 | if self.isExpired(submission): 49 | return True 50 | try: 51 | self.process_submission(submission) 52 | except FileNotFoundError as e: 53 | logger.error( 54 | "FileNotFoundError: Output directory doesn't exist!") 55 | except TypeError as e: 56 | if fail_count == 2: 57 | logger.warning( 58 | "Code continually getting None. It may caused by service banning, wait minutes to continue.") 59 | break 60 | fail_count += 1 61 | logger.warning("Code is None. Skip. Re-login") 62 | self.lc.login() 63 | time.sleep(PAGE_TIME * 2) 64 | except Exception as e: 65 | logger.error( 66 | "Unknwon bug happened, please raise an issue with your log to the writer.") 67 | logger.error(type(e)) 68 | logger.error(e) 69 | import traceback 70 | traceback.print_exc() 71 | return False 72 | 73 | def process_submission(self, submission): 74 | submission_details = self.lc.downloadCode(submission) 75 | 76 | problem_frontendId = submission_details["question"]["questionFrontendId"] 77 | problem_title = submission_details["question"]["translatedTitle"] 78 | submission_lang = submission["lang"] 79 | submission_token = problem_title + submission_lang 80 | 81 | if submission_token not in self.visited: 82 | self.visited[submission_token] = problem_frontendId 83 | full_path = generatePath( 84 | problem_frontendId, problem_title, submission["lang"], self.OUTPUT_DIR 85 | ) 86 | if not self.OVERWRITE and os.path.exists(full_path): 87 | return 88 | self.save_code( 89 | submission_details["code"], problem_frontendId, problem_title, submission_lang, full_path) 90 | 91 | def save_code(self, code, problem_frontendId, problem_title, submission_lang, full_path): 92 | with open(full_path, "w") as f: # 开始写到本地 93 | f.write(code) 94 | logger.info("Writing ends! " + full_path) 95 | if self.is_temporary_problem(problem_frontendId): 96 | self.problems_to_be_reprocessed.append( 97 | (full_path, problem_title, submission_lang)) 98 | 99 | def is_temporary_problem(self, problem_frontendId): 100 | if problem_frontendId[0].isdigit(): 101 | format_name = '{:0>4}'.format( 102 | problem_frontendId) 103 | if format_name[0] >= "6": 104 | return True 105 | return False 106 | 107 | def process_temporary_problems(self): 108 | if os.path.exists(TEMP_FILE_PATH): 109 | with open(TEMP_FILE_PATH, "r") as f: 110 | for line in f.readlines(): 111 | try: 112 | path, title, lang = line.rstrip().split(" ", 1) 113 | except ValueError: 114 | logger.warning("Your " + TEMP_FILE_PATH + 115 | " is in old format, delete all temp code.") 116 | _, path = line.rstrip().split(" ", 1) 117 | os.remove(path) 118 | token = title + lang 119 | if token in self.visited: 120 | if not self.is_temporary_problem(self.visited[token]): 121 | logger.info( 122 | path + " is no longer a temporary problem, delete temp code.") 123 | os.remove(path) 124 | else: 125 | self.problems_to_be_reprocessed.append( 126 | (path, title, lang)) 127 | 128 | def write_temorary_file(self): 129 | if self.problems_to_be_reprocessed: 130 | with open(TEMP_FILE_PATH, "w") as f: 131 | for full_path, problem_title, submission_lang in self.problems_to_be_reprocessed: 132 | f.write(full_path + " " + problem_title + 133 | " " + submission_lang + "\n") 134 | logger.info("Record temporary code: " + full_path) 135 | 136 | def scraping(self): 137 | page_num = START_PAGE 138 | while True: 139 | submission_list = self.lc.getSubmissionList(page_num) 140 | expired = self.process_submissions( 141 | submission_list["submissions_dump"]) 142 | if not submission_list.get("has_next") or expired: 143 | logger.info("No more submissions!") 144 | break 145 | page_num += LIMIT 146 | time.sleep(PAGE_TIME) 147 | self.process_temporary_problems() 148 | self.write_temorary_file() 149 | 150 | def execute(self): 151 | logger.info('Login') 152 | self.lc.login() 153 | logger.info('Start scrapping') 154 | self.scraping() 155 | logger.info('End scrapping \n') 156 | gitPush(self.OUTPUT_DIR) 157 | 158 | 159 | if __name__ == '__main__': 160 | c = Crawler() 161 | c.execute() 162 | -------------------------------------------------------------------------------- /src/leetcode_client.py: -------------------------------------------------------------------------------- 1 | import time 2 | import requests 3 | import json 4 | 5 | 6 | class LeetcodeClient: 7 | LOGIN_PATH = 'accounts/login/' 8 | GRAPHQL_PATH = 'graphql/' 9 | 10 | def __init__( 11 | self, 12 | cookie, 13 | sleep_time=5, 14 | base_url='https://leetcode.cn/', 15 | logger=None) -> None: 16 | self.sleep_time = sleep_time 17 | self.endpoint = base_url 18 | self.logger = logger 19 | 20 | self.client = requests.session() 21 | self.client.cookies.set('LEETCODE_SESSION', cookie) 22 | self.client.encoding = "utf-8" 23 | 24 | self.headers = { 25 | 'Connection': 'keep-alive', 26 | 'Content-Type': 'application/json', 27 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36' 28 | } 29 | 30 | def login(self) -> None: 31 | ATTEMPT = 3 32 | login_url = self.endpoint + self.LOGIN_PATH 33 | login_header = self.headers 34 | login_header['Referer'] = login_url 35 | 36 | for try_cnt in range(ATTEMPT): 37 | self.client.get(login_url) 38 | result = self.client.post( 39 | login_url, headers=login_header) 40 | 41 | # result.url 判断是否真正登录成功 42 | if result.ok and result.url == self.endpoint: 43 | self.logger.info("Login successfully!") 44 | return 45 | self.logger.warning("Login failed, Wait till next round!") 46 | if try_cnt != ATTEMPT - 1: 47 | time.sleep(self.sleep_time) 48 | 49 | self.logger.error( 50 | "LoginError: Login failed, ensure your login credential is correct!" 51 | ) 52 | 53 | raise Exception( 54 | "LoginError: Login failed, ensure your login credential is correct!") 55 | 56 | def downloadCode(self, submission) -> str: 57 | with open('query/query_download_submission', 'r') as f: 58 | query_string = f.read() 59 | 60 | data = { 61 | 'query': query_string, 62 | 'operationName': "mySubmissionDetail", 63 | "variables": { 64 | "id": submission["id"] 65 | } 66 | } 67 | 68 | response = self.client.post( 69 | self.endpoint + 70 | self.GRAPHQL_PATH, 71 | json=data, 72 | headers=self.headers) 73 | submission_details = response.json()["data"]["submissionDetail"] 74 | return submission_details 75 | 76 | def getSubmissionList(self, page_num): 77 | self.logger.info( 78 | 'Now scraping submissions list for page:{page_num}'.format( 79 | page_num=page_num 80 | ) 81 | ) 82 | submissions_url = "https://leetcode.cn/api/submissions/?offset={page_num}&limit=40".format( 83 | page_num=page_num 84 | ) 85 | submissions_list = self.client.get( 86 | submissions_url, headers=self.headers) 87 | return json.loads(submissions_list.text) 88 | -------------------------------------------------------------------------------- /src/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | class Logger: 5 | LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" 6 | DATE_FORMAT = "%Y/%m/%d %H:%M:%S" 7 | logging.getLogger().setLevel(logging.DEBUG) 8 | 9 | logging.basicConfig( 10 | level=logging.INFO, format=LOG_FORMAT, datefmt=DATE_FORMAT 11 | ) 12 | 13 | def debug(msg): 14 | logging.debug(msg) 15 | 16 | def info(self, msg): 17 | logging.info(msg) 18 | 19 | def warning(self, msg): 20 | logging.warning(msg) 21 | 22 | def error(self, msg): 23 | logging.error(msg) 24 | 25 | def critical(self, msg): 26 | logging.critical(msg) 27 | 28 | 29 | logger = Logger() 30 | -------------------------------------------------------------------------------- /src/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | from src.logger import logger 5 | 6 | FILE_FORMAT = { 7 | "C++": ".cpp", 8 | "Python3": ".py", 9 | "Python": ".py", 10 | "MySQL": ".sql", 11 | "Go": ".go", 12 | "Java": ".java", 13 | "C": ".c", 14 | "JavaScript": ".js", 15 | "TypeScript": ".ts", 16 | "PHP": ".php", 17 | "C#": ".cs", 18 | "Ruby": ".rb", 19 | "Swift": ".swift", 20 | "Scala": ".scl", 21 | "Kotlin": ".kt", 22 | "Rust": ".rs"} 23 | 24 | def generatePath(problem_id, problem_title, submission_language, OUTPUT_DIR): 25 | # 如果题目是传统的数字题号 26 | if problem_id[0].isdigit(): 27 | problem_id = int(problem_id) 28 | pathname = OUTPUT_DIR + "/" + \ 29 | '{:0=4}'.format(problem_id) + "." + problem_title 30 | filename = '{:0=4}'.format(problem_id) + "-" + \ 31 | problem_title + FILE_FORMAT[submission_language] 32 | else: 33 | # 如果题目是新的面试题, 比如 剑指 Offer 27-二叉树的镜像.py 34 | pathname = OUTPUT_DIR + "/" + problem_id + "." + problem_title 35 | filename = problem_id + "-" + problem_title + \ 36 | FILE_FORMAT[submission_language] 37 | 38 | if not os.path.exists(pathname): 39 | os.mkdir(pathname) 40 | 41 | return os.path.join(pathname, filename) 42 | 43 | 44 | def gitPush(OUTPUT_DIR): 45 | today = time.strftime('%Y-%m-%d', time.localtime(time.time())) 46 | os.chdir(OUTPUT_DIR) 47 | instructions = ["git add .", "git status", 48 | "git commit -m \"" + today + "\"", "git push"] 49 | try: 50 | for instruction in instructions: 51 | os.system(instruction) 52 | logger.info("~~~~~~~~~~~~~" + instruction + " finished! ~~~~~~~~") 53 | except Exception: 54 | logger.warning( 55 | "Git operations failed, please install git, skip it for now.") --------------------------------------------------------------------------------