作者:jason
日期:2025-07-26
版权:wanwusangzhi 2024-2025
项目地址:https://github.com/wanwusangzhigit/hydro
1. 背景故事
很多人第一次刷题时,都会把题目复制到本地笔记软件里做草稿。
动手复制粘贴几次后,你会立刻意识到:
“公式全丢了,图片全是外链,表格惨不忍睹。”
“主要无法用AI解题”
于是就有了今天这 60 行小脚本:
自动登录 OJ → 拉取指定题目 → 把 KaTeX 公式还原成 LaTeX → 输出 Markdown 文件。
以后写题解、做笔记,再也不用对着网页敲公式了。
2. 整体思路
一句话概括:
“用 requests 做登录,用 BeautifulSoup 找内容,用 html2text 转 Markdown。”
流程图:- ┌--------------┐
- │ 用户输入 │
- │ BASE_URL │
- │ Problem_ID │
- │ USERNAME/PWD │
- └------┬--------┘
- │
- ┌------┴--------┐
- │ 1. 拉登录页 │ ← GET /login
- │ 取 csrf-token │
- └------┬--------┘
- │
- ┌------┴--------┐
- │ 2. 提交登录 │ ← POST /login
- │ 302 跳转即成功│
- └------┬--------┘
- │
- ┌------┴--------┐
- │ 3. 拉题目页 │ ← GET /p/{Problem_ID}
- │ 替换 KaTeX │
- └------┬--------┘
- │
- ┌------┴--------┐
- │ 4. 转 Markdown│ ← html2text
- │ 写入 p.md │
- └--------------┘
复制代码 3. 关键代码拆解
3.1 会话与伪装 UA
- s = requests.Session()
- s.headers.update({
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
- 'AppleWebKit/537.36 (KHTML, like Gecko) '
- 'Chrome/120.0.0.0 Safari/537.36'
- })
复制代码
- 使用 Session 可自动携带后续 Cookie,不用手动维护。
- UA 伪装成桌面 Chrome,避免服务器直接拒绝“非浏览器”请求。
3.2 动态获取 CSRF-token
- login_html = s.get(LOGIN_URL).text
- soup = BeautifulSoup(login_html, 'lxml')
- csrf = (soup.find('meta', attrs={'name': 'csrf-token'}) or
- soup.find('input', attrs={'name': re.compile(r'csrf|_csrf')}))
- if csrf:
- csrf = csrf.get('content') or csrf['value']
- else:
- csrf = ''
复制代码
- 兼容两种常见写法:
- 如果站点没开 CSRF,就留空字符串,不报错。
3.3 登录并检查重定向
- resp = s.post(LOGIN_URL, data={
- 'uname': USERNAME,
- 'password': PASSWORD,
- '_csrf': csrf
- }, allow_redirects=False)
- if resp.status_code != 302:
- raise RuntimeError('登录失败,请检查账号密码或抓包核对字段名')
复制代码
- allow_redirects=False:
大多数 OJ 登录成功后 302 跳转到首页 / 个人页,用这一特征即可判断是否成功。
- 若返回 200,多半是密码错误或字段名不对。
3.4 把 KaTeX 还原成 LaTeX
- for katex_span in soup.find_all('span', class_='katex'):
- annotation = katex_span.find('annotation')
- if annotation:
- katex_span.replace_with(f"${annotation.text}$")
复制代码
- KaTeX 渲染后的 HTML 会把公式藏在 里。
- 取出文本,再用 $...$ 包裹,Markdown 就能被本地渲染器正确识别为行内公式。
3.5 提取题目主体
- problem_content = soup.find('div', class_='problem-content')
复制代码
- 不同 OJ 的类名可能不一样,按需修改。
- 找到后直接传给 html2text。
3.6 HTML → Markdown
- h = html2text.HTML2Text()
- h.ignore_links = False
- h.bypass_tables = False
- h.ignore_images = False
- h.body_width = 0
- markdown = h.handle(str(problem_content))
复制代码
- 关闭自动换行,防止长公式被截断。
- 保留链接、图片、表格,保证题目完整性。
4.完整代码
- #!/usr/bin/env python3
- """
- by jason
- 2025-07-26
- copyright wanwusangzhi 2024-2025
- """
- import requests, re, sys
- from bs4 import BeautifulSoup
- import html2text
- # ========== 按需修改 ==========
- BASE_URL = input("") # 你的站点根域名
- LOGIN_URL = f'{BASE_URL}/login' # login page
- Problem_ID = input("")
- HOME_URL = f'{BASE_URL}/p/{Problem_ID}'
- USERNAME = input("")
- PASSWORD = input("")
- # ===============================
- def main():
- s = requests.Session()
- s.headers.update({
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
- 'AppleWebKit/537.36 (KHTML, like Gecko) '
- 'Chrome/120.0.0.0 Safari/537.36'
- })
- # 1. 拉登录页,取 csrf
- login_html = s.get(LOGIN_URL).text
- soup = BeautifulSoup(login_html, 'lxml')
- csrf = (soup.find('meta', attrs={'name': 'csrf-token'}) or
- soup.find('input', attrs={'name': re.compile(r'csrf|_csrf')}))
- if csrf:
- csrf = csrf.get('content') or csrf['value']
- else:
- csrf = '' # 站点没开 csrf 验证
- # 2. 提交账号密码
- resp = s.post(LOGIN_URL, data={
- 'uname': USERNAME,
- 'password': PASSWORD,
- '_csrf': csrf
- }, allow_redirects=False)
- if resp.status_code != 302:
- raise RuntimeError('登录失败,请检查账号密码或抓包核对字段名')
- # 3. 登录成功后拿首页
- home_html = s.get(HOME_URL).text
- soup = BeautifulSoup(home_html, 'html.parser')
- for katex_span in soup.find_all('span', class_='katex'):
- annotation = katex_span.find('annotation')
- if annotation:
- katex_span.replace_with(f"${annotation.text}$") # 可选:加 $ 变成 LaTeX 公式
- # 查找的div
- problem_content = soup.find('div', class_='problem-content')
- html=problem_content
- print(html)
- # 创建 html2text 处理器
- h = html2text.HTML2Text()
- h.ignore_links = False # 不忽略链接
- h.bypass_tables = False # 不忽略表格
- h.ignore_images = False # 不忽略图片
- h.body_width = 0 # 不自动换行
- # 转换 HTML 为 Markdown
- markdown = h.handle(str(html))
- print(markdown)
- with open("p.md","w",encoding='utf-8') as f:
- f.write(markdown)
- if __name__ == '__main__':
- main()
复制代码 5. 运行示例
- $ python3 grab.py
- https://hydro.ac
- H1001
- username
- password
复制代码 程序会在当前目录生成 p.md,内容示例:- ### 1000. A + B Problem
- #### Description
- Calculate $a+b$.
- #### Input
- Two integers $a, b$ ($0 \le a, b \le 10^9$).
- #### Output
- Output $a+b$.
- #### Sample Input
复制代码 1 23
[code][/code]6. 小结
60 行代码,解决了“复制题目丢格式”的痛点。
核心思路只有三步:登录 → 解析 → 转换。
把它跑通后,你就有了一套完全属于自己的题库快照,离线刷题、写题解、做 LaTeX 笔记都方便很多。
Happy hacking & happy coding!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |