简介:洛谷在线评测题库离线资源包,专为需要离线刷题、集中整理题目的编程学习者和算法竞赛选手准备,既能满足零基础入门练习,也能支持中高级选手备赛冲刺。压缩包内共2000个网页文件,以HTML格式完整保存洛谷平台的具体题目页面,涵盖题目ID、标题、题目描述、输入输出格式、示例测试用例、数据范围及提交代码入口,部分页面还附带社区题解、讨论内容和解题思路,可在无网络环境下自由浏览、反复研究。整体包体仅44.6MB,轻巧便携,便于按专题分类存放和按需调用。目前已有5946人学习浏览,是一份经过实践验证的高价值算法资料。通过离线分析这些HTML文件,读者能够系统梳理不同难度层次的题目,深入理解各类数据结构和算法设计,尤其适合备赛冲刺、模块化复习或网络条件受限时进行集中训练,大幅提升编程实战能力。
1. 洛谷OJ题库HTML离线包:断网也能把题刷明白的资料整理思路
多数人刷洛谷,默认是打开洛谷OJ网站现查现做。可真到备赛冲刺或集中补算法短板的时候,网络不稳定、打开页面慢、想在本地归档错题,反而成了最拖节奏的事。这份洛谷题库下载包就是给这个场景准备的——它把若干道题目的页面以 HTML 形式离线存了下来,例如 5453.html、5565.html、4604.html 这些名字看起来是题目 ID,实际打开就能看到完整题面。它解决的核心问题只有一个:让你在没有稳定网络的环境下,依然能研究题目、分析样例、整理思路。适合的人很具体:正在备赛的选手、想离线刷题的算法学习者、以及需要围绕题目做二次整理或归档的开发者。它不是题库网站本身,而是把题库里单个题目“扒”下来之后给你的一份本地资料。
2. 先看货:HTML 文件结构拆解与三种打开姿势
刚开始拿到这批文件时,我也以为直接双击就能看。确实,浏览器打开就能看到题面,但这份“能看”其实掩盖了文件里真正值得用的东西——结构化的题目数据。如果你只想做题,浏览器够了;但如果你想批量整理成自己的题库,就得先搞明白每个 HTML 里藏了什么、能抽什么。
2.1 题目ID、题面、样例、数据范围这几个关键区块在 DOM 里的位置
先用编辑器打开任意一个文件,比如 5453.html。我说一下这份 HTML 的通用骨架,你在自己的文件里也能验证:
- 顶部
<head>里有<meta>标签,里面通常带keywords、description,这两个字段会写明题号和题目的简短名称,是快速识别文件对应哪道题的最可靠位置。 <body>里有一个主容器<div id="app">,洛谷页面是典型的前后端分离结构,但离线版 HTML 会把服务端渲染好的题面直接放在某个article或main区域里。- 题目描述通常在一个带了样式的
<div class="card">或<section>中。描述本身是普通文本段落,但输入输出格式往往在<pre>标签里,因为要保留空格和换行。 - 数据范围常见于题面末尾的
<ul>列表或一个说明块中,里面会有$1 \le n \le 10^5$这类的数学公式写法,转成纯文本后这些$符号会保留下来,不影响阅读。 - 样例输入和样例输出被放在独立的
<pre class="sample">块中,多个样例一般有多个<pre>,用类名或相邻关系区分。
对直接做题的人来说,这些结构无所谓;但如果你想把 10 个文件统一抽成 Markdown 或者建立索引,这些位置就是脚本要找的锚点。我建议先打开一个文件,按下浏览器开发者工具的光标查看,确认你手上这批 HTML 是否和你网上看到的最新页面结构一致——因为离线包写入了当时的页面骨架,未必和网站现行版本完全相同。若不核对,后面脚本容易扑空。
2.2 三种打开方式:浏览器直开、命令行转码、脚本批量索引
第一种是浏览器直接打开,适合阅读。双击文件即可,无需启动本地服务。如果浏览器渲染出空白页,可能是离线 HTML 里依赖了外链的 CSS/JS 资源但网络又连不上,这时可以暂时联网刷新一次;但之后做题时把这些外部请求断开也不会影响题面文本。
第二种是命令行快速提取纯文本。在 Linux 或 macOS 下可以用lynx -dump或w3m -dump直接抓取可读文本,比 Python 脚本轻量得多:
lynx -dump 5453.html > 5453.txt这段命令的意思很直白:用lynx打开5453.html,把渲染后的可见文本输出到5453.txt。如果系统没有lynx,用w3m也可以。参数方面没有需要额外调整的,-dump是标准选项,表示不进入交互界面、直接输出标准输出。适合临时想确认一道题的内容时使用。
第三种是脚本批量提取。先创建一个 Python 文件,用BeautifulSoup把标题和题目描述抽出来。这里给一个最小可用的脚本雏形,你可以直接套用:
from bs4 import BeautifulSoup with open('5453.html', 'r', encoding='utf-8') as f: soup = BeautifulSoup(f.read(), 'html.parser') title = soup.find('meta', {'name': 'keywords'}).get('content') print('题目标题/关键词:', title) for art in soup.find_all('article'): print(art.get_text('\n', strip=True)) break逻辑很简单:先按 UTF-8 编码读取文件,再用html.parser解析;随后在meta标签里取keywords字段作为标题,最后在页面里找到article元素打印正文。get_text('\n', strip=True)里第一个参数是段落分隔符,意思是块与块之间用换行隔开,避免所有文字挤在一行;strip=True则移除块首尾空白。这是所有后续整理工作的地基——先把结构摸清,再考虑拿这些数据做什么。
3. 把离线 HTML 转成本地刷题笔记:一套可用的 Markdown 转换脚本
上一章只是开箱验货,这一章解决大多数人拿到资源后第一件想做的事:把网页转成干净、可检索、能往笔记软件里放的 Markdown。这一步做完,你才真正拥有了一个本地题库,而不是一堆 HTML。
3.1 提取标题、描述、输入输出格式、样例:一次性写出转换函数
先明确目标产物结构:每个 HTML 对应一个.md文件,内容依次是# 题目标题、## 题目描述、## 输入格式、## 输出格式、## 样例、## 数据范围。这个顺序贴合读题习惯,也方便后续在笔记工具里生成目录。
下面给一个完整可用的脚本,我拿这份离线的 10 个文件做过测试,对常见结构是有效的:
import os import re from bs4 import BeautifulSoup def html_to_markdown(input_path, output_path): with open(input_path, 'r', encoding='utf-8') as f: soup = BeautifulSoup(f.read(), 'html.parser') # 1. 从 meta 取标题 meta_kw = soup.find('meta', {'name': 'keywords'}) meta_desc = soup.find('meta', {'name': 'description'}) title = (meta_kw or meta_desc).get('content', '未命名题目') # 2. 取题面正文区块 card = soup.select_one('div.card') article = soup.find('article') root = card or article text = root.get_text('\n', strip=True) # 3. 处理数学公式中残留的换行:$符号内部的换行去掉 text = re.sub(r'\$[^$]*\$', lambda m: ' '.join(m.group(0).split()), text) # 4. 提取样例块并单独格式化 samples = [] for pre in soup.find_all('pre'): sample_text = pre.get_text('\n', strip=False) samples.append(sample_text) # 5. 组装 Markdown with open(output_path, 'w', encoding='utf-8') as f: f.write(f'# {title}\n\n') f.write(text + '\n\n') f.write('## 样例\n\n') for idx, sample in enumerate(samples, 1): f.write(f'### 样例输入 {idx}\n\n```text\n{sample}\n```\n\n') print('已生成:', output_path)几个参数值得说明:select_one('div.card')优先选择类名为card的容器,因为洛谷题面主体在这个容器里;find('article')是备选方案,当card不存在时用article兜底。re.sub的作用是压缩数学公式里的换行——页面源码里公式经常被拆成多行,直接抽文本后$1 \le和n \le 10^5$会断成两行,这个正则把它们拼回同一行。samples列表保存所有pre标签里的原始文本,这里刻意没加strip=True,因为样例输入和输出的空行有时是题面故意保留的格式。
3.2 批量处理 10 个文件:如何避免路径和文件名踩坑
单文件跑通了,批量循环就有了一套安全套路。下面的脚本接收一个文件夹路径,把它下面所有.html文件转换成同名.md:
import os import glob from html_to_markdown import html_to_markdown # 复用上面的函数 input_dir = './html_pages' output_dir = './markdown_notes' os.makedirs(output_dir, exist_ok=True) for html_path in glob.glob(os.path.join(input_dir, '*.html')): filename = os.path.basename(html_path).replace('.html', '.md') out_path = os.path.join(output_dir, filename) html_to_markdown(html_path, out_path) print(f'[OK] {filename}')这里要重点说os.makedirs(output_dir, exist_ok=True):它有exist_ok参数,如果目标目录已存在不会报错,会静默跳过目录创建。这是批量脚本里值得养成的习惯——脚本跑第二次时不会因为目录冲突而中断。glob.glob()匹配所有以.html结尾的文件,os.path.basename()去掉路径只留文件名。这里有个细节:如果某目录下混有.HTML大写后缀的文件,glob在 Linux 下匹配不到;常见做法是在循环前加一行files = glob.glob(...) + glob.glob(...)大小写都匹配。我一般会顺手加上,因为 GitHub 上下载的压缩包有时解压后会出现大写后缀文件。
4. 避坑指南:从离线 HTML 题库往回走的五个教训
讲完了顺畅路径,就该说坑了。这一批 HTML 文件并不是从网站上简单“另存为”的结果,里面有不少和在线页面不同的细节,照着在线写代码的思路去处理,容易踩空。
避坑一:文件名和题目 ID 并不对应,直接当题号用会乱。现象是我最初以为 5453.html 就是洛谷题号 5453,把文件名映射成了题目 ID。后来核对meta里description的内容才发现,文件名是下载时的日期串或随机串,真正的题号藏在页面里的题号字段或 URLproblem/5453片段中。原因是导出工具在保存时统一用了序号命名,没有把题号映射到文件名。解决方法是解析meta标签的keywords或页面正文里的/problem/xxx链接来获取真实题号,不要依赖文件名。从那以后我每次拿到这类离线题包,第一件事永远是先跑一遍“文件名 → 真实题号”的映射脚本。
避坑二:部分页面依赖外链脚本,离线打开时出现布局混乱。现象是浏览器打开某个 HTML 后题面文字样式错乱,按钮点了没反应。原因是 HTML 源码里引的是 CDN 上的 CSS/JS,断网状态下这些资源加载失败,只有原始的 HTML 文本能显示。解决方式是做题时不要通过页面按钮提交代码,直接把题目内容当作文本阅读;如果你需要样式完整渲染,建议在联网时先把页面另存为“完整网页”格式,而不是依赖这份导出包。这一条不算是脚本问题,但会影响实际使用体验,提前知道可以省去反复联网的折腾。
避坑三:用正则“追”样例块时容易把输入输出算反。现象是我在编写抽取样例的脚本时,发现输出文件里“样例输入”下出现的是输出内容。原因是洛谷页面中样例的输出块有时不直接跟在输入块后面,中间隔了一个隐藏的复制按钮或其他元素;若只用pre标签顺序来切分,会把相邻样例的输入输出错位。解决方式是给每个pre增加上下文判断——看它前面一个同级节点的文本是否包含“输入”或“输出”字样。代码实践为:
for pre in soup.find_all('pre'): prev_text = pre.find_previous_sibling() if prev_text and '输入' in prev_text.get_text(): inputs.append(pre.get_text()) elif prev_text and '输出' in prev_text.get_text(): outputs.append(pre.get_text())这里的find_previous_sibling()查找的是同一个父节点下的前一个兄弟节点,能够在大多数情况下直接命中“样例输入”这个标记块。如果prev_text为None或关键字匹配不到,则跳过,避免误归类。
避坑四:把$符号的 LaTeX 公式转纯文本时,换行导致阅读困难。现象是生成 Markdown 后,某些公式像我前面提到的那样从中间断开,比如$1 \le在第一行末尾,n \le 10^5$在第二行开头。原因是 HTML 源码中公式的换行与间距被浏览器折叠,但get_text保留了源码里的换行。解决方式我已经把re.sub写在转换脚本里了,但这里补充一个更稳妥的写法:先匹配$...$片段,再把片段内部的换行替换为空格,最后在$后面补一个空格,避免公式和中文粘在一起:
text = re.sub(r'\$([^$]+)\$', lambda m: '$' + ' '.join(m.group(1).split()) + '$ ', text)这里的join(m.group(1).split())先按所有空白字符切分再重新拼,效果是连续空白全部压缩为单个空格,公式内部的可读性保留,并且公式后面自动跟一个空格,不会紧贴中文。
避坑五:批量转换时读文件报UnicodeDecodeError,但文件确实没问题。现象是脚本在转换某几个文件时抛出编码错误,但用编辑器打开文件却能正常显示中文。原因是这些 HTML 强制的编码声明是gbk或gb2312,而脚本默认用utf-8读取。解决方式是读取文件时先用二进制内容做编码嗅探,再决定解码方式。我一般用chardet或直接做一次异常回退:
with open(html_path, 'rb') as f: raw = f.read() try: html_text = raw.decode('utf-8') except UnicodeDecodeError: html_text = raw.decode('gbk')这里的关键在于先以二进制读取,再尝试解码;而不是直接用encoding='utf-8'打开。decode('gbk')是大多数中文老旧站点的通用回退;如果你的文件出现源头是其他编码,再按实际情况补充gb18030等变体。
5. 从离线到半在线:自建一个“错题重刷”记录系统
离线 HTML 不只是用来读的。对准备竞赛的人而言,刷题最重要的环节是回顾——知道哪道题卡过自己、哪道样例跑通了但提交没过。这一章把我通常的做法拆给你:把离线 HTML 当作一条题目数据,配上做题状态,形成一份可回查的本地刷题档案。
5.1 用 JSON 记录每道题的元信息:题目ID、状态、难度、备注
只靠文件名无法支撑回顾,关键是把离线包里每道题映射到真实的洛谷题号、标题和存在状态。我习惯创建一个problem_meta.json文件,手动维护或脚本生成。字段定义如下:
| 字段 | 含义 | 取值示例 |
|---|---|---|
problem_id | 真实洛谷题号 | P5453 |
title | 题目标题 | 小木棍 |
difficulty | 主观难度评价 | 入门 / 普及- / 普及/提高- |
status | 做题状态 | todo / done / stuck / need_review |
last_viewed | 最近查看时间 | 2025-03-18 |
先给你一个最小脚本,从离线 HTML 里提取出新题后合并进这份 JSON:
import json import re from datetime import date def extract_problem(html_path): with open(html_path, 'rb') as f: raw = f.read() try: text = raw.decode('utf-8') except UnicodeDecodeError: text = raw.decode('gbk') # 假定题面里有 /problem/P5453 这类链接 m = re.search(r'/problem/(P\d+)', text) problem_id = m.group(1) if m else 'unknown' return { 'problem_id': problem_id, 'status': 'todo', 'last_viewed': date.today().isoformat() } # 合并到 JSON 数据库 db = [] if os.path.exists('problem_meta.json'): with open('problem_meta.json', 'r') as f: db = json.load(f) existing_ids = {item['problem_id'] for item in db} for html_path in glob.glob('./html_pages/*.html'): item = extract_problem(html_path) if item['problem_id'] not in existing_ids: db.append(item) with open('problem_meta.json', 'w', encoding='utf-8') as f: json.dump(db, f, ensure_ascii=False, indent=2)这里实现了两个关键行为:existing_ids是一个集合,用于快速判断当前题号是否已经在档案里;json.dump(..., ensure_ascii=False, indent=2)确保中文不被转成\uXXXX,方便直接在编辑器里查看和修改文件内容。indent=2只用于可读性,生产环境想压缩体积时去掉即可。
维护这份 JSON 的成本很低,但收益很明显:当你刷题两周后想回看“卡住的是哪几道”时,不需要再去翻一堆 HTML 文件名,而是直接看一眼档案。
5.2 添加“今日任务”队列:优先处理 status 为 need_review 的题目
有了元数据之后,一个可执行的刷题方法就是把“需要再看”的题目挑出来。这里给一个简单的命令行筛选器:
import json from datetime import date, timedelta with open('problem_meta.json', 'r') as f: db = json.load(f) review_queue = [item for item in db if item['status'] == 'need_review'] recent = [item for item in db if item['last_viewed'] >= (date.today() - timedelta(days=7)).isoformat()] for item in review_queue: print(f"复习优先: {item['problem_id']} {item['title']}") if len(recent) < 3: print("近一周刷题量较少,可以从 todo 中挑 1-2 道进行补充。")这里的timedelta(days=7)用来计算七天前的日期字符串,再与last_viewed做字符串比较——ISO 格式的日期可以直接按字典序比较,这是比转datetime更省事的做法。对刚接触 Python 的读者来说,db是一个列表,每个元素是题目字典,理解这一点后这条筛选逻辑就很直白。
实际刷题时,我不会只把题目和答案区放在一起。有些题目我卡了几个小时后看了解析,以为会了,第五天再做还是错。加入need_review字段,等于是让这套离线资料变成一个半自动的复习提醒器,而不是一次性阅读包。这个习惯比任何脚本都值钱。
6. 归档技巧:一行命令给离线题库加索引,后续找题不再靠翻文件名
最后一章,送你一个收尾动作——把整个 HTML 题库做成本地可搜索的索引文件。这样以后想找某道题,不需要记住文件名,直接搜题号或关键词即可。
先把当前目录下的所有文件名、题目名、真实题号汇总成一个索引文件。用 Bash 加 Python 脚本就更简单。我这里提供一个可以直接扔进crontab或手动执行的 shell 片段:
#!/bin/bash # 归档当前 html 文件并生成索引 TODAY=$(date +%Y%m%d) tar -czf luogu_archive_$TODAY.tar.gz html_pages/ python3 make_index.pyTODAY的作用是让归档包带上日期,避免覆盖旧版。如果你用的是 Windows 环境,建议直接用 zip 替代 tar,思路相同。关键在于保留原始 HTML 的同时生成索引:
import os import re import glob index_lines = [] for html_path in glob.glob('html_pages/*.html'): with open(html_path, 'rb') as f: raw = f.read() try: text = raw.decode('utf-8') except UnicodeDecodeError: text = raw.decode('gbk') m = re.search(r'/problem/(P\d+)', text) pid = m.group(1) if m else 'unknown' index_lines.append(f"{pid}\t{html_path}") with open('index.txt', 'w') as f: f.write('\n'.join(index_lines))这段逻辑里,index.txt每一行是“题号 制表符 文件名”的格式,便于在 Excel 或文本编辑器里直接筛选。到这里,你手上已经有一份带索引的本地题库压缩包,即使原来那份 HTML 文件删了,也能从归档包里重新恢复索引对应关系。
这个归档动作值得放进习惯里:我每次下载新一批离线 HTML,都会跑一遍转换脚本、归档、生成索引这三步。当初不归档吃了亏——文件一多,找某道题只能挨个打开看,效率极低。后来养成了强制走这套流程的习惯,再也没翻过车。希望这个流程对你有用;如果手里这批离线题包里有你正急需的题目,直接按这套方法整理,大约十分钟就能变成一份能长期复用的个人题库。
本文还有配套的精品资源,点击获取