A lightweight async Python tool for querying Online Judge (OJ) statistics across multiple platforms. Track your accepted problems and total submissions from competitive programming sites.
- Async/concurrent requests via
aiohttp - CLI and web interface
- BSD-2 Licensed
Install once, use anywhere (pipx, uv tool, or pip):
pipx install ojhunt
# or: uv tool install ojhunt
# or: pip install ojhuntRun directly from a clone (no install needed):
git clone https://github.com/Liu233w/ojhunt-lite
cd ojhunt-lite
uv run ojhunt tourist@codeforcesRun via container (no Python needed):
docker run --rm ghcr.io/liu233w/ojhunt-lite tourist@codeforcesExample output:
$ ojhunt tourist@codeforces tourist@atcoder
Querying CodeForces...
Querying AtCoder...
AtCoder done (1051 solved, 1.25s)
CodeForces done (2962 solved, 2.78s)
Total: 2962 solved / 6437 submissions
┏━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Crawler ┃ Username ┃ Solved ┃ Submissions ┃ Status ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ CodeForces │ tourist │ 2962 │ 5386 │ OK (2.78s) │
│ AtCoder │ tourist │ 1051 │ 1051 │ OK (1.25s) │
└────────────┴──────────┴────────┴─────────────┴────────────┘
Completed: 2 OK, 0 failed (2.78s total)Full CLI reference, login-required crawlers, and JSON output: docs/cli.md
The web interface is designed to be self-hosted. Clone the repo and deploy:
git clone https://github.com/Liu233w/ojhunt-lite
cd ojhunt-lite
uv sync
uv run fastapi run src/ojhunt/web/app.py --port 8080Container images are available at ghcr.io/liu233w/ojhunt-lite — see docs/web.md.
Add ojhunt as a project dependency:
uv add ojhunt
# or: pip install ojhuntSync (simplest):
from ojhunt.crawlers.codeforces import query
from ojhunt.crawlers import query_syncresult = query_sync(query, "tourist")
print(result.solved, result.submissions, result.solved_list)Async (when you already have an event loop):
import asyncio, aiohttp
from ojhunt.crawlers.codeforces import query
from ojhunt.crawlers import CrawlerResultasync def main():
async with aiohttp.ClientSession() as session:
result = CrawlerResult.from_dict(await query(session, "tourist"))
print(result.solved, result.submissions, result.solved_list)
asyncio.run(main())query_sync and CrawlerResult work with any crawler in ojhunt.crawlers.*.
Some crawlers (nit, uva) use a persistent label cache and require the full package — they cannot be used as standalone copied files.
See the src/ojhunt/crawlers/ directory. Archived crawlers (dead sites) are in archived_crawlers/.
Adding crawlers, running tests, templates: docs/development.md
BSD 2-Clause License — see individual crawler files for full license text.
Lightweight Python rewrite of OJHunt (acm-statistics), originally inspired by 西北工业大学ACM查询系统 (npuacm.info) by Jiduo Zhang.
Special thanks to test account providers: @leoloveacm, @2013300262
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!