蓝桥杯题库导入QDUOJ全攻略:格式转换、批量建题与避坑指南
发布时间:2026/9/25 6:14:38来源:尧图网络
简介面向蓝桥杯备赛选手与青岛大学在线评测系统使用者这份题库资源将历年蓝桥杯赛题整理为适配QDUOJ的导入格式用以模拟评测与专项训练。资源压缩包共2个文件整体约70.24MB包含一个JSON格式题目数据文件记录题目描述、输入输出格式、时间与内存限制等信息和一个ZIP测试用例包内含多组输入输出样例便于自动比对判题。目前已有1342人学习下载。借助该题库读者可在QDUOJ中直接导入题目与测试数据快速熟悉竞赛题型和评测规则通过反复提交代码、对比运行结果可有效排查算法实现中的边界问题。旧题数据与标准测试样例的整合也有助于搭建本地练习环境对备战蓝桥杯及其他算法竞赛均具实用价值。1. 蓝桥杯题库 for QDUOJ 到底是什么从“能看到题”到“能判题”的那一步每年二三月备战蓝桥杯的同学最常问的一句话就是真题去哪刷蓝桥杯真题散落在 PDF、博客和各种公众号里光看题没有评测反馈提交之后也不知道边界 case 挂在哪。把 QDUOJ 这套在线评测系统在本机或校内服务器跑起来之后很多人第一件事就是找“蓝桥杯题库 for QDUOJ”这类现成题库包导入。它的价值在于让人跳过手动录题、造测试数据的重复劳动而“修改版”三个字通常意味着有人在原版基础上修过题面、补过测试数据、重排过目录结构。接下来就按这个标题把导入全链路拆开格式怎么对齐、数据怎么打成 zip、批量建题怎么跑、哪些坑我踩过。适合带竞赛的老师、自己搭 OJ 的运维同学以及想自建刷题环境的选手。2. 先吃透 QDUOJ 的题目结构再谈把蓝桥杯真题装进去很多人拿到题库包第一反应是“解压、上传、完事”结果题目建了一堆一提交全报错。原因很简单QDUOJ 对题目的组织方式有自己的约定而蓝桥杯真题的原始形态五花八门。不先搞清两者怎么对应导入就是碰运气。2.1 QDUOJ 建题唯一绕不开的字段清单QDUOJ 的题目本质上是一条结构化记录不是一篇纯 Markdown 文档。管理后台建题页面的核心字段如下字段作用蓝桥杯真题来源title题目标题原题“试题 X题目名”description题面正文支持 Markdown原题“问题描述”及背景input_description输入格式说明原题“输入格式”output_description输出格式说明原题“输出格式”sample_input样例输入原题样例输入sample_output样例输出原题样例输出hint提示信息原题“数据规模与约定”也可放题解time_limit单点时间限制单位 ms蓝桥杯练习系统给出的时限memory_limit单点内存限制单位 MB官方一般 256MBspj是否开启 Special Judge结果不唯一的题需要开source题目来源第 16 届蓝桥杯省赛、蓝桥杯 C 组真题等建题只是第一步。真正让一道题“能判”的是它名下绑定的测试数据QDUOJ 判题时会把选手程序跑起来喂入某个.in文件再把程序输出和对应的.out文件比对。一个题目可以挂多组测试数据后台通常支持上传一个 zip 压缩包里面按1.in / 1.out、2.in / 2.out这样成对命名。新手最容易漏掉的就是这个“数据绑定”步骤。题面填得再漂亮不挂测试数据提交后判题机直接报错或一直等待。2.2 蓝桥杯真题的三种原始形态和它们的转换路线蓝桥杯题库包的作者们收集真题时主要面对三种来源每种都要做不同处理。第一种是官方 PDF。蓝桥杯历届省赛、国赛的真题卷在官网和各类网盘里很好找但 PDF 里的题面是排版好的有分栏、有表格、字体不统一直接复制会丢失换行和缩进。通常需要人工整理成 Markdown重点检查“输入格式”“输出格式”两段有没有被 PDF 的分栏截断。我见过不少题库包的题面把输入格式和样例输出拼在一起就是 PDF 复制时没对齐列。第二种是博客和题解站的转录。很多个人博客把真题贴成 HTML 或 Markdown看起来干净实际上图片多挂在第三方图床导进 QDUOJ 后图片外链可能失效。出现这种情况要么把图片下载到本地再传成 OJ 的附件要么直接在题面里删掉图片换成文字描述。填充题、图形题尤其依赖图删图后信息可能不完整需要人工补写。第三种是从蓝桥杯练习系统或其它 OJ 抓取。这种形态的题面和测试数据最规整直接拿接口导出即可。但要注意两点一是原站可能对抓取有限制别把人家服务打挂了二是抓来的数据文件命名各不相同有的叫1.in/1.ans有的叫case1_input.txt导入前必须统一改名为 QDUOJ 认识的1.in / 1.out。“修改版”的价值恰恰体现在这一层它把这些来源混在一起造成的脏数据重新洗了一遍。比较常见的修改包括修正题面里缺失的输入输出格式说明、把填空题改造成“直接输出答案”的 OJ 题、补齐测试数据文件、重排年份与组别目录。所以说“修改版”改的不是题目本身而是让题目能直接被 QDUOJ 判定。2.3 “修改版”修改的到底是什么围绕“蓝桥杯题库 for QDUOJ修改版”这个标题你需要先弄清楚手上这个包和原版差在哪。虽然没有统一官方定义但根据这类题库包在高校OJ圈子里的流传情况修改点通常集中在四个方面。第一题面 HTML 清洗。原版题库可能直接抓取网页 HTML标签冗余、图片外链失效、公式显示乱掉。修改版会把这些清洗成干净的 Markdown并在 QDUOJ 里正常渲染。第二测试数据补齐与修正。蓝桥杯官方并不公开完整测试数据所以原版包里很多题只有一两组弱样例甚至样例就是全部数据。修改版会补入边界数据、大数据、随机数据让 AC 的代码更有说服力。这个改动是“修改版”最有价值的部分也是最需要你警惕的部分——后面第 5 章会细说怎么验证补的数据对不对。第三测试数据文件名重排。原版包里可能出现1.in/10.in/2.in这种文件名按字符串排序时 10 排在 2 前面导致判题顺序和题目描述不一致。修改版一般会统一改成三位编号比如001.in/010.in/002.in避免这类低级错误。第四判题参数和 SPJ 修正。原版有的题 time_limit 设置过高或过低有的需要 SPJ 却开了严格比较导致结果不唯一的题没法通过。修改版会在题目配置里把这些调对。一句话总结如果你拿到的题库包标题里带“修改版”重点不是看题面多不多而是看它的测试数据目录和 SPJ 配置是不是完整。这两样没做好题目量再大也是摆设。3. 把题库包变成可判题题目解压、打包与批量导入格式问题想清楚之后就可以动手导入了。整个流程分成三步检查目录结构、把散落的测试数据打包成统一命名的 zip、然后逐题导入 QDUOJ。下面给的是我在校内 OJ 上实际跑过一遍的流程每一步都能复现。3.1 拿到题库包先别急着上传目录结构检查先别用鼠标双击解压。把题库包传到 OJ 服务器所在的主机上用命令行解开顺便看看目录长什么样。unzip lanqiao_qduoj.zip -d lanqiao_qduoj cd lanqiao_qduoj find . -maxdepth 2 -type f | head -50这段先把压缩包解压到lanqiao_qduoj目录然后用find列出两层以内的所有文件。maxdepth 2的意思是只看第一级主题目目录和它里面的文件避免一次性打印几百个文件把终端刷爆。head -50只取前 50 行先看个大概。这一步要确认三件事。第一题目目录是否按组别或年份分好层比如C组、Python组、嵌入式而不是所有 md 文件平铺在一个目录里。第二每道题是否都有独立的data目录里面是否成对出现.in和.out文件。第三题目说明文件是.md还是.html编码是不是 UTF-8。这一步发现问题比导入后在 OJ 后台一个个改要省力得多。如果unzip解压后文件名乱码先别急这不是包坏了是编码问题。第 5 章专门有这条避坑记录这里先跳过继续走干净的流程。3.2 把散落的数据文件打成 QDUOJ 认得的测试数据 zip原版题库包里的测试数据命名很乱有的叫1.in/1.ans有的叫01_input.txt/01_output.txt还有的是嵌套目录。QDUOJ 后台上传测试数据时认的是 zip 内成对的1.in / 1.out这种名字。所以导入前得先写个小脚本统一转换。# pack_cases.py # 把题目目录下的 data 文件夹打包成 QDUOJ 能识别的测试数据 zip import os import zipfile def pack_case(problem_dir: str, out_zip: str) - None: data_dir os.path.join(problem_dir, data) if not os.path.isdir(data_dir): raise FileNotFoundError(f缺 data 目录: {problem_dir}) cases [] for name in os.listdir(data_dir): # 只看 .in 结尾的文件找到每个 case 的编号 if name.endswith(.in): cases.append(name[:-3]) # 去掉后缀留下 1 或 01_input 这种前缀 # 按数值排序避免 10、2 混排 cases.sort(keylambda x: int(.join(ch for ch in x if ch.isdigit()))) with zipfile.ZipFile(out_zip, w, zipfile.ZIP_DEFLATED) as zf: for i, case_id in enumerate(cases, start1): in_path os.path.join(data_dir, f{case_id}.in) out_path os.path.join(data_dir, f{case_id}.out) if not os.path.exists(out_path): print(f警告: {out_path} 不存在已跳过) continue # 统一重命名为 1.in / 1.out避免原文件名的各种怪前缀 zf.write(in_path, f{i}.in) zf.write(out_path, f{i}.out) if __name__ __main__: import sys pack_case(sys.argv[1], f{sys.argv[1]}_cases.zip)这段脚本做的事很简单遍历data目录找出所有.in文件按里面的数字编号排序再连同对应的.out文件一起重命名写入 zip。注意cases.sort那行把10排到2后面避免字符串排序带来的顺序错乱。如果某个 case 只有输入没有输出脚本会打印警告并跳过——这种残缺 case 宁可不要也别让它混进 OJ 里造成误判。运行方式python3 pack_cases.py 01_入门训练/AB问题会在题目目录下生成xxx_cases.zip这个文件就是待会上传到 QDUOJ 后台的测试数据包。整个过程可以套一层循环把整个题库包里的所有题目一次跑完建议先单题跑通再批量。3.3 批量建题的两条路后台手动与脚本调 API数据 zip 准备好了接下来要建题。如果只有十道题直接在 QDUOJ 管理后台手动建进入 Problem 列表点 Create把 Markdown 题面粘贴进对应字段保存后打开题目的 Test Data 页面上传 zip。这么做直观但题量超过五十道时后台点鼠标会让人崩溃。更常见的做法是写脚本直接调后端 API。QDUOJ 是前后端分离架构后端提供了建题接口登录拿 token 之后逐题创建。下面是个最小可用的脚本骨架# batch_create.py # 批量把 markdown 题目和数据 zip 导入 QDUOJ import os import requests API http://127.0.0.1:8080/api def read_meta(md_path: str): 从一个约定格式的 md 文件里读出题面字段 with open(md_path, r, encodingutf-8) as f: lines f.read().splitlines() return { title: lines[0].lstrip(# ).strip(), description: \n.join(lines[1:]), input_description: , output_description: , } def create_problem(token: str, meta: dict, case_zip: str) - int: headers {Authorization: fBearer {token}} with open(case_zip, rb) as zf: resp requests.post( f{API}/problem, headersheaders, data{ title: meta[title], description: meta[description], time_limit: 1000, memory_limit: 256, source: 蓝桥杯真题, }, files{test_case: zf}, # 字段名以你部署的版本为准 ) resp.raise_for_status() return resp.json()[data][id] # 登录拿 token login requests.post( f{API}/login, json{username: admin, password: your_password}, ) token login.json()[data][token] # 遍历题目目录 for root, dirs, files in os.walk(lanqiao_qduoj): if data in dirs: md_file os.path.join(root, [f for f in files if f.endswith(.md)][0]) meta read_meta(md_file) case_zip os.path.join(root, cases.zip) pid create_problem(token, meta, case_zip) print(f创建题目成功, ID: {pid})这个脚本的重点在files{test_case: zf}这一行它不仅把题面字段发过去还把测试数据 zip 作为文件一起提交后端会自动解压绑定。API路径、login接口、test_case字段名在不同发行版里可能有差异以你部署版本后端里的路由定义为准。脚本里read_meta只做了最基础的 title 提取真实题库包的 md 文件结构更复杂需要按实际格式调整解析逻辑。脚本跑完立刻登录后台抽查几道题确认题面渲染和数据绑定都正常。不要相信“脚本没有报错”就等于“题目都建对了”QDUOJ 对部分字段做了默认值处理漏传字段它不会报错只会悄悄用默认值。4. 导入后先别急着宣传用三条验证路径确认题目能判题库导完之后最怕的是学生打开 OJ 一提交全 WA然后整个题库被贴上“不能用”的标签。所以导入当天就要做一轮系统验证。我的经验是分三层样例冒烟、标程回归、参数核对。这三层都过了题目才算真正上线。4.1 样例冒烟进题目页确认题面渲染与样例输出第一层检查不需要写代码。登录一个普通用户账号随机打开几道题看题面 Markdown 是否正常渲染图片是否挂了输入输出格式有没有被挤成一行。然后手动复制题面里的样例输入写一个最简单的“照抄输出”程序提交看能否 AC。这一步对蓝桥杯的填空题特别重要。蓝桥杯里有不少“结果填空”题原题是让考生在答题纸上填一个数改成 OJ 题后需要选手提交一段直接输出答案的程序。这种题的样例输入往往是空的如果题库作者没处理QDUOJ 判题机可能因为读不到输入而异常。正确做法是给这类题放一个空的.in文件并在题面里写清楚“不需要读入任何内容直接输出答案”。冒烟时重点检查这类题提交一个print(答案)的代码能 AC 才算过。样例冒烟还有一个容易被忽略的细节样例输出末尾有没有多余的空格或换行。QDUOJ 默认的严格比较模式下行尾空白可能直接判 WA。如果原题 PDF 复制样例时多带了一个看不见的换行就会让所有学生栽在样例上。发现这种情况要么修正.out文件要么给题目开启忽略行尾空白的 SPJ。4.2 标程全判用题库自带的 AC 代码做回归样例冒烟只证明“样例能过”证明不了“数据能判”。更靠谱的做法是拿每题的标准程序提交一遍看是不是全 AC。修改版题库包里通常有一个solution或answer目录里面放着按题目编号组织的 AC 代码没带的话就从蓝桥杯公开题解里找一份可编译的参考实现。先在本地跑一遍标程确认它和样例数据对得上g -O2 -stdc17 solution/main.cpp -o /tmp/ac_prog /tmp/ac_prog data/1.in /tmp/out.txt diff /tmp/out.txt data/1.out echo case 1 passed这段命令用g编译标程然后把1.in作为输入运行输出重定向到/tmp/out.txt再用diff和标准答案比对。diff输出为空时表示一致。这里有个小坑本地diff对行尾空白是敏感的而在线 OJ 的判题机可能会忽略行尾空白。所以本地比完还得真的提交到 OJ 上以判题机判定为准。批量提交标程比较繁琐。常见做法是直接复用第 3 章的 API 脚本把提交接口也包一层逐题提交标程并记录判题结果。我自己一般只抽查每组的头尾题以及所有填空题和所有 SPJ 题覆盖关键类型即可。全量提交一遍更稳代价是多花几分钟我觉得值。4.3 判题参数核对时间/内存限制与蓝桥杯官方评测的偏差最后一层是核对判题参数。蓝桥杯练习系统对多数 C 题的时限是 1 秒、内存 256MB但题库作者手工录入时经常填错。最常见的是 Python 题时限不给足官方在蓝桥杯比赛里对 Python 往往有额外时长或倍率导入到 QDUOJ 后如果 time_limit 照抄 1000msPython 选手跑大数据很容易 TLE。需要重点调参的题有三类。第一类是模拟题和搜索题数据量稍大时 C 1 秒都悬Python 更不用说time_limit 至少按 2~3 倍放。第二类是结果填空题输出内容是固定的时限给 1000ms 甚至更短都行不用跟着原题跑。第三类是带 SPJ 的题要确认后台的 SPJ 开关真的打开了而不是只改了题目描述。参数核对完成后建议在题目列表页存一份截图或表格。后面如果有人报“某题 TLE”你能知道到底是代码问题还是题目配置问题不会像无头苍蝇一样到处查。5. 导入避坑编码乱码、题目错位、数据缺失与 SPJ 的常见翻车现场这部分写我踩过、也见过别人踩的坑。每一条都是“现象 → 原因 → 解决”的结构你导入时照着对照就行。5.1 Windows 打的包传到 Linux 解压乱码题面全是锟斤拷现象题库包在 Windows 上解压正常传到 Linux 服务器用unzip解开后文件名变成一堆乱码打开 Markdown 文件内容倒正常但文件名没法识别。原因压缩包在 Windows 上打包时文件名的编码用了 GBK而 Linux 的unzip默认按 UTF-8 解码。编码对不上文件名就乱了。解决解压时指定 GBK 编码。GNUunzip的新版支持-O参数unzip -O GBK lanqiao_qduoj.zip -d lanqiao_qduoj如果发行版的unzip不认-O可以用 Python 的zipfile模块在解压时转码。文件名编码问题用下面这段处理# fix_encoding.py # 解压时把 GBK 文件名转为 UTF-8 import zipfile import os with zipfile.ZipFile(lanqiao_qduoj.zip, r) as z: for info in z.infolist(): # zipfile 默认按 cp437 解码先还原字节再按 gbk 解 raw info.filename.encode(cp437) filename raw.decode(gbk) target os.path.join(lanqiao_qduoj, filename) if info.is_dir(): os.makedirs(target, exist_okTrue) else: os.makedirs(os.path.dirname(target), exist_okTrue) with z.open(info) as src, open(target, wb) as dst: dst.write(src.read())这段把压缩包内的每个文件按cp437 → gbk重新解码文件名并写出。注意cp437是 Python 的 zipfile 模块在非 Windows 平台上默认使用的伪编码把它还原成原始字节后再按 GBK 解码中文名就能恢复。这个问题处理完再走第 3 章的目录检查不要跳过。5.2 测试点顺序错乱字典序让 10.in 跑到了 2.in 前头现象题目 A 的测试数据里混着题目 B 的个别 case或者题目数据本身没问题但判题机总是先跑 10 号再跑 2 号导致某道依赖前序状态的题 WA。原因原题库作者整理数据时把不同来源的文件直接拼在一起文件名前缀不一致或者 zip 打包时用文件名字典序排序10.in排在了2.in前面。判题机按 zip 内的顺序或文件名顺序读取时case 归属和顺序就全乱了。解决导入前用脚本扫描所有题目目录检查.in和.out是否严格配对并且按数值编号重排。第 3.2 节给的pack_cases.py已经做了重命名这里补一个扫描脚本用来揪出“数据目录不完整”的题# scan_cases.py # 找出所有 .in/.out 数量不一致的题目目录 import os bad [] for root, _, files in os.walk(lanqiao_qduoj): ins [f for f in files if f.endswith(.in)] outs [f for f in files if f.endswith(.out)] if ins or outs: if len(ins) ! len(outs): bad.append((root, len(ins), len(outs))) for path, i, o in bad: print(f[不平衡] {path}: in{i}, out{o})这段的逻辑很简单如果一个目录下同时存在.in和.out数量必须相等。不等的目录会被打印出来你再人工决定是补还是删。跑完这个脚本目录层面的错位问题基本能杜绝。5.3 数据缺胳膊少腿只传样例的题库AC 了也不代表真会做现象题库导入后学生提交网上的标程能 AC但自己按题意写的代码也 AC实际比赛却拿不了满分。打开测试数据一看每组数据都是样例级别的弱数据。原因蓝桥杯官方不公开完整测试数据题库作者想补也无从下手于是很多版本只放了样例或三五组弱数据。这种题拿来练手可以拿来当模拟赛会严重失真。解决用对拍的方式自行补数据。常见做法是写一个暴力解和一个高效解跑同样输入对比输出不一致的输入就是边界 case人工确认后加入data目录。如果没有暴力解就用网上多个公开题解互相校验。补完的数据务必在题目 hint 里注明“非官方数据仅供练习”免得学生误以为这是蓝桥杯官方数据强度。另外要注意补数据时不要盲目追求大数据量。QDUOJ 判题机是逐点跑数据点太多会让一次提交耗时很长。每道题 8~20 组测试点比较合适兼顾覆盖面和判题速度。5.4 SPJ 题目裸奔结果不唯一却开了严格比较现象某道题的标准程序在本地输出正常提交到 QDUOJ 却 WA。题目要求的是“输出任意一种可行方案”比如迷宫路径、24 点解法只要方案合法就算对但 OJ 把它按标准答案严格要求了。原因QDUOJ 默认使用严格比较判题必须逐字节比对输出。蓝桥杯部分真题的方案并不唯一没有开启 SPJ。解决在后台打开该题的 Special Judge 开关并上传一个适合题目要求的 SPJ 程序。下面是一个忽略行尾空白、按 token 序列比较的 SPJ 最小实现适合“方案不唯一但输出内容都是单词/数字”的题// spj.cpp // 用法: 由 QDUOJ 判题机调用argv[2] 为标准输出argv[3] 为选手输出 #include bits/stdc.h using namespace std; int main(int argc, char *argv[]) { ifstream ans(argv[2]); ifstream out(argv[3]); string a, b; while (ans a) { // 标准输出有 token但选手没有或 token 不一致 WA if (!(out b) || a ! b) return 1; } // 选手输出多出 token WA if (out b) return 1; return 0; // AC }这段代码把标准输出和选手输出都按空白分隔成 token 逐个比较完全忽略空格和换行的差异。适合填空题、答案位置不固定的题。如果题目要求“包含某关键词即可”那需要你在 SPJ 里写包含检查而不是用这个通用版本。SPJ 编译方式在各版本 QDUOJ 里有差异上传前先拿一道题的标程和错解各测一次确认 SPJ 确实生效了再批量处理。5.5 老题目编译失败蓝桥杯 C 代码撞上 QDUOJ 的新编译环境现象一道几年前的老题学生在本地用老编译器写出来没问题提交到 QDUOJ 编译报错或者反过来网上找的标程用了 C17 的语法QDUOJ 默认编译器不支持。原因蓝桥杯老题按当年 C98/03 标准出题而 QDUOJ 判题镜像默认开了较新的标准同时新版编译器的头文件和语法解析更严格老代码里一些写法会直接报错。解决不要追求统一编译标准。给每组题在题目描述里标注推荐语言和标准比如“C14 提交”。如果某道题确实必须用老标准需要到判题服务器上给该题配置独立的编译命令这个配置在 QDUOJ 判题机的题目里通常有编译选项字段。学校场景下更省事的做法是把老题统一放进一个“历史真题”分组学生提交时语言选择 C 并自行规避新标准特性。这类问题最容易被误判成“题库坏了”其实题目数据没问题只是编译环境差异。排查时先看提交记录里的编译报错内容确认是语法错误还是数据错误再决定改题还是改环境。6. 把题库做成训练闭环分组、题解与新增题目的维护套路题库上线只是开始。实际带训练时一个只有题目、没有组织和沉淀的 OJ过两周就会变成“僵尸题库”。我自己的维护套路有三件事。第一件是三维打标。QDUOJ 后台支持题目分类和标签我一般按“年份 组别 题型”三套标签来挂比如“16届蓝桥杯省赛”“C组”“贪心”。这样排训练计划时直接按标签筛选不用在几百道题里翻。每届比赛结束后再把新题按第 3 章的流程补进去顺便记一笔“录入清单”題目来源、数据从哪里来、SPJ 开没开、谁验证过。这个清单用 Markdown 存在题库目录根下就行三个月后你还能对每道题负责。第二件是给题目沉淀题解。QDUOJ 的 hint 字段支持 Markdown我会把每题思路、复杂度、常见坑写进去而不只是贴代码。学生 AC 之后再看一眼 hint比刷三遍题都管用。要注意 hint 别写太长两三段加一段参考代码足够写多了反而没人看。第三件是定期给“重点题”加数据。我发现学生 WA 最多的题往往也是测试数据最弱的题。每个训练周期结束后把学生提交记录里 WA 次数高的题挑出来用对拍补数据。补数据时注意保留原数据在题目的 hint 里更新数据说明。我现在每届蓝桥杯省赛结束后第一周必做的一件事就是把当年真题按这套流程在本地 OJ 上重跑一遍该补数据的补数据该开 SPJ 的开 SPJ顺手把题解写进 hint。坚持两个赛季这个题库的质量就明显高于刚下载时的“修改版”慢慢会变成你们自己版本。如果你也是自己搭 OJ 带训练的人把这套检查固化下来后面维护会轻松很多。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网