找回密码
 立即注册
首页 业界区 业界 从登录到 Markdown:用 60 行 Python 批量抓取 Hydro OJ ...

从登录到 Markdown:用 60 行 Python 批量抓取 Hydro OJ 题目

绂染 前天 16:04
作者:jason
日期:2025-07-26
版权:wanwusangzhi 2024-2025
项目地址:https://github.com/wanwusangzhigit/hydro
1. 背景故事

很多人第一次刷题时,都会把题目复制到本地笔记软件里做草稿。
动手复制粘贴几次后,你会立刻意识到:
“公式全丢了,图片全是外链,表格惨不忍睹。”
“主要无法用AI解题”
于是就有了今天这 60 行小脚本:
自动登录 OJ → 拉取指定题目 → 把 KaTeX 公式还原成 LaTeX → 输出 Markdown 文件。
以后写题解、做笔记,再也不用对着网页敲公式了。
2. 整体思路

一句话概括:
“用 requests 做登录,用 BeautifulSoup 找内容,用 html2text 转 Markdown。”
流程图:
  1. ┌--------------┐
  2. │ 用户输入      │
  3. │ BASE_URL      │
  4. │ Problem_ID    │
  5. │ USERNAME/PWD  │
  6. └------┬--------┘
  7.        │
  8. ┌------┴--------┐
  9. │ 1. 拉登录页   │  ← GET /login
  10. │ 取 csrf-token │
  11. └------┬--------┘
  12.        │
  13. ┌------┴--------┐
  14. │ 2. 提交登录   │  ← POST /login
  15. │ 302 跳转即成功│
  16. └------┬--------┘
  17.        │
  18. ┌------┴--------┐
  19. │ 3. 拉题目页   │  ← GET /p/{Problem_ID}
  20. │ 替换 KaTeX    │
  21. └------┬--------┘
  22.        │
  23. ┌------┴--------┐
  24. │ 4. 转 Markdown│  ← html2text
  25. │ 写入 p.md     │
  26. └--------------┘
复制代码
3. 关键代码拆解

3.1 会话与伪装 UA
  1. s = requests.Session()
  2. s.headers.update({
  3.     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
  4.                   'AppleWebKit/537.36 (KHTML, like Gecko) '
  5.                   'Chrome/120.0.0.0 Safari/537.36'
  6. })
复制代码

  • 使用 Session 可自动携带后续 Cookie,不用手动维护。
  • UA 伪装成桌面 Chrome,避免服务器直接拒绝“非浏览器”请求。
3.2 动态获取 CSRF-token
  1. login_html = s.get(LOGIN_URL).text
  2. soup = BeautifulSoup(login_html, 'lxml')
  3. csrf = (soup.find('meta', attrs={'name': 'csrf-token'}) or
  4.         soup.find('input', attrs={'name': re.compile(r'csrf|_csrf')}))
  5. if csrf:
  6.     csrf = csrf.get('content') or csrf['value']
  7. else:
  8.     csrf = ''
复制代码

  • 兼容两种常见写法:




  • 如果站点没开 CSRF,就留空字符串,不报错。
3.3 登录并检查重定向
  1. resp = s.post(LOGIN_URL, data={
  2.     'uname': USERNAME,
  3.     'password': PASSWORD,
  4.     '_csrf': csrf
  5. }, allow_redirects=False)
  6. if resp.status_code != 302:
  7.     raise RuntimeError('登录失败,请检查账号密码或抓包核对字段名')
复制代码

  • allow_redirects=False:
    大多数 OJ 登录成功后 302 跳转到首页 / 个人页,用这一特征即可判断是否成功。
  • 若返回 200,多半是密码错误或字段名不对。
3.4 把 KaTeX 还原成 LaTeX
  1. for katex_span in soup.find_all('span', class_='katex'):
  2.     annotation = katex_span.find('annotation')
  3.     if annotation:
  4.         katex_span.replace_with(f"${annotation.text}$")
复制代码

  • KaTeX 渲染后的 HTML 会把公式藏在  里。
  • 取出文本,再用 $...$ 包裹,Markdown 就能被本地渲染器正确识别为行内公式。
3.5 提取题目主体
  1. problem_content = soup.find('div', class_='problem-content')
复制代码

  • 不同 OJ 的类名可能不一样,按需修改。
  • 找到后直接传给 html2text。
3.6 HTML → Markdown
  1. h = html2text.HTML2Text()
  2. h.ignore_links = False
  3. h.bypass_tables = False
  4. h.ignore_images = False
  5. h.body_width = 0
  6. markdown = h.handle(str(problem_content))
复制代码

  • 关闭自动换行,防止长公式被截断。
  • 保留链接、图片、表格,保证题目完整性。
4.完整代码
  1. #!/usr/bin/env python3
  2. """
  3. by jason
  4. 2025-07-26
  5. copyright wanwusangzhi 2024-2025
  6. """
  7. import requests, re, sys
  8. from bs4 import BeautifulSoup
  9. import html2text
  10. # ========== 按需修改 ==========
  11. BASE_URL = input("")       # 你的站点根域名
  12. LOGIN_URL = f'{BASE_URL}/login' # login page
  13. Problem_ID = input("")
  14. HOME_URL = f'{BASE_URL}/p/{Problem_ID}'
  15. USERNAME  = input("")
  16. PASSWORD  = input("")
  17. # ===============================
  18. def main():
  19.     s = requests.Session()
  20.     s.headers.update({
  21.         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
  22.                       'AppleWebKit/537.36 (KHTML, like Gecko) '
  23.                       'Chrome/120.0.0.0 Safari/537.36'
  24.     })
  25.     # 1. 拉登录页,取 csrf
  26.     login_html = s.get(LOGIN_URL).text
  27.     soup = BeautifulSoup(login_html, 'lxml')
  28.     csrf = (soup.find('meta', attrs={'name': 'csrf-token'}) or
  29.             soup.find('input', attrs={'name': re.compile(r'csrf|_csrf')}))
  30.     if csrf:
  31.         csrf = csrf.get('content') or csrf['value']
  32.     else:
  33.         csrf = ''          # 站点没开 csrf 验证
  34.     # 2. 提交账号密码
  35.     resp = s.post(LOGIN_URL, data={
  36.         'uname': USERNAME,
  37.         'password': PASSWORD,
  38.         '_csrf': csrf
  39.     }, allow_redirects=False)
  40.     if resp.status_code != 302:
  41.         raise RuntimeError('登录失败,请检查账号密码或抓包核对字段名')
  42.     # 3. 登录成功后拿首页
  43.     home_html = s.get(HOME_URL).text
  44.     soup = BeautifulSoup(home_html, 'html.parser')
  45.     for katex_span in soup.find_all('span', class_='katex'):
  46.         annotation = katex_span.find('annotation')
  47.         if annotation:
  48.             katex_span.replace_with(f"${annotation.text}$")  # 可选:加 $ 变成 LaTeX 公式
  49.         # 查找的div
  50.     problem_content = soup.find('div', class_='problem-content')
  51.     html=problem_content
  52.     print(html)
  53.     # 创建 html2text 处理器
  54.     h = html2text.HTML2Text()
  55.     h.ignore_links = False  # 不忽略链接
  56.     h.bypass_tables = False  # 不忽略表格
  57.     h.ignore_images = False  # 不忽略图片
  58.     h.body_width = 0  # 不自动换行
  59.                     # 转换 HTML 为 Markdown
  60.     markdown = h.handle(str(html))
  61.     print(markdown)               
  62.     with open("p.md","w",encoding='utf-8') as f:
  63.         f.write(markdown)
  64. if __name__ == '__main__':
  65.     main()
复制代码
5. 运行示例
  1. $ python3 grab.py
  2. https://hydro.ac
  3. H1001
  4. username
  5. password
复制代码
程序会在当前目录生成 p.md,内容示例:
  1. ### 1000. A + B Problem
  2. #### Description
  3. Calculate $a+b$.
  4. #### Input
  5. Two integers $a, b$ ($0 \le a, b \le 10^9$).
  6. #### Output
  7. Output $a+b$.
  8. #### Sample Input
复制代码
1 2
  1. #### Sample Output
复制代码
3
[code][/code]6. 小结

60 行代码,解决了“复制题目丢格式”的痛点。
核心思路只有三步:登录 → 解析 → 转换
把它跑通后,你就有了一套完全属于自己的题库快照,离线刷题、写题解、做 LaTeX 笔记都方便很多。
Happy hacking & happy coding!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册