ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

科目一科目四题库SQL与JSON数据落地指南

科目一科目四题库SQL与JSON数据落地指南 简介这是一份面向驾考备考学员及驾考类应用开发者的多车型题库资源覆盖小车、客车、货车、摩托车涵盖科目一和科目四可用于刷题练习、模拟考试及题库系统二次开发适用场景灵活。压缩包共两千个文件大小约103MB包含一千九百九十五张WebP题目图片、两个SQL数据文件、两个JSON数据文件及一张GIF动图SQL便于直接导入数据库JSON适合程序化读取。题库总题量超过一万一千道按客车、小车、摩托车、货车四类车型分类组织并配有对应图片素材。目前已有1006人学习下载适合需要系统记忆交规知识或快速搭建题库模块的开发者。获取后可直接解析数据与图片省去手工整理题目和素材的时间便于直接投入练习也便于后续进行二次开发与功能扩展。1. 驾照科目一科目四题库这包 SQLJSON 数据能直接省掉你三天录题时间做驾考类 App、公众号答题小程序或者驾校内部考试系统的人都有个共同烦心事题库从哪来。网上搜到的题库要么是 PDF 截图要么散在网页 HTML 里要么字段命名乱七八糟根本没法直接入库。这套驾照考试科目一科目四题库给的是sql 表数据和 json 格式双份发放小车、客车、货车、摩托车四类车型的科目一和科目四全覆盖。它不是给你几百道题当练习用的而是按考试场景整理好的数据集加起来超过一万一千道题并且带了chapter章节表和几张图片素材。适合想跳过手工录题、直接扑到业务逻辑上的人——App 端开发者拿 JSON 做本地题库后端或做数据分析的拿 SQL 直接导库做驾考学习产品的能省下好几天体力活。下面我按自己拿到这套资源后的实际拆解过程来讲每一步都告诉你我为什么这么干以及在哪里翻过车。2. 表结构与题量先搞清 questions 和 chapter 两张表里藏着什么拿到压缩包第一件事不是急着导库而是先解压看文件。清单里是questions.json、chapter.json、questions.sql、chapter.sql外加几张 webp/gif 图片素材。光看文件名能猜到questions是题目主表chapter是章节分类表图片素材用于选项或题干的配图。但具体字段长什么样、两张表怎么关联得打开文件才能确认。2.1 questions 表字段设计与题量明细用文本编辑器打开questions.sql看到的建表语句大致是常见驾考题库的标准设计CREATE TABLE questions ( id int(11) NOT NULL AUTO_INCREMENT, type tinyint(4) DEFAULT NULL COMMENT 题型1单选 2判断 3多选, question text COMMENT 题干, option_a varchar(255) DEFAULT NULL, option_b varchar(255) DEFAULT NULL, option_c varchar(255) DEFAULT NULL, option_d varchar(255) DEFAULT NULL, answer varchar(10) DEFAULT NULL COMMENT 正确答案, explanation text COMMENT 解析, image_url varchar(255) DEFAULT NULL COMMENT 图片路径, chapter_id int(11) DEFAULT NULL COMMENT 所属章节ID, vehicle_type varchar(20) DEFAULT NULL COMMENT 车型car/bus/truck/motor, PRIMARY KEY (id), KEY idx_chapter_id (chapter_id), KEY idx_vehicle_type (vehicle_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;我打开看的时候实际字段大体就是这个套路type区分单选、判断和多选answer存正确答案字母explanation放解析chapter_id关联章节vehicle_type区分车型。这种设计在驾考类项目里几乎是事实标准因为科目四的“安全文明驾驶常识”确实有多选题单选和判断的答案格式也完全不同分开存后面做答题逻辑才方便。题量数据在摘要里写得很清楚我做成了一张对照表方便你按目标车型规划数据量车型科目一科目四小车16001300客车21542126货车21621206摩托车446383把四类车型全部加起来是 11377 道题其中小车最少、货车和客车的题量最大。这里有个很实在的选型判断如果你的产品只做 C1/C2 小车那 1600 题 1300 题就够覆盖考试大纲了如果做 B2/A2 这种职业资格类考试客车和货车的数据就必不可少。我的建议是整套入库vehicle_type字段做筛选成本极低别等产品要加车型时再回来补数据。另一个需要注意的细节是答案的存储形式。科目一里判断题的答案往往是“对/错”但在这套库里答案大概率是以A、B、C、D这种选项字母存的或者是正确/错误字符串。这个差异直接决定了你前端做题时的判分逻辑怎么写。我一般会在导完库后跑一条 SQL 看答案分布SELECT type, LEFT(answer, 1) AS first_char, COUNT(*) AS cnt FROM questions GROUP BY type, LEFT(answer, 1) ORDER BY type, cnt DESC;这条 SQL 的作用是把每个题型下答案的首字符分布拉出来。如果判断题的答案首字符只有 A/B说明判断题也被转成了选项制如果出现“对”“错”这类中文首字就得在判分逻辑里做单独分支。改判分逻辑的代价远大于改数据所以提前看清这一条能省掉后面模拟考试里一堆摸不着头脑的错判。2.2 chapter 表的层级结构与字段说明chapter.sql对应的chapter表设计上一般是一个树形结构因为驾考题目是按章节组织的比如“道路交通安全法律、法规和规章”“交通信号”等而章节下面可能还有子章节。常见的建表方式是CREATE TABLE chapter ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 章节名称, parent_id int(11) DEFAULT 0 COMMENT 父章节ID0为一级章节, sort int(11) DEFAULT 0 COMMENT 排序权重, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;打开实际文件后大概率能看到parent_id这个字段。一级章节的parent_id是 0子章节的parent_id指向上一级。这种设计的好处是你可以用一条递归查询把章节目录完整拉出来前端做章节练习时按树形菜单展示。如果你用的是 MySQL 8.0 以上版本可以直接用递归 CTECommon Table ExpressionWITH RECURSIVE chapter_tree AS ( SELECT id, name, parent_id, 1 AS depth FROM chapter WHERE parent_id 0 UNION ALL SELECT c.id, c.name, c.parent_id, ct.depth 1 FROM chapter c INNER JOIN chapter_tree ct ON c.parent_id ct.id ) SELECT * FROM chapter_tree ORDER BY depth, id;这里我解释一下为什么推荐递归 CTE 而不是在业务代码里循环查父节点题库的章节层级通常就两到三层循环查也跑得动但递归一条 SQL 出结果代码更干净。depth字段是我加的用来标识层级深度方便前端做缩进展示。如果你的 MySQL 是 5.7 及以下版本递归 CTE 不可用就用程序循环先查所有parent_id 0的顶级章节再逐个查子章节两层循环搞定数据量小性能上没有任何压力。3. 落地导入两个动作SQL 直接进 MySQLJSON 交给程序按需拆数据文件本身不能产生价值导进你自己的库或者被业务代码读走才算落地。这一章我拆三条路径第一条是拿 SQL 文件直接导入 MySQL立竿见影第二条是拿 JSON 文件用 Python 或 Node.js 读出来转成自己的表结构第三条是 SQL 和 JSON 互转应对“资源是 SQL 但客户端要 JSON”这种常见尴尬。3.1 路径 A直接把 questions.sql 导进 MySQL这是最省事的路前提是你本机或服务器已经有 MySQL 服务。我的操作顺序是先建库再指定字符集然后 source 导入mysql -u root -p进入 MySQL 命令行后执行CREATE DATABASE IF NOT EXISTS driving_exam DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE driving_exam; SOURCE /your/path/questions.sql; SOURCE /your/path/chapter.sql;为什么先建库而不是直接 source因为questions.sql里不一定包含CREATE DATABASE语句如果你直接source到默认库表会落到mysql系统库里后面管理起来非常别扭别问我怎么知道的这种低级错误我犯过一次清理半天。utf8mb4是必须要指定的因为题目和解析里全是中文数据库默认latin1的话导进去就是乱码后面排查到怀疑人生。导入完成后立刻做一次行数校验SELECT vehicle_type, COUNT(*) FROM questions GROUP BY vehicle_type; SELECT COUNT(*) FROM chapter;对照摘要里的题量小车科目一 1600、科目四 1300客车 21542126货车 21621206摩托车 446383。如果数字对得上说明 SQL 文件完整如果对不上先看是不是导了重复数据再看是不是文件本身缺内容。这一步花不了三十秒但能让你心里有底。3.2 路径 B用 Python 读 JSON 并导入你自己的表结构如果你的项目不是 MySQL或者表结构跟原文件差异很大直接读questions.json是更灵活的方式。JSON 文件的优点是结构一目了然缺点是中文内容如果被转义成了\uXXXX肉眼可读性极差。解决方法是带ensure_asciiFalse读入输出import json with open(questions.json, r, encodingutf-8) as f: questions json.load(f) print(f题目总数: {len(questions)}) print(questions[0])这里json.load(f)会把整个 JSON 文件解析成 Python 的 listquestions[0]是第一道题的字典结构。如果你打开文件看到的是\u5c0f\u8f66这种格式别慌这是 JSON 标准转义json.load会自动还原成中文。打印出来后你会看到每道题的字段结构然后就能按自己的表结构写插入逻辑import json import pymysql with open(questions.json, r, encodingutf-8) as f: questions json.load(f) conn pymysql.connect( host127.0.0.1, userroot, passwordyour_password, databasedriving_exam, charsetutf8mb4 ) cursor conn.cursor() for q in questions: cursor.execute( INSERT INTO questions (type, question, option_a, option_b, option_c, option_d, answer, explanation, chapter_id, vehicle_type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s), (q.get(type), q.get(question), q.get(option_a), q.get(option_b), q.get(option_c), q.get(option_d), q.get(answer), q.get(explanation), q.get(chapter_id), q.get(vehicle_type)) ) conn.commit() cursor.close() conn.close() print(导入完成)这段代码里有个关键点我用q.get()而不是q[xxx]因为 JSON 里可能有字段缺失get()方法在键不存在时返回None而不是抛KeyError这对脏数据容忍度高很多。pymysql的charsetutf8mb4必须写不写的话连接字符集可能是latin1中文写进库就是乱码。另外如果你是批量导入最好用executemany做批量插入我这里为了演示可读性用了单条循环实际生产你可以把questions列表分批切片每批 500 条executemany速度能快一个数量级。3.3 路径 CSQL 反向导出成 JSON满足客户端的本地题库需求不少同学实际遇到的问题是资源给的是questions.sql但客户端是 React Native 或离线包需要一份 JSON 直接打进 App。这就要把 MySQL 数据导回 JSON。我用 Python 从库读取再json.dumpimport pymysql import json conn pymysql.connect( host127.0.0.1, userroot, passwordyour_password, databasedriving_exam, charsetutf8mb4 ) cursor conn.cursor(pymysql.cursors.DictCursor) cursor.execute(SELECT id, type, question, option_a, option_b, option_c, option_d, answer, explanation, chapter_id, vehicle_type FROM questions WHERE vehicle_typecar) rows cursor.fetchall() with open(car_questions.json, w, encodingutf-8) as f: json.dump(rows, f, ensure_asciiFalse, indent2) cursor.close() conn.close() print(f导出 {len(rows)} 道题)DictCursor是这里最值得说的参数普通Cursor返回 tuple你得靠位置索引取字段改字段顺序就崩DictCursor返回字典rows直接就是 list of dict转 JSON 零成本。ensure_asciiFalse保证文件里存的是真正的中文字符而不是\uXXXX转义序列否则前端拿到的 JSON 是满屏乱码式的转义调试体验极差。indent2是为了让人眼能直接检查导出结果调试完可去掉以减小体积。4. 图片素材接入webp/gif 与题目 ID 的对齐方案这套资源里的图片素材是1542-1694068096616.gif、1543-1694068140126.webp、1544-1694068188609.webp、1539-1694067980751.webp、2163-1694068510206.webp、2192-1694068475659.webp这种命名风格。一眼能看出来前缀像数字 ID后面的 13 位数字是标准的毫秒级时间戳。这说明图片大概率是按某个业务 ID 生成的文件名。我拿到后第一反应是这可能是示范性的图片素材并不是每道题都有对应配图。这也符合很多题库资源包的现状——图片素材给几个样例让你知道格式和命名规则真正的全量配图靠业务方自己去搜集版权合规的素材。4.1 图片文件命名推断与批量校验先做一步最基础的把图片文件名解析一下看能不能跟questions表的 ID 对上。我写了个小脚本import os import re import pymysql pattern re.compile(r^(\d)-\d{13}\.(gif|webp|png|jpg)$, re.IGNORECASE) for filename in os.listdir(image_dir): m pattern.match(filename) if m: pid int(m.group(1)) print(f{filename} - 疑似题目ID: {pid}) else: print(f{filename} - 文件名格式异常)脚本做什么把文件名拆成“前缀数字”和“时间戳”两部分前缀数字输出出来。然后拿这些 ID 去questions表里查看是否存在对应题目SELECT id, question FROM questions WHERE id IN (1542, 1543, 1544, 1539, 2163, 2192);如果查到了对应题目说明图片前缀就是题目 ID你的image_url字段可以直接填文件名如果查不到说明前缀不是题目 ID可能是图片自己的序号你就得放弃自动匹配改成人工标注或用别的关联字段。这个判断非常重要因为它决定了你图片接入是“批量自动化”还是“人工对号”。4.2 图片目录搭建与前端引用策略确认命名规则后图片要放到能被前端访问的位置。我的常见做法是在项目里建static/images/questions/目录图片丢进去image_url字段只存文件名。后端接口返回时拼完整路径BASE_URL https://your-domain.com/static/images/questions/ def to_question_dict(q): if q.get(image_url): q[image_url] BASE_URL q[image_url] return q前端拿到image_url如果是完整 URL直接塞进img标签就完事了。这里有个细节值得注意如果是 webp 格式老版本 iOS SafariiOS 14 以下对 webp 支持有瑕疵某些版本会出现不显示的问题。最简单的兼容方案是后端返回时带一个格式检测把 webp 转成 JPEG 再输出或者在前端picture标签里提供多种格式。驾考类 App 的用户群年龄跨度大老机型占比不低为了这个翻车完全不值得。如果你的后端用 Nginx 托管静态文件路径配置大概是这样location /static/images/ { alias /var/www/driving_exam/images/; expires 7d; add_header Cache-Control public; }expires 7d是给图片做浏览器缓存因为题目配图基本不会变这个缓存策略能显著减少重复请求。驾考题目的图片真题里有不少是交通标志和路面实景尺寸一般不大缓存收益很可观。4.3 图片缺失的兜底策略与占位方案拆到这一步你大概率会发现图片素材就是几张而题库是全量的。那些没有配图的题目前端拿到image_url为NULL你不能让img标签显示一个裂开的图标。我的处理方式是接口里对image_url为空的题目直接不返回该字段前端判断如果没这个字段就不渲染图片容器而不是渲染一个空白框。// 前端渲染判断 function renderQuestion(q) { if (q.image_url) { return img src${q.image_url} onerrorthis.style.displaynone / q.question; } return q.question; }onerrorthis.style.displaynone是最后一道防线哪怕后端返回了图片地址但文件实际不存在前端也会静默隐藏而不是展示破图。这样做的好处是不影响做题流程。驾考 App 里图片加载失败导致题目卡住这是会让用户直接卸载 App 的体验灾难所以后端校验兜底 前端onerror兜底两层保险缺一不可。5. 常见问题排查导库失败、中文乱码与图片失配这套资源本身问题不大但导入到自己环境时翻车点基本集中在字符集、SQL 语法兼容性、JSON 转义和图片匹配四块。我把自己遇到过的和身边同事踩过的坑整理成现象记录每一条都是“现象 → 原因 → 解决”的结构你可以直接对照自己的报错来查。5.1 现象source 导入报 ERROR 1064 语法错误MySQL 执行SOURCE questions.sql时直接报ERROR 1064 (42000): You have an error in your SQL syntax或者提示某个关键字不对。我第一次遇到时很懵明明 SQL 文件看起来没问题后来才发现原因MySQL 8.0 和 5.7 对某些语法和默认值的处理不一样。比如 5.7 里DEFAULT CHARSETutf8mb4没问题但有些老版本生成的 SQL 里会用ENGINEMyISAM或者带TYPEInnoDB这种过时写法在新版本里直接报错。解决方式一是用文本编辑器打开questions.sql看开头有没有明显的版本特征二是在 MySQL 5.7 环境导入报错时换成 MySQL 8.0 再试或者反过来。如果手头没有多版本 MySQL可以用 Docker 快速起一个 8.0 容器做导入验证docker run --name mysql8 -e MYSQL_ROOT_PASSWORDroot -d -p 3307:3306 mysql:8.0-p 3307:3306把宿主机的 3307 端口映射到容器的 3306这样你可以不干扰本机已有 MySQL 的情况下做测试。导入成功后把数据用mysqldump导出一个兼容版本再导入生产库。5.2 现象导完库中文全部变成问号或乱码这是题库类资源最高频的翻车现场。questions表导入后查询题干和选项里的中文变成???或者馃挕这种乱七八糟的字符。原因几乎都是同一个链条建库时字符集不是 utf8mb4或者导入时连接字符集没指定。解决方式是建库显式指定CREATE DATABASE driving_exam DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;导入前在 MySQL 命令行执行SET NAMES utf8mb4;SET NAMES utf8mb4是告诉服务器“客户端发来的数据是 utf8mb4 编码”如果漏了这句即使表字符集正确导入时也可能被转错。另外如果你用命令行直接复制粘贴 SQL 内容而不是SOURCEWindows 终端默认编码可能是 GBK复制粘贴时中文就会串味所以务必用SOURCE读文件而不是肉眼复制。5.3 现象用 Python 读 JSON 发现中文都变成 \uXXXX打开questions.json看到满屏\u5c0f\u8f66第一反应是“文件坏了”实际上这完全是正常的。JSON 规范允许用 Unicode 转义序列表示字符很多导出工具默认就开ensure_asciiTrue把中文转成 ASCII 安全的转义序列。数据本身一点没坏只要用标准 JSON 解析器读入中文就会还原。解决方法是别用文本编辑器读原始 JSON直接写个 Python 脚本输出检查with open(questions.json, r, encodingutf-8) as f: data json.load(f) print(data[0][question])json.load会做转义还原打印出来的就是正常中文。如果你确实想让文件本身可读重新json.dump一次并指定ensure_asciiFalse即可。这个坑主要坑在“用编辑器肉眼检查数据”的人身上代码层面完全不是问题。5.4 现象题目图片跟题干对不上有些题目给出了image_url打开图片后发现内容跟当前题目牛头不对马嘴。原因多半是图片文件名用的是图片 ID 而非题目 ID或者资源包的图片来自不同批次ID 发生了偏移。我前面在 4.1 节讲过校验办法这里再强调一次一定要先做 ID 关联验证再批量接入而不是想当然地“前缀是数字那肯定就是题目 ID”。解决方式如果确认前缀不是题目 ID那就只能放弃自动匹配。更稳的做法是用图片内容做反向验证把每张图片涉及的考点关键词跟题干比对。但这工作量太大一般资源包里给的图片素材就是个样式参考建议直接按“无图模式”处理用 4.3 节的兜底方案隐藏图片不为难自己。6. 进阶玩法按车型按章节抽题做成随机模拟考试题库落地之后最核心的业务场景就是模拟考试。科目一考试是 100 道题科目四一般是 50 道题需要从全库中随机抽题并且保证题不重复。这一章我给出一套按车型、按章节比例抽题的实现思路外加验证方法。先看简单的随机抽题 SQLSELECT * FROM questions WHERE vehicle_type car ORDER BY RAND() LIMIT 100;ORDER BY RAND()在小数据量下没问题但一万人同时模拟考试这种写法会让 MySQL 把所有候选行排序然后取前 100数据库压力非常大医院挂号系统这么干早被骂死了。更稳的方案是分两步先从 ID 范围随机取 100 个 ID再根据 ID 取题目import pymysql import random conn pymysql.connect(host127.0.0.1, userroot, passwordyour_password, databasedriving_exam, charsetutf8mb4) cursor conn.cursor(pymysql.cursors.DictCursor) # 第一步查出该车型可用的题目ID cursor.execute(SELECT id FROM questions WHERE vehicle_typecar) all_ids [row[id] for row in cursor.fetchall()] # 第二步随机取100个不重复ID exam_ids random.sample(all_ids, 100) # 第三步按ID批量取题 placeholders ,.join([%s] * len(exam_ids)) cursor.execute(fSELECT * FROM questions WHERE id IN ({placeholders}), exam_ids) questions cursor.fetchall() # 第四步打乱顺序呈现给用户 random.shuffle(questions)random.sample(all_ids, 100)保证不重复取样比多次RAND()取数然后判重快得多。questions拿到后还要做一次顺序打乱因为IN查询返回的顺序是按索引或主键的不是随机顺序你不打乱的话每场考试的前几题永远是一样的。进阶一点可以按chapter_id的比例抽题。比如模拟考要覆盖全部章节且章节 1 出 10 题、章节 2 出 15 题那就先查每个章节的题目池再按比例随机取# 假设 weighted_plan [{chapter_id: 1, count: 10}, {chapter_id: 2, count: 15}, ...] exam_ids [] for plan in weighted_plan: cursor.execute( SELECT id FROM questions WHERE vehicle_typecar AND chapter_id%s, (plan[chapter_id],) ) chapter_ids [row[id] for row in cursor.fetchall()] exam_ids.extend(random.sample(chapter_ids, plan[count]))遇到某个章节题量不足count的情况random.sample会抛ValueError所以抽前要先判断len(chapter_ids) plan[count]不够就从相近章节补或者从该章节池子里全量取。这是模拟考试系统里非常容易被忽略的边界但一上线就会遇到。验证这卷子是否合理我一般做三件事查题号是否重复、查每题是否属于目标车型、查章节覆盖率是否达到预期SELECT COUNT(*) AS dup_count FROM ( SELECT id FROM questions WHERE id IN (...) AND vehicle_typecar GROUP BY id HAVING COUNT(*) 1 ) t;这种验证脚本建议直接固化成一个函数每次生成试卷后自动跑一遍。说句实在话题库类项目里用户最敏感的就是“做到一道重复题”和“题目跟车型不匹配”这两个问题一旦出现用户对产品的信任度直接归零。我从那以后每次接题库类资源都强制先跑一遍完整性校验再谈业务开发——数据底子是 1功能都是后面的 0。希望这次的拆解能帮到你题库类资源拿到手先做格式确认、入库校验、图片关联这三步后面会省心很多。本文还有配套的精品资源点击获取
返回列表