├── LICENSE ├── leetcode-crawler.py ├── pictures ├── 文档示例.png └── 生成目录.png └── readme.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 gcyml 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /leetcode-crawler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sqlite3 5 | import json 6 | import traceback 7 | import html2text 8 | import os 9 | import requests 10 | from requests_toolbelt import MultipartEncoder 11 | import random,time 12 | import re 13 | import argparse,sys 14 | import threading 15 | 16 | db_path = 'leetcode.db' 17 | user_agent = r'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36' 18 | 19 | def initLock(l): 20 | global lock 21 | lock = l 22 | 23 | 24 | threadLock = threading.Lock() 25 | 26 | # 获取题目信息线程 27 | class insetQuestionThread(threading.Thread): 28 | def __init__(self, title_slug, *args): 29 | threading.Thread.__init__(self) 30 | self.title_slug = title_slug 31 | self.status = None 32 | if len(args) == 1: 33 | self.status = args[0] 34 | def run(self): 35 | IS_SUCCESS = False 36 | conn = sqlite3.connect(db_path, timeout=10) 37 | while not IS_SUCCESS: 38 | try: 39 | # 休眠随机 1-3 秒,以免爬去频率过高被服务器禁掉 40 | time.sleep(random.randint(1, 3)) 41 | cursor = conn.cursor() 42 | 43 | session = requests.Session() 44 | headers = {'User-Agent': user_agent, 'Connection': 45 | 'keep-alive', 'Content-Type': 'application/json', 46 | 'Referer': 'https://leetcode.com/problems/' + self.title_slug} 47 | 48 | url = "https://leetcode.com/graphql" 49 | params = {'operationName': "getQuestionDetail", 50 | 'variables': {'titleSlug': self.title_slug}, 51 | 'query': '''query getQuestionDetail($titleSlug: String!) { 52 | question(titleSlug: $titleSlug) { 53 | questionId 54 | questionFrontendId 55 | questionTitle 56 | questionTitleSlug 57 | content 58 | difficulty 59 | stats 60 | similarQuestions 61 | categoryTitle 62 | topicTags { 63 | name 64 | slug 65 | } 66 | } 67 | }''' 68 | } 69 | 70 | json_data = json.dumps(params).encode('utf8') 71 | 72 | question_detail = () 73 | resp = session.post(url, data = json_data, headers = headers, timeout = 10) 74 | content = resp.json() 75 | questionId = content['data']['question']['questionId'] 76 | tags = [] 77 | for tag in content['data']['question']['topicTags']: 78 | tags.append(tag['name']) 79 | 80 | if content['data']['question']['content'] != None: 81 | question_detail = (questionId, 82 | content['data']['question']['questionFrontendId'], 83 | content['data']['question']['questionTitle'], 84 | content['data']['question']['questionTitleSlug'], 85 | content['data']['question']['difficulty'], 86 | content['data']['question']['content'], 87 | self.status) 88 | threadLock.acquire() 89 | cursor.execute('INSERT INTO question (id, frontend_id, title, slug, difficulty, content, status) VALUES (?, ?, ?, ?, ?, ?, ?)', question_detail) 90 | for tag in tags: 91 | question_tag = (questionId, tag) 92 | cursor.execute('INSERT INTO question_tag (question_id, tag) VALUES (?, ?)', question_tag) 93 | conn.commit() 94 | print("insert question [%s] success" %(self.title_slug)) 95 | threadLock.release() 96 | IS_SUCCESS = True 97 | # 若出现连接超时或连接错误则继续获取 98 | except (requests.exceptions.Timeout,requests.exceptions.ConnectionError) as error: 99 | print(str(error)) 100 | cursor.close() 101 | conn.close() 102 | 103 | class LeetcodeCrawler(): 104 | def __init__(self): 105 | self.session = requests.Session() 106 | self.csrftoken = '' 107 | self.is_login = False 108 | 109 | # 获取到 token 110 | def get_csrftoken(self): 111 | url = 'https://leetcode.com' 112 | cookies = self.session.get(url).cookies 113 | for cookie in cookies: 114 | if cookie.name == 'csrftoken': 115 | self.csrftoken = cookie.value 116 | break 117 | 118 | # 登陆 leetcode 账号 119 | def login(self, username, password): 120 | url = "https://leetcode.com/accounts/login" 121 | 122 | params_data = { 123 | 'csrfmiddlewaretoken': self.csrftoken, 124 | 'login': username, 125 | 'password':password, 126 | 'next': 'problems' 127 | } 128 | headers = {'User-Agent': user_agent, 'Connection': 'keep-alive', 'Referer': 'https://leetcode.com/accounts/login/', 129 | "origin": "https://leetcode.com"} 130 | m = MultipartEncoder(params_data) 131 | 132 | headers['Content-Type'] = m.content_type 133 | self.session.post(url, headers = headers, data = m, timeout = 10, allow_redirects = False) 134 | self.is_login = self.session.cookies.get('LEETCODE_SESSION') != None 135 | return self.is_login 136 | 137 | def get_problems(self, filters): 138 | 139 | url = "https://leetcode.com/api/problems/all/" 140 | 141 | headers = {'User-Agent': user_agent, 'Connection': 'keep-alive'} 142 | resp = self.session.get(url, headers = headers, timeout = 10) 143 | 144 | question_list = json.loads(resp.content.decode('utf-8')) 145 | 146 | question_update_list = [] 147 | threads = [] 148 | 149 | cursor = self.conn.cursor() 150 | 151 | for question in question_list['stat_status_pairs']: 152 | question_id = question['stat']['question_id'] 153 | question_slug = question['stat']['question__title_slug'] 154 | question_status = question['status'] 155 | 156 | question_difficulty = "None" 157 | level = question['difficulty']['level'] 158 | 159 | if level == 1: 160 | question_difficulty = "Easy" 161 | elif level == 2: 162 | question_difficulty = "Medium" 163 | elif level == 3: 164 | question_difficulty = "Hard" 165 | 166 | 167 | if filters.get('difficulty'): 168 | if filters['difficulty'] != question_difficulty: 169 | continue 170 | 171 | if filters.get('status'): 172 | if filters['status'] != question_status: 173 | continue 174 | 175 | if question['paid_only']: 176 | continue 177 | 178 | cursor.execute('SELECT status FROM question WHERE id = ?', (question_id,)) 179 | result = cursor.fetchone() 180 | if not result: 181 | # 创建新线程 182 | thread = insetQuestionThread(question_slug, question_status) 183 | thread.start() 184 | while True: 185 | #判断正在运行的线程数量,如果小于5则退出while循环, 186 | #进入for循环启动新的进程.否则就一直在while循环进入死循环 187 | if(len(threading.enumerate()) < 60): 188 | break 189 | 190 | # 添加线程到线程列表 191 | threads.append(thread) 192 | elif self.is_login and question_status != result[0]: 193 | question_update_list.append((question_status, question_id)) 194 | for t in threads: 195 | t.join() 196 | 197 | cursor.executemany('UPDATE question SET status = ? WHERE id = ?', question_update_list) 198 | self.conn.commit() 199 | cursor.close() 200 | 201 | 202 | def connect_db(self, db_path): 203 | self.conn = sqlite3.connect(db_path, timeout = 10) 204 | cursor = self.conn.cursor() 205 | 206 | query_table_exists = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name = 'question';" 207 | cursor.execute(query_table_exists) 208 | if cursor.fetchone()[0] == 0: 209 | cursor.execute('''CREATE TABLE question 210 | (id INT PRIMARY KEY NOT NULL, 211 | frontend_id INT NOT NULL, 212 | title CHAR(50) NOT NULL, 213 | slug CHAR(50) NOT NULL, 214 | difficulty CHAR(10) NOT NULL, 215 | content TEXT NOT NULL, 216 | status CHAR(10));''') 217 | 218 | query_table_exists = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name = 'last_ac_submission_record';" 219 | cursor.execute(query_table_exists) 220 | if cursor.fetchone()[0] == 0: 221 | cursor.execute('''CREATE TABLE last_ac_submission_record 222 | (id INT PRIMARY KEY NOT NULL, 223 | question_slug CHAR(50) NOT NULL, 224 | timestamp INT NOT NULL, 225 | language CHAR(10) NOT NULL, 226 | code TEXT, 227 | runtime CHAR(10));''') 228 | 229 | query_table_exists = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name = 'question_tag';" 230 | cursor.execute(query_table_exists) 231 | if cursor.fetchone()[0] == 0: 232 | cursor.execute('''CREATE TABLE question_tag 233 | (question_id INT NOT NULL, 234 | tag CHAR(30) NOT NULL);''') 235 | 236 | cursor.close() 237 | 238 | def generate_questions_markdown(self, path, filters): 239 | if not os.path.isdir(path): 240 | os.mkdir(path) 241 | cursor = self.conn.cursor() 242 | cursor.execute("SELECT * FROM question") 243 | for row in cursor: 244 | question_detail = { 245 | 'id': row[0], 246 | 'frontedId': row[1], 247 | 'title': row[2], 248 | 'slug': row[3], 249 | 'difficulty': row[4], 250 | 'content': row[5], 251 | 'status': row[6] 252 | } 253 | 254 | if not self.filter_question(question_detail, filters): 255 | continue 256 | tags = '' 257 | tag_cursor = self.conn.cursor() 258 | tag_cursor.execute('SELECT tag FROM question_tag WHERE question_id = ?', (row[0],)) 259 | tag_list = tag_cursor.fetchall() 260 | 261 | for tag in tag_list: 262 | tags += tag[0] + ', ' 263 | 264 | if len(tags) > 2: 265 | tags = tags[:-2] 266 | question_detail['tags'] = tags 267 | 268 | has_get_code = filters.__contains__('code') 269 | self.generate_question_markdown(question_detail, path, has_get_code) 270 | cursor.close() 271 | 272 | def filter_question(self, question_detail, filters): 273 | 274 | if filters.get('difficulty'): 275 | if filters['difficulty'] != question_detail['difficulty']: 276 | return False 277 | if filters.get('status'): 278 | if filters['status'] != question_detail['status']: 279 | return False 280 | 281 | tag_cursor = self.conn.cursor() 282 | tag_cursor.execute('SELECT tag FROM question_tag WHERE question_id = ?', (question_detail['id'],)) 283 | tag_list = tag_cursor.fetchall() 284 | tag_cursor.close() 285 | if filters.get('tags'): 286 | tag_count = 0 287 | for tag in tag_list: 288 | if tag[0] in filters['tags']: 289 | tag_count += 1 290 | if tag_count != len(filters['tags']): 291 | return False 292 | return True 293 | 294 | 295 | def get_ac_question_submission(self, filters): 296 | if not self.is_login: 297 | return 298 | sql = "SELECT id,slug,difficulty,status FROM question WHERE status = 'ac';" 299 | cursor = self.conn.cursor() 300 | cursor.execute(sql) 301 | results = cursor.fetchall() 302 | 303 | threads = [] 304 | 305 | slug_list = [] 306 | for row in results: 307 | question_detail = { 308 | 'id': row[0], 309 | 'slug': row[1], 310 | 'difficulty': row[2], 311 | 'status': row[3] 312 | } 313 | 314 | if not self.filter_question(question_detail, filters): 315 | continue 316 | slug = question_detail['slug'] 317 | slug_list.append(question_detail['slug']) 318 | IS_SUCCESS = False 319 | while not IS_SUCCESS: 320 | try: 321 | url = "https://leetcode.com/graphql" 322 | params = {'operationName': "Submissions", 323 | 'variables':{"offset":0, "limit":20, "lastKey": '', "questionSlug": slug}, 324 | 'query': '''query Submissions($offset: Int!, $limit: Int!, $lastKey: String, $questionSlug: String!) { 325 | submissionList(offset: $offset, limit: $limit, lastKey: $lastKey, questionSlug: $questionSlug) { 326 | lastKey 327 | hasNext 328 | submissions { 329 | id 330 | statusDisplay 331 | lang 332 | runtime 333 | timestamp 334 | url 335 | isPending 336 | __typename 337 | } 338 | __typename 339 | } 340 | }''' 341 | } 342 | 343 | json_data = json.dumps(params).encode('utf8') 344 | 345 | headers = {'User-Agent': user_agent, 'Connection': 'keep-alive', 'Referer': 'https://leetcode.com/accounts/login/', 346 | "Content-Type": "application/json", 'x-csrftoken': self.csrftoken} 347 | resp = self.session.post(url, data = json_data, headers = headers, timeout = 10) 348 | content = resp.json() 349 | for submission in content['data']['submissionList']['submissions']: 350 | if submission['statusDisplay'] == "Accepted": 351 | cursor.execute("SELECT COUNT(*) FROM last_ac_submission_record WHERE id =" + str(submission['id'])) 352 | if cursor.fetchone()[0] == 0: 353 | IS_GET_SUBMISSION_SUCCESS = False 354 | while not IS_GET_SUBMISSION_SUCCESS: 355 | code_content = self.session.get("https://leetcode.com" + submission['url'], headers = headers, timeout = 10) 356 | 357 | pattern = re.compile( 358 | r'submissionCode: \'(?P.*)\',\n editCodeUrl', re.S 359 | ) 360 | m1 = pattern.search(code_content.text) 361 | code = m1.groupdict()['code'] if m1 else None 362 | if not code: 363 | print('WARN: Can not get [{}] solution code'.format(slug)) 364 | continue 365 | IS_GET_SUBMISSION_SUCCESS = True 366 | 367 | submission_detail = (submission['id'], slug, submission['timestamp'], submission['lang'], submission['runtime'], code) 368 | cursor.execute("INSERT INTO last_ac_submission_record (id, question_slug, timestamp, language, runtime, code) VALUES(?, ?, ?, ?, ?, ?)", submission_detail) 369 | print("insert submission[%s] success" % (submission['id'])) 370 | self.conn.commit() 371 | IS_SUCCESS = True 372 | break 373 | except (requests.exceptions.Timeout,requests.exceptions.ConnectionError) as error: 374 | print(str(error)) 375 | finally: 376 | pass 377 | cursor.close() 378 | 379 | def generate_question_markdown(self, question, path, has_get_code): 380 | text_path = os.path.join(path, "{:0>3d}-{}".format(question['frontedId'], question['slug'])) 381 | if not os.path.isdir(text_path): 382 | os.mkdir(text_path) 383 | with open(os.path.join(text_path, "README.md"), 'w', encoding='utf-8') as f: 384 | f.write("# [{}][title]\n".format(question['title'])) 385 | f.write("\n## Description\n\n") 386 | text = question['content'] 387 | 388 | content = html2text.html2text(text).replace("**Input:**", "Input:").replace("**Output:**", "Output:").replace('**Explanation:**', 'Explanation:').replace('\n ', ' ') 389 | f.write(content) 390 | 391 | f.write("\n**Tags:** {}\n".format(question['tags'])) 392 | f.write("\n**Difficulty:** {}\n".format(question['difficulty'])) 393 | f.write("\n## 思路\n") 394 | 395 | if self.is_login and has_get_code: 396 | sql = "SELECT code, language FROM last_ac_submission_record WHERE question_slug = ? ORDER BY timestamp" 397 | cursor = self.conn.cursor() 398 | cursor.execute(sql, (question['slug'],)) 399 | submission = cursor.fetchone() 400 | cursor.close() 401 | 402 | if submission != None: 403 | f.write("\n``` %s\n" %(submission[1])) 404 | f.write(submission[0].encode('utf-8').decode('unicode_escape')) 405 | f.write("\n```\n") 406 | 407 | 408 | f.write("\n[title]: https://leetcode.com/problems/{}\n".format(question['slug'])) 409 | 410 | def generate_questions_submission(self, path, filters): 411 | if not self.is_login: 412 | return 413 | 414 | sql = """ 415 | SELECT l.question_slug, l.code,l.language, q.frontend_id,max(l.timestamp) FROM last_ac_submission_record as l,question as q 416 | WHERE l.question_slug == q.slug and q.status = 'ac' GROUP BY l.question_slug 417 | """ 418 | cursor = self.conn.cursor() 419 | cursor.execute(sql) 420 | 421 | filter_cursor = self.conn.cursor() 422 | for submission in cursor: 423 | filter_cursor.execute("SELECT id,slug,difficulty,status FROM question WHERE slug = ?", (submission[0],)) 424 | result = filter_cursor.fetchone() 425 | question_detail = { 426 | 'id': result[0], 427 | 'slug': result[1], 428 | 'difficulty': result[2], 429 | 'status': result[3] 430 | } 431 | if not self.filter_question(question_detail, filters): 432 | continue 433 | self.generate_question_submission(path, submission) 434 | 435 | cursor.close() 436 | filter_cursor.close() 437 | 438 | 439 | def generate_question_submission(self, path, submission): 440 | if not os.path.isdir(path): 441 | os.mkdir(path) 442 | 443 | text_path = os.path.join(path, "{:0>3d}-{}".format(submission[3], submission[0])) 444 | 445 | if not os.path.isdir(text_path): 446 | os.mkdir(text_path) 447 | with open(os.path.join(text_path, "solution.class"), 'w', encoding='utf-8') as f: 448 | f.write(submission[1].encode('utf-8').decode('unicode_escape')) 449 | 450 | def close_db(self): 451 | self.conn.close() 452 | if __name__=='__main__': 453 | parser = argparse.ArgumentParser() 454 | parser.add_argument("output", nargs = '?', default="note") 455 | parser.add_argument('-d', '--difficulty', 456 | nargs = '+', 457 | choices = ['Easy', 'Medium', 'Hard'], 458 | help = "Specify the difficulty.\n" 459 | "If not specified, all problems will be grasped.") 460 | 461 | parser.add_argument('-t', '--tags', 462 | nargs = '+', 463 | help = "Specify the tag") 464 | 465 | parser.add_argument('-v', '--verbose', 466 | action = "store_true", 467 | default = False, 468 | help = "Verbose output") 469 | 470 | parser.add_argument('-s', '--status', 471 | nargs='+', 472 | choices=['ac', 'notac', 'none'], 473 | help="Specify the probelms statu.\n" 474 | "If not specified, all problems will be grasped.") 475 | parser.add_argument('-c', '--code', 476 | nargs='+', 477 | help="Code solution output path.") 478 | parser.add_argument('-u', '--username', 479 | nargs='+', 480 | help="username") 481 | parser.add_argument('-p', '--password', 482 | nargs='+', 483 | help="password") 484 | 485 | if len(sys.argv) > 1: 486 | args = parser.parse_args() 487 | else: 488 | parser.print_help() 489 | sys.exit(1) 490 | argsDict = vars(args) 491 | filters = {} 492 | 493 | 494 | test = LeetcodeCrawler() 495 | test.get_csrftoken() 496 | 497 | login_flag = True 498 | 499 | if argsDict.get('code') or argsDict.get('status'): 500 | if not (argsDict.get('username') and argsDict.get('password')): 501 | print("ERROR: choice problem by statu or generate submission code must set username and password!") 502 | sys.exit() 503 | else: 504 | is_login = test.login(args.username[0], args.password[0]) 505 | if not is_login: 506 | print("ERROR: login account fail!") 507 | sys.exit() 508 | if argsDict.get('code'): 509 | filters['code'] = args.code 510 | if args.verbose: 511 | print('Specified code path is: {}'.format(args.code)) 512 | if argsDict.get('status'): 513 | filters['status'] = args.status[0] 514 | if args.verbose: 515 | print('Specified statu is: {}'.format(args.status)) 516 | 517 | 518 | if argsDict.get('difficulty') or argsDict.get('tags'): 519 | if argsDict.get('difficulty'): 520 | filters["difficulty"] = args.difficulty[0] 521 | if args.verbose: 522 | print('Specified difficulty is: {}'.format(args.difficulty)) 523 | if argsDict.get('tags'): 524 | filters['tags'] = args.tags 525 | if args.verbose: 526 | print('Specified tag is: {}'.format(args.tags)) 527 | 528 | 529 | test.connect_db(db_path) 530 | test.get_problems(filters) 531 | if argsDict.get('code'): 532 | test.get_ac_question_submission(filters) 533 | test.generate_questions_submission(args.output, filters) 534 | 535 | test.generate_questions_markdown(args.output, filters) 536 | 537 | test.close_db() 538 | -------------------------------------------------------------------------------- /pictures/文档示例.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcyml/leetcode-crawler/995dce8abba99021495ebf67d84a8bd7e05b5ba4/pictures/文档示例.png -------------------------------------------------------------------------------- /pictures/生成目录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcyml/leetcode-crawler/995dce8abba99021495ebf67d84a8bd7e05b5ba4/pictures/生成目录.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # leetcode-crawler 2 | 3 | ## 概述 4 | 5 | 爬取 LeetCode 题目及提交的 AC 代码的工具,存入到本地 Sqlite 数据库中,并支持生成相应的 README.md 文件。支持爬取指定状态、难度以及标签的题目以及 AC 代码。 6 | 7 | ## 运行环境 8 | 9 | 基于 Python3 运行,依赖 Python 库: 10 | 11 | * requests 12 | * requests_toolbelt 13 | * html2text 14 | 15 | ## 使用说明 16 | 17 | ``` 18 | positional arguments: 19 | output 20 | 21 | optional arguments: 22 | -h, --help show this help message and exit 23 | -d {Easy,Medium,Hard} [{Easy,Medium,Hard} ...], --difficulty {Easy,Medium,Hard} [{Easy,Medium,Hard} ...] 24 | Specify the difficulty. If not specified, all problems 25 | will be grasped. 26 | -t TAGS [TAGS ...], --tags TAGS [TAGS ...] 27 | Specify the tag 28 | -v, --verbose Verbose output 29 | -s {ac,notac,none} [{ac,notac,none} ...], --status {ac,notac,none} [{ac,notac,none} ...] 30 | Specify the probelms statu. If not specified, all 31 | problems will be grasped. 32 | -c CODE [CODE ...], --code CODE [CODE ...] 33 | Code solution output path. 34 | -u USERNAME [USERNAME ...], --username USERNAME [USERNAME ...] 35 | username 36 | -p PASSWORD [PASSWORD ...], --password PASSWORD [PASSWORD ...] 37 | password 38 | ``` 39 | 40 | 可选参数说明 41 | 42 | | Name | Full Name | Type | Description | 43 | | ---- | ---- | ---- | ---- | 44 | | d | difficulty | str | 难度:Easy,Medium,Hard | 45 | | t | tags | str | 题目标签 | 46 | | s | status | str | 题目状态, ac: 通过,notac:未通过,none:未尝试 | 47 | | c | code | str | 生成AC代码文件存入路径 | 48 | | u | username | str | Leetcode 账号名 | 49 | | p | password | str | Leetcode 密码 | 50 | | v | password | bool | 是否输出配置,默认不输出 | 51 | 52 | **注:若指定了题目状态或者要爬取 AC 代码,必须要提供用户账号密码信息参数** 53 | 54 | ## 示例 55 | 56 | 爬取简单难度的题目,并输出配置,生成 README.md 到 output 目录中 57 | 58 | ``` shell 59 | python3 leetcode-crawler.py output -d Easy -v 60 | ``` 61 | 62 | 爬取标签为 “Hash Table”,难度为简单的题目,生成文档到 output 目录中 63 | 64 | ``` shell 65 | python3 leetcode-crawler.py output -t "Hash Table" -d Easy 66 | ``` 67 | 68 | 爬取所有 AC 的题目,生成文档到 output 目录中,生成代码文件到 code 目录中 69 | 70 | ``` shell 71 | python3 leetcode-crawler.py output -s ac -c code -u username -p password 72 | ``` 73 | 74 | ## 效果 75 | 76 | 生成的 README.md 模版示例,由于我是用于刷题总结,生成的文档结合了题目以及 AC 代码。如果有其他需要,可修改源代码改成自己想要的格式 77 | 78 | ![文档示例](./pictures/文档示例.png) 79 | 80 | 输出文件目录,每道题以"编号+题目"的形式来命名文件 81 | 82 | ![生成目录](./pictures/生成目录.png) 83 | 84 | ## TODO 85 | 86 | * 生成代码文件目前只支持生成 class 文件 87 | --------------------------------------------------------------------------------