├── .gitignore ├── Pipfile ├── Pipfile.lock ├── Procfile ├── Procfile.windows ├── README.md ├── app ├── __init__.py ├── app.cfg ├── controllers │ ├── __init__.py │ └── controller.py ├── models │ ├── __init__.py │ ├── cache.py │ ├── db_mongo.py │ └── leetcode_user.py ├── scanner.py ├── static │ ├── ads.txt │ ├── favicon.ico │ └── recap_tech_65.png └── templates │ └── index.html ├── docker-compose.yml ├── requirements.txt └── run.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | .idea 91 | .vscode/ 92 | test_crawl.py 93 | 94 | .history/ -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | 5 | [packages] 6 | gunicorn = "*" 7 | requests = "*" 8 | flask = "*" 9 | pymongo = "*" 10 | flask-paginate = "*" 11 | flask-compress = "*" 12 | flask-caching = "*" 13 | scout-apm = "*" 14 | 15 | [requires] 16 | python_version = "3.7" 17 | 18 | [dev-packages] 19 | pylint = "*" 20 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "1af1379a55ea6dc3d0a3712667022bf4857c2e13e009cb261ee04f6040a276cd" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "url": "https://pypi.python.org/simple", 13 | "verify_ssl": true 14 | } 15 | ] 16 | }, 17 | "default": { 18 | "asgiref": { 19 | "hashes": [ 20 | "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", 21 | "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" 22 | ], 23 | "markers": "python_version >= '3.5'", 24 | "version": "==3.3.1" 25 | }, 26 | "brotli": { 27 | "hashes": [ 28 | "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8", 29 | "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b", 30 | "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c", 31 | "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70", 32 | "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f", 33 | "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429", 34 | "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126", 35 | "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4", 36 | "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438", 37 | "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f", 38 | "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389", 39 | "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6", 40 | "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26", 41 | "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7", 42 | "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14", 43 | "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430", 44 | "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296", 45 | "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12", 46 | "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452", 47 | "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea", 48 | "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a", 49 | "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5", 50 | "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d", 51 | "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa", 52 | "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb", 53 | "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b", 54 | "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4", 55 | "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7", 56 | "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1", 57 | "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1" 58 | ], 59 | "version": "==1.0.9" 60 | }, 61 | "certifi": { 62 | "hashes": [ 63 | "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", 64 | "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" 65 | ], 66 | "version": "==2020.12.5" 67 | }, 68 | "cffi": { 69 | "hashes": [ 70 | "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e", 71 | "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d", 72 | "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a", 73 | "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec", 74 | "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362", 75 | "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668", 76 | "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c", 77 | "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b", 78 | "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06", 79 | "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698", 80 | "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2", 81 | "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c", 82 | "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7", 83 | "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009", 84 | "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03", 85 | "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b", 86 | "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909", 87 | "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53", 88 | "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35", 89 | "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26", 90 | "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b", 91 | "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01", 92 | "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb", 93 | "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293", 94 | "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd", 95 | "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d", 96 | "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3", 97 | "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d", 98 | "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e", 99 | "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca", 100 | "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d", 101 | "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775", 102 | "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375", 103 | "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b", 104 | "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b", 105 | "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f" 106 | ], 107 | "version": "==1.14.4" 108 | }, 109 | "chardet": { 110 | "hashes": [ 111 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 112 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 113 | ], 114 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 115 | "version": "==4.0.0" 116 | }, 117 | "click": { 118 | "hashes": [ 119 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 120 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 121 | ], 122 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 123 | "version": "==7.1.2" 124 | }, 125 | "cryptography": { 126 | "hashes": [ 127 | "sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d", 128 | "sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7", 129 | "sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901", 130 | "sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c", 131 | "sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244", 132 | "sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6", 133 | "sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5", 134 | "sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e", 135 | "sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c", 136 | "sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0", 137 | "sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812", 138 | "sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a", 139 | "sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030", 140 | "sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302" 141 | ], 142 | "version": "==3.3.1" 143 | }, 144 | "flask": { 145 | "hashes": [ 146 | "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", 147 | "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" 148 | ], 149 | "version": "==1.1.2" 150 | }, 151 | "flask-caching": { 152 | "hashes": [ 153 | "sha256:a0356ad868b1d8ec2d0e675a6fe891c41303128f8904d5d79e180d8b3f952aff", 154 | "sha256:e6ef2e2af84e13c4fd32c1839c1943a42f11b6b0fbcfdd6bf46547ea5482dbfe" 155 | ], 156 | "version": "==1.9.0" 157 | }, 158 | "flask-compress": { 159 | "hashes": [ 160 | "sha256:c132590e7c948877a96d675c13cbfa64edec0faafa2381678dea6f36aa49a552", 161 | "sha256:fda1b8a834176c485c7018a4b9326599311afbaaa08a715771c1fd9021f871fa" 162 | ], 163 | "version": "==1.8.0" 164 | }, 165 | "flask-paginate": { 166 | "hashes": [ 167 | "sha256:949b93d0535d1223b91ac0048586bd878aaebf4044c54c1dc3068acc9bdf441f", 168 | "sha256:f69434e9a8d85e50bd248c52bd8b22b0ca0284edef45d3c51b3ab5e4f703b733" 169 | ], 170 | "version": "==0.7.1" 171 | }, 172 | "gunicorn": { 173 | "hashes": [ 174 | "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", 175 | "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" 176 | ], 177 | "version": "==20.0.4" 178 | }, 179 | "idna": { 180 | "hashes": [ 181 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 182 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 183 | ], 184 | "version": "==2.10" 185 | }, 186 | "itsdangerous": { 187 | "hashes": [ 188 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 189 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 190 | ], 191 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 192 | "version": "==1.1.0" 193 | }, 194 | "jinja2": { 195 | "hashes": [ 196 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", 197 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" 198 | ], 199 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 200 | "version": "==2.11.2" 201 | }, 202 | "markupsafe": { 203 | "hashes": [ 204 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 205 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 206 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 207 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 208 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 209 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 210 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 211 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 212 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 213 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 214 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 215 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 216 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 217 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 218 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 219 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 220 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 221 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 222 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 223 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 224 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 225 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 226 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 227 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 228 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 229 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 230 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 231 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 232 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 233 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 234 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 235 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 236 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" 237 | ], 238 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 239 | "version": "==1.1.1" 240 | }, 241 | "psutil": { 242 | "hashes": [ 243 | "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64", 244 | "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131", 245 | "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c", 246 | "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6", 247 | "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023", 248 | "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df", 249 | "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394", 250 | "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4", 251 | "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b", 252 | "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2", 253 | "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d", 254 | "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65", 255 | "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d", 256 | "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef", 257 | "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7", 258 | "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60", 259 | "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6", 260 | "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8", 261 | "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b", 262 | "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d", 263 | "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac", 264 | "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935", 265 | "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d", 266 | "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28", 267 | "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876", 268 | "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0", 269 | "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3", 270 | "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563" 271 | ], 272 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 273 | "version": "==5.8.0" 274 | }, 275 | "pycparser": { 276 | "hashes": [ 277 | "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", 278 | "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" 279 | ], 280 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 281 | "version": "==2.20" 282 | }, 283 | "pymongo": { 284 | "hashes": [ 285 | "sha256:019ddf7ced8e42cc6c8c608927c799be8097237596c94ffe551f6ef70e55237e", 286 | "sha256:047c325c4a96e7be7d11acf58639bcf71a81ca212d9c6590e3369bc28678647a", 287 | "sha256:047cc2007b280672ddfdf2e7b862aad8d898f481f65bbc9067bfa4e420a019a9", 288 | "sha256:061d59f525831c4051af0b6dbafa62b0b8b168d4ef5b6e3c46d0811b8499d100", 289 | "sha256:082832a59da18efab4d9148cca396451bac99da9757f31767f706e828b5b8500", 290 | "sha256:0a53a751d977ad02f1bd22ddb6288bb4816c4758f44a50225462aeeae9cbf6a0", 291 | "sha256:1222025db539641071a1b67f6950f65a6342a39db5b454bf306abd6954f1ad8a", 292 | "sha256:1580fad512c678b720784e5c9018621b1b3bd37fb5b1633e874738862d6435c7", 293 | "sha256:202ea1d4edc8a5439fc179802d807b49e7e563207fea5610779e56674ac770c6", 294 | "sha256:21d7b48567a1c80f9266e0ab61c1218a31279d911da345679188733e354f81cc", 295 | "sha256:264843ce2af0640994a4331148ef5312989bc004678c457460758766c9b4decc", 296 | "sha256:270a1f6a331eac3a393090af06df68297cb31a8b2df0bdcbd97dc613c5758e78", 297 | "sha256:29a6840c2ac778010547cad5870f3db2e080ad7fad01197b07fff993c08692c8", 298 | "sha256:3646c2286d889618d43e01d9810ac1fc17709d2b4dec61366df5edc8ba228b3e", 299 | "sha256:36b9b98a39565a8f33803c81569442b35e749a72fb1aa7d0bcdb1a33052f8bcc", 300 | "sha256:3ec8f8e106a1476659d8c020228b45614daabdbdb6c6454a843a1d4f77d13339", 301 | "sha256:422069f2cebf58c9dd9e8040b4768f7be4f228c95bc4505e8fa8e7b4f7191ad8", 302 | "sha256:44376a657717de8847d5d71a9305f3595c7e78c91ac77edbb87058d12ede87a6", 303 | "sha256:45728e6aae3023afb5b2829586d1d2bfd9f0d71cfd7d3c924b71a5e9aef617a8", 304 | "sha256:46792b71ab802d9caf1fc9d52e83399ef8e1a36e91eef4d827c06e36b8df2230", 305 | "sha256:4942a5659ae927bb764a123a6409870ca5dd572d83b3bfb71412c9a191bbf792", 306 | "sha256:4be4fe9d18523da98deeb0b554ac76e1dc1562ee879d62572b34dda8593efcc1", 307 | "sha256:523804bd8fcb5255508052b50073a27c701b90a73ea46e29be46dad5fe01bde6", 308 | "sha256:540dafd6f4a0590fc966465c726b80fa7c0804490c39786ef29236fe68c94401", 309 | "sha256:5980509801cbd2942df31714d055d89863684b4de26829c349362e610a48694e", 310 | "sha256:5ad7b96c27acd7e256b33f47cf3d23bd7dd902f9c033ae43f32ffcbc37bebafd", 311 | "sha256:6122470dfa61d4909b75c98012c1577404ba4ab860d0095e0c6980560cb3711f", 312 | "sha256:6175fd105da74a09adb38f93be96e1f64873294c906e5e722cbbc5bd10c44e3b", 313 | "sha256:646d4d30c5aa7c0ddbfe9b990f0f77a88621024a21ad0b792bd9d58caa9611f0", 314 | "sha256:6700e251c6396cc05d7460dc05ef8e19e60a7b53b62c007725b48e123aaa2b1c", 315 | "sha256:6aac7e0e8de92f11a410eb68c24a2decbac6f094e82fd95d22546d0168e7a18b", 316 | "sha256:6e7a6057481a644970e43475292e1c0af095ca39a20fe83781196bd6e6690a38", 317 | "sha256:76579fcf77052b39796fe4a11818d1289dd48cffe15951b3403288fa163c29f6", 318 | "sha256:7e69fa025a1db189443428f345fea5555d16413df6addc056e17bb8c9794b006", 319 | "sha256:7f0c507e1f108790840d6c4b594019ebf595025c324c9f7e9c9b2b15b41f884e", 320 | "sha256:813db97e9955b6b1b50b5cebd18cb148580603bb9b067ea4c5cc656b333bc906", 321 | "sha256:82d5ded5834b6c92380847860eb28dcaf20b847a27cee5811c4aaceef87fd280", 322 | "sha256:82f6e42ba40440a7e0a20bfe12465a3b62d65966a4c7ad1a21b36ffff88de6fe", 323 | "sha256:8d669c720891781e7c82d412cad39f9730ef277e3957b48a3344dae47d3caa03", 324 | "sha256:944ed467feb949e103555863fa934fb84216a096b0004ca364d3ddf9d18e2b9e", 325 | "sha256:96c6aef7ffb0d37206c0342abb82d874fa8cdc344267277ec63f562b94335c22", 326 | "sha256:9be785bd4e1ba0148fb00ca84e4dbfbd1c74df3af3a648559adc60b0782f34de", 327 | "sha256:9d19843568df9d263dc92ae4cc2279879add8a26996473f9155590cac635b321", 328 | "sha256:a118a1df7280ffab7fe0f3eab325868339ff1c4d5b8e0750db0f0a796da8f849", 329 | "sha256:b4294ddf76452459433ecfa6a93258608b5e462c76ef15e4695ed5e2762f009f", 330 | "sha256:b50af6701b4a5288b77fb4db44a363aa9485caf2c3e7a40c0373fd45e34440af", 331 | "sha256:b875bb4b438931dce550e170bfb558597189b8d0160f4ac60f14a21955161699", 332 | "sha256:b95d2c2829b5956bf54d9a22ffec911dea75abf0f0f7e0a8a57423434bfbde91", 333 | "sha256:c046e09e886f4539f8626afba17fa8f2e6552731f9384e2827154e3e3b7fda4e", 334 | "sha256:c1d1992bbdf363b22b5a9543ab7d7c6f27a1498826d50d91319b803ddcf1142e", 335 | "sha256:c2b67881392a9e85aa108e75f62cdbe372d5a3f17ea5f8d3436dcf4662052f14", 336 | "sha256:c6cf288c9e03195d8e12b72a6388b32f18a5e9c2545622417a963e428e1fe496", 337 | "sha256:c812b6e53344e92f10f12235219fb769c491a4a87a02c9c3f93fe632e493bda8", 338 | "sha256:cc421babc687dc52ce0fc19787b2404518ca749d9db59576100946ff886f38ed", 339 | "sha256:ce53c00be204ec4428d3c1f3c478ae89d388efec575544c27f57b61e9fa4a7f2", 340 | "sha256:ce9964c117cbe5cf6269f30a2b334d28675956e988b7dbd0b4f7370924afda2e", 341 | "sha256:d6f82e86896a8db70e8ae8fa4b7556a0f188f1d8a6c53b2ba229889d55a59308", 342 | "sha256:d9d3ae537f61011191b2fd6f8527b9f9f8a848b37d4c85a0f7bb28004c42b546", 343 | "sha256:e565d1e4388765c135052717f15f9e0314f9d172062444c6b3fc0002e93ed04b", 344 | "sha256:ed98683d8f01f1c46ef2d02469e04e9a8fe9a73a9741a4e6e66677a73b59bec8", 345 | "sha256:ef18aa15b1aa18c42933deed5233b3284186e9ed85c25d2704ceff5099a3964c", 346 | "sha256:fa741e9c805567239f845c7e9a016aff797f9bb02ff9bc8ccd2fbd9eafefedd4", 347 | "sha256:fc4946acb6cdada08f60aca103b61334995523da65be5fe816ea8571c9967d46", 348 | "sha256:fcc66d17a3363b7bd6d2655de8706e25a3cd1be2bd1b8e8d8a5c504a6ef893ae" 349 | ], 350 | "version": "==3.11.2" 351 | }, 352 | "pyopenssl": { 353 | "hashes": [ 354 | "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51", 355 | "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b" 356 | ], 357 | "version": "==20.0.1" 358 | }, 359 | "requests": { 360 | "hashes": [ 361 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 362 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 363 | ], 364 | "version": "==2.25.1" 365 | }, 366 | "scout-apm": { 367 | "hashes": [ 368 | "sha256:1247ac71ab797956ff3bb64af056168de76b9fc0b086bd492cb2b5cf8cd436e8", 369 | "sha256:17627aa62065fa2ef92e66cb81d1bdfccaa695363b324ebeb63edd6be9dd500e", 370 | "sha256:1b69c7a1355f8dc07fa32a2d93de00ec8a8bc1b4006d631838ea5030676f46fa", 371 | "sha256:23ecc92d852c4c7adc7340701f8787ed119b06ed072e610f0324ae904e032b77", 372 | "sha256:2444802b43b1191666978787b435e804b66850046614419c835d242a97a909dd", 373 | "sha256:2679c61ce19f436fbf1d6baf805fec1cc185175dba06598a5d82b8e196bf5fe6", 374 | "sha256:299967150693911105d1de067b3ff7c179437457aa565db81c3f9cb32026b7d7", 375 | "sha256:381c65e04b97581d70d9a5d405dd5cb95c3c3cc6908450f72ebde84b138ceffb", 376 | "sha256:46d5574b357aab2bf11ef0cc20481a2d4cc0df8a292ed2ee76304e4b90391033", 377 | "sha256:553e1897ce4d5e180a2257abf5d9964d7d7d62a6320e63dae4cc8bb12802bc6b", 378 | "sha256:56fb2da6d070f06717870d8bd5cd0a97e47988fe9a960ceb7211602f3b233016", 379 | "sha256:64f1ee0633a7ac6c99e11afeaac4ebf950ceca5542780975960a7f1f535cd246", 380 | "sha256:6b91c5649ba6e141666279abf225b0623ae31e44e52b079aed9e6790b09c4c33", 381 | "sha256:70e6438c868bf5e013ffacba066518cd678ed44ee4ff0aa5917358b7b8ba1bf1", 382 | "sha256:8966b16667c13466e246ab8ef0b2620464bd7707eb49d4d07609aaf943900fba", 383 | "sha256:9cbf8f207e057d6e5e857b03087e596d18aca927b080c8b2b4993cc62e343423", 384 | "sha256:b89b9e68710290db7d25dc00402fb1d3d28bb7fc0f1dbd3c8e21d61d7a2ff992", 385 | "sha256:c246d9e751a91725a2fecc5e6bfa48c1c1fb17e3667d0668d124158525db0865", 386 | "sha256:da3e1f3d991caf2a5e6a06a3e07099d62c4496613ae397420690adaa368597c2" 387 | ], 388 | "version": "==2.17.0" 389 | }, 390 | "six": { 391 | "hashes": [ 392 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 393 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 394 | ], 395 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 396 | "version": "==1.15.0" 397 | }, 398 | "urllib3": { 399 | "extras": [ 400 | "secure" 401 | ], 402 | "hashes": [ 403 | "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", 404 | "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" 405 | ], 406 | "markers": "python_version >= '3.5'", 407 | "version": "==1.26.2" 408 | }, 409 | "werkzeug": { 410 | "hashes": [ 411 | "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", 412 | "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" 413 | ], 414 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 415 | "version": "==1.0.1" 416 | }, 417 | "wrapt": { 418 | "hashes": [ 419 | "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" 420 | ], 421 | "version": "==1.12.1" 422 | } 423 | }, 424 | "develop": { 425 | "astroid": { 426 | "hashes": [ 427 | "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703", 428 | "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386" 429 | ], 430 | "markers": "python_version >= '3.5'", 431 | "version": "==2.4.2" 432 | }, 433 | "isort": { 434 | "hashes": [ 435 | "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7", 436 | "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58" 437 | ], 438 | "markers": "python_version >= '3.6' and python_version < '4.0'", 439 | "version": "==5.6.4" 440 | }, 441 | "lazy-object-proxy": { 442 | "hashes": [ 443 | "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", 444 | "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", 445 | "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", 446 | "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", 447 | "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", 448 | "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", 449 | "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", 450 | "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", 451 | "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", 452 | "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", 453 | "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", 454 | "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", 455 | "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", 456 | "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", 457 | "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", 458 | "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", 459 | "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", 460 | "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", 461 | "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", 462 | "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", 463 | "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" 464 | ], 465 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 466 | "version": "==1.4.3" 467 | }, 468 | "mccabe": { 469 | "hashes": [ 470 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 471 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 472 | ], 473 | "version": "==0.6.1" 474 | }, 475 | "pylint": { 476 | "hashes": [ 477 | "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210", 478 | "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f" 479 | ], 480 | "version": "==2.6.0" 481 | }, 482 | "six": { 483 | "hashes": [ 484 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 485 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 486 | ], 487 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 488 | "version": "==1.15.0" 489 | }, 490 | "toml": { 491 | "hashes": [ 492 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 493 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 494 | ], 495 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 496 | "version": "==0.10.2" 497 | }, 498 | "wrapt": { 499 | "hashes": [ 500 | "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" 501 | ], 502 | "version": "==1.12.1" 503 | } 504 | } 505 | } 506 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn run:app -w 1 -b 0.0.0.0:$PORT --timeout 300 --log-level debug -------------------------------------------------------------------------------- /Procfile.windows: -------------------------------------------------------------------------------- 1 | web: python manage.py runserver 0.0.0.0:5000 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeetCode Ranking 2 | Crawling LeetCode Global Ranking and building a website to support to find the LeetCode ranking by country and username. 3 | 4 | ## Getting Started 5 | Because LeetCode does not support to filter the ranking by country, so I've written some Python scripts to crawl global ranking data in LeetCode. It helps to search, filter by user name or countries. 6 | 7 | Try to find your place and others in your country at: https://leetcode-country-ranking.onrender.com/ 8 | 9 | ![Screenshot](https://i.ibb.co/RBd6z6x/Screen-Shot-2019-07-14-at-22-29-29.png) 10 | 11 | I'm not good at frontend web development, so it would be great if you could help me to improve. 12 | 13 | ### Development at Local 14 | In case, you want to build your own website. Run these steps at your local machine: 15 | ``` 16 | #Step 1: Run docker for the environment 17 | docker-compose up -d 18 | 19 | #Step 2: Download dependencies 20 | pip3 install -r requirements.txt 21 | 22 | #Step 3: Run at local 23 | flask run 24 | 25 | #Step 4: Test at http://localhost:5000/ 26 | ``` 27 | ### TODO 28 | - Crawling LeetCode weekly contests 29 | 30 | ### Jan 2023 31 | Since Heroku stopped free dynos, I use [render](https://render.com/) instead. 32 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | from flask import Flask 3 | from flask_compress import Compress 4 | from app.models.cache import cache 5 | from app.controllers.controller import app_ctr 6 | from app import scanner 7 | 8 | COMPRESS_MIMETYPES = ['text/html', 'text/css', 'text/xml', 'application/json', 'application/javascript'] 9 | COMPRESS_LEVEL = 6 10 | COMPRESS_MIN_SIZE = 500 11 | 12 | app = Flask(__name__, static_folder='static', static_url_path='') 13 | app.config.from_pyfile('app.cfg') 14 | cache.init_app(app) 15 | app.register_blueprint(app_ctr) 16 | Compress(app) 17 | 18 | scanner.run() 19 | -------------------------------------------------------------------------------- /app/app.cfg: -------------------------------------------------------------------------------- 1 | PER_PAGE = 30 -------------------------------------------------------------------------------- /app/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintutu/leetcode-country-ranking/a79be0a667a7d71c5d26095febe6beb55c2d7df8/app/controllers/__init__.py -------------------------------------------------------------------------------- /app/controllers/controller.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, render_template, send_from_directory, request, redirect 2 | from flask_paginate import Pagination, get_page_args 3 | from app.models import db_mongo 4 | import os 5 | from app.models.cache import cache 6 | 7 | app_ctr = Blueprint('app_ctr', __name__) 8 | 9 | 10 | @app_ctr.route('/') 11 | def index(): 12 | last_updated_format = cache.get('last_updated_format') 13 | page, per_page, offset = get_page_args(page_parameter='page', per_page_parameter='per_page') 14 | total = db_mongo.get_total_users_count() 15 | pagination_users = db_mongo.get_users(per_page, offset) 16 | pagination = Pagination(page=page, per_page=per_page, total=total, 17 | css_framework='bootstrap4') 18 | return render_template('index.html', users=pagination_users, page=page, per_page=per_page, pagination=pagination, 19 | last_updated=last_updated_format) 20 | 21 | 22 | @app_ctr.route('/country/') 23 | def search_by_country(country_name): 24 | last_updated_format = cache.get('last_updated_format') 25 | page, per_page, offset = get_page_args(page_parameter='page', per_page_parameter='per_page') 26 | total = db_mongo.get_total_users_count_by_country(country_name) 27 | pagination_users = db_mongo.get_users_by_country(country_name, per_page, offset) 28 | pagination = Pagination(page=page, per_page=per_page, total=total, 29 | css_framework='bootstrap4') 30 | return render_template('index.html', users=pagination_users, page=page, per_page=per_page, pagination=pagination, 31 | last_updated=last_updated_format) 32 | 33 | 34 | @app_ctr.route('/user', methods=['POST']) 35 | def search_by_user(): 36 | last_updated_format = cache.get('last_updated_format') 37 | user_name = request.form.get("user-search") 38 | if not user_name: 39 | return redirect('/') 40 | page, per_page, offset = get_page_args(page_parameter='page', per_page_parameter='per_page') 41 | pagination_users = db_mongo.get_users_by_name(user_name, per_page, offset) 42 | total = len(pagination_users) 43 | pagination = Pagination(page=page, per_page=per_page, total=total, 44 | css_framework='bootstrap4') 45 | return render_template('index.html', users=pagination_users, page=page, per_page=per_page, pagination=pagination, 46 | last_updated=last_updated_format) 47 | 48 | 49 | @app_ctr.context_processor 50 | def my_utility_processor(): 51 | def format_rate(rate): 52 | return format(float(rate), '.2f') 53 | return dict(format_rate=format_rate) -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintutu/leetcode-country-ranking/a79be0a667a7d71c5d26095febe6beb55c2d7df8/app/models/__init__.py -------------------------------------------------------------------------------- /app/models/cache.py: -------------------------------------------------------------------------------- 1 | from flask_caching import Cache 2 | 3 | cache = Cache(config={"CACHE_TYPE": "simple"}) 4 | -------------------------------------------------------------------------------- /app/models/db_mongo.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pymongo 3 | import datetime 4 | import os 5 | from app.models.leetcode_user import User 6 | 7 | mongo_url = os.getenv("MONGODB_URI2", "mongodb://root:example@localhost:27017/") 8 | mongo_db_name = os.getenv("MONGODB_NAME2", "LEET_CODE") 9 | 10 | myclient = pymongo.MongoClient(mongo_url, connect=False) 11 | mydb = myclient[mongo_db_name] 12 | 13 | def get_mongo_client(): 14 | client = pymongo.MongoClient(mongo_url, connect=False) 15 | return client 16 | 17 | def get_mongo_db(client): 18 | return client[mongo_db_name] 19 | 20 | def rebuild_indexes(mongo_db): 21 | try: 22 | users_collection = mongo_db.users 23 | user_indexes = ['country_code', 'user_name', 'real_name'] 24 | existed_indexes = list(map(lambda x: x['name'], users_collection.list_indexes())) 25 | # Don't use reindex: 26 | # Starting in MongoDB 4.6, the reIndex command can only be run when connected to a standalone mongod. 27 | # 28 | # if len(existed_indexes) > len(indexes): 29 | # users_collection.reindex() 30 | for index in existed_indexes: 31 | if index == "_id_": 32 | continue 33 | users_collection.drop_index(index) 34 | 35 | for index in user_indexes: 36 | users_collection.create_index(index) 37 | except Exception as e: 38 | logging.error(e, exc_info=True) 39 | 40 | 41 | def insert_users(mongo_db, users): 42 | try: 43 | users_collection = mongo_db.users 44 | data = [] 45 | for user in users: 46 | row = {"global_ranking": user.global_ranking, "ranking": user.rating, "user_name": user.user_name, 47 | "real_name": user.real_name, "country_code": user.country_code, "country_name": user.country_name, 48 | "page": user.page, "data_region": user.data_region} 49 | data.append(row) 50 | return users_collection.insert_many(data) 51 | except Exception as e: 52 | logging.error(e, exc_info=True) 53 | 54 | 55 | def delete_users_by_page(mongo_db, page): 56 | try: 57 | return mongo_db.users.remove({"page": page}) 58 | except Exception as e: 59 | logging.error(e, exc_info=True) 60 | 61 | 62 | def get_users(page_size, page_num): 63 | res = [] 64 | try: 65 | cursor = mydb.users.find().sort("global_ranking").skip(page_num).limit(page_size) 66 | users = [x for x in cursor] 67 | for user in users: 68 | res.append(User(user['global_ranking'], user['ranking'], user['user_name'], user['real_name'], 69 | user['country_code'], user['country_name'], user['page'], user['data_region'])) 70 | except Exception as e: 71 | logging.error(e, exc_info=True) 72 | finally: 73 | return res 74 | 75 | 76 | def get_users_by_country(country_code, page_size, page_num): 77 | res = [] 78 | try: 79 | cursor = mydb.users.find({"country_code": country_code}).sort("global_ranking").skip(page_num).limit(page_size) 80 | users = [x for x in cursor] 81 | for user in users: 82 | res.append(User(user['global_ranking'], user['ranking'], user['user_name'], user['real_name'], 83 | user['country_code'], user['country_name'], user['page'], user['data_region'])) 84 | except Exception as e: 85 | logging.error(e, exc_info=True) 86 | finally: 87 | return res 88 | 89 | 90 | def get_users_by_name(user_name, page_size, page_num): 91 | res = [] 92 | try: 93 | query = {"$or": [{"user_name": {"$regex": user_name}}, {"real_name": {"$regex": user_name}}]} 94 | cursor = mydb.users.find(query).sort("global_ranking").skip(page_num).limit(page_size) 95 | users = [x for x in cursor] 96 | for user in users: 97 | res.append(User(user['global_ranking'], user['ranking'], user['user_name'], user['real_name'], 98 | user['country_code'], user['country_name'], user['page'], user['data_region'])) 99 | except Exception as e: 100 | logging.error(e, exc_info=True) 101 | finally: 102 | return res 103 | 104 | 105 | def get_total_users_count_by_country(country_code): 106 | try: 107 | total = mydb.users.find({"country_code": country_code}).count() 108 | return total 109 | except Exception as e: 110 | logging.error(e, exc_info=True) 111 | 112 | 113 | def get_total_users_count(): 114 | try: 115 | total = mydb.users.count() 116 | return total 117 | except Exception as e: 118 | logging.error(e, exc_info=True) 119 | 120 | 121 | def store_last_update(mongo_db, curr_page): 122 | try: 123 | date_time_now = datetime.datetime.now() 124 | date_time_now_str = date_time_now.strftime("%d/%m/%Y %H:%M:%S") 125 | mongo_db.updated_user_info.update({"id": 1}, 126 | {"id": 1, "last_update_time": date_time_now_str, "last_page": curr_page}, 127 | upsert=True) 128 | except Exception as e: 129 | logging.error(e, exc_info=True) 130 | 131 | 132 | def get_last_update(mongo_client): 133 | try: 134 | date_time_now = datetime.datetime.now() 135 | date_time_now_str = date_time_now.strftime("%d/%m/%Y %H:%M:%S") 136 | rows = mongo_client.updated_user_info.find({"id": 1}) 137 | for row in rows: 138 | return {"last_update_time": row["last_update_time"], "last_page": row["last_page"]} 139 | return {"last_update_time": date_time_now_str, "last_page": 1} 140 | except Exception as e: 141 | logging.error(e, exc_info=True) 142 | return {"last_update_time": date_time_now_str, "last_page": 1} 143 | -------------------------------------------------------------------------------- /app/models/leetcode_user.py: -------------------------------------------------------------------------------- 1 | class User: 2 | def __init__(self, global_ranking, rating, user_name, real_name, country_code, country_name, page, data_region="US"): 3 | self.global_ranking = global_ranking 4 | self.rating = rating 5 | self.user_name = user_name 6 | self.real_name = real_name 7 | self.country_code = country_code 8 | self.country_name = country_name 9 | self.page = page 10 | self.data_region = data_region 11 | 12 | def __str__(self): 13 | return "Global Ranking: {}\t UserName: {}\t realName: {}\t countryCode: {}\t countryName: {}".format( 14 | self.global_ranking, self.user_name, self.real_name, self.country_code, self.country_name) 15 | -------------------------------------------------------------------------------- /app/scanner.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import threading 3 | import requests 4 | import os 5 | import json 6 | import time 7 | import datetime 8 | from app.models import db_mongo 9 | from app.models.leetcode_user import User 10 | from app.models.cache import cache 11 | 12 | last_updated_format = None 13 | 14 | 15 | def craw_leetcode(page): 16 | users = list() 17 | try: 18 | data = {"query": "{ globalRanking(page: " + str( 19 | page) + ") { totalUsers, userPerPage, myRank { ranking, currentGlobalRanking, currentRating, dataRegion, __typename, }, rankingNodes { ranking, currentRating, currentGlobalRanking, dataRegion, user { username, profile { userSlug, userAvatar, countryCode, countryName, realName, __typename, }, __typename }, __typename, }, __typename }}"} 20 | result = requests.post("https://leetcode.com/graphql", 21 | json=data, 22 | headers={ 23 | "accept": "*/*", 24 | "accept-language": "en-US,en;q=0.9,vi;q=0.8", 25 | "authority": "leetcode.com", 26 | "content-type": "application/json; charset=utf8", 27 | "origin": "https://leetcode.com", 28 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36" 29 | }) 30 | json_result = json.loads(result.text) 31 | rankingNodes = json_result['data']['globalRanking']['rankingNodes'] 32 | for node in rankingNodes: 33 | global_ranking = node["currentGlobalRanking"] 34 | currrent_rating = node["currentRating"] 35 | data_region = node["dataRegion"] 36 | user_profile = node['user']['profile'] 37 | user_name = user_profile['userSlug'] 38 | real_name = user_profile['realName'] 39 | country_code = user_profile['countryCode'] 40 | country_name = user_profile['countryName'] 41 | user = User(global_ranking, currrent_rating, user_name, real_name, country_code, country_name, page, data_region) 42 | users.append(user) 43 | except Exception as e: 44 | logging.error(e, exc_info=True) 45 | finally: 46 | return users 47 | 48 | 49 | def scan_ranking(mongo_db, last_page): 50 | MAX_PAGE = int(os.getenv("MAX_PAGE_LEET_CODE", "15")) 51 | for i in range(last_page, MAX_PAGE): 52 | users = craw_leetcode(i) 53 | if len(users) > 0: 54 | db_mongo.delete_users_by_page(mongo_db, i) 55 | db_mongo.insert_users(mongo_db, users) 56 | db_mongo.store_last_update(mongo_db, i) 57 | print("Inserted to DB page " + str(i)) 58 | time.sleep(3) 59 | 60 | 61 | def start_scan(): 62 | while True: 63 | scanner_enabled = os.getenv("SCANNER_ENABLED", "True") 64 | if scanner_enabled == 'True': 65 | mongo_client = db_mongo.get_mongo_client() 66 | mongo_db = db_mongo.get_mongo_db(mongo_client) 67 | last_updated_info = db_mongo.get_last_update(mongo_db) 68 | last_page = last_updated_info["last_page"] 69 | last_updated_str = last_updated_info["last_update_time"] 70 | last_updated = datetime.datetime.strptime(last_updated_str, "%d/%m/%Y %H:%M:%S") 71 | cache.set('last_updated_format', last_updated.strftime("%B %d, %Y"), timeout=60 * 60 * 24) 72 | 73 | diff_date_time = datetime.datetime.now() - last_updated 74 | if diff_date_time.days >= 1: 75 | last_page = 1 76 | print('Start scan leetcode ranking from page ' + str(last_page)) 77 | scan_ranking(mongo_db, last_page) 78 | cache.set('last_updated_format', datetime.datetime.now().strftime("%B %d, %Y"), timeout=60 * 60 * 24) 79 | print('Finish scan leetcode ranking') 80 | db_mongo.rebuild_indexes(mongo_db) 81 | print('Complete rebuild index') 82 | mongo_client.close() 83 | # Rescan every day 84 | time.sleep(60 * 60 * 24) 85 | 86 | #Try to ping website to avoid instance sleeping 87 | def ping(): 88 | try: 89 | while True: 90 | time.sleep(60 * 15) 91 | requests.get("https://leetcode-country-ranking.onrender.com/") 92 | except Exception as e: 93 | logging.error(e, exc_info=True) 94 | 95 | def run(): 96 | t1 = threading.Thread(target=start_scan) 97 | t1.start() 98 | 99 | t2 = threading.Thread(target=ping) 100 | t2.start() -------------------------------------------------------------------------------- /app/static/ads.txt: -------------------------------------------------------------------------------- 1 | google.com, pub-5213877146531950, DIRECT, f08c47fec0942fa0 -------------------------------------------------------------------------------- /app/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintutu/leetcode-country-ranking/a79be0a667a7d71c5d26095febe6beb55c2d7df8/app/static/favicon.ico -------------------------------------------------------------------------------- /app/static/recap_tech_65.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintutu/leetcode-country-ranking/a79be0a667a7d71c5d26095febe6beb55c2d7df8/app/static/recap_tech_65.png -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LeetCode Ranking 7 | 8 | 9 | 10 | 19 | 20 | 21 | 22 | 23 | 34 | 35 | 36 | 37 |
38 |
39 |
40 |
41 |
42 |

LeetCode Global Ranking

43 | Star Me!!! 44 |
45 |
46 |
47 |
48 | 49 |
50 | 221 |
222 |
223 | 225 |
226 |
227 | {{ pagination.links }} 228 |
229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | {% for user in users %} 242 | 243 | 244 | 245 | 246 | 253 | 254 | 261 | 262 | {% endfor %} 263 | 264 |
#Global RankingRatingUser NameReal NameCountry Name
{{ loop.index + pagination.skip }}{{ user.global_ranking }}{{ format_rate(user.rating)}} 247 | {% if user.data_region == "US" %} 248 | {{ user.user_name }} 249 | {% else %} 250 | {{ user.user_name }} 251 | {% endif %} 252 | {{ user.real_name }} 255 | {% if user.country_name != None %} 256 |
{{ user.country_name }}
257 | {% else %} 258 |
259 | {% endif %} 260 |
265 |
266 | {{ pagination.links }} 267 |
268 | 269 | 279 | 287 | 288 | 289 | 296 |
297 | 298 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Use root/example as user/password credentials 2 | version: '3.1' 3 | 4 | services: 5 | 6 | mongo: 7 | image: mongo 8 | restart: always 9 | ports: 10 | - 27017:27017 11 | environment: 12 | MONGO_INITDB_ROOT_USERNAME: root 13 | MONGO_INITDB_ROOT_PASSWORD: example 14 | 15 | mongo-express: 16 | image: mongo-express 17 | restart: always 18 | ports: 19 | - 8081:8081 20 | environment: 21 | ME_CONFIG_MONGODB_ADMINUSERNAME: root 22 | ME_CONFIG_MONGODB_ADMINPASSWORD: example -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -i https://pypi.python.org/simple 2 | asgiref==3.2.10; python_version >= '3.5' 3 | brotli==1.0.7 4 | certifi==2020.6.20 5 | cffi==1.14.1 6 | chardet==3.0.4 7 | click==7.1.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' 8 | cryptography==3.2 9 | flask-caching==1.9.0 10 | flask-compress==1.5.0 11 | flask-paginate==0.7.0 12 | flask==1.1.2 13 | gunicorn==20.0.4 14 | idna==2.10 15 | itsdangerous==1.1.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 16 | jinja2==2.11.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' 17 | markupsafe==1.1.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 18 | psutil==5.7.2; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' 19 | pycparser==2.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 20 | pymongo==3.11.0 21 | pyopenssl==19.1.0 22 | requests==2.24.0 23 | scout-apm==2.15.2 24 | six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 25 | urllib3[secure]==1.25.10; python_version >= '3.5' 26 | werkzeug==1.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' 27 | wrapt==1.12.1 28 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import os 2 | from app import app 3 | from scout_apm.flask import ScoutApm 4 | 5 | ScoutApm(app) 6 | 7 | # Scout settings 8 | app.config["SCOUT_MONITOR"] = os.getenv("SCOUT_MONITOR", "false") 9 | app.config["SCOUT_KEY"] = os.getenv("SCOUT_KEY", "") 10 | app.config["SCOUT_NAME"] = "leetcode-country-ranking" 11 | 12 | if __name__ == '__main__': 13 | app.run() --------------------------------------------------------------------------------