老婆:去给我做一个刷题工具!(Node 实现)

科技   2024-11-21 08:45   北京  

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群


哈喽,大家好,我是考拉🐨。

今天为大家分享一篇比较有意思的文章,看看无所不能的程序员是如何满足老婆的'刁钻'需求的。^_^

以下是正文:

背景

老婆:难道你不能将机构的pdf的试卷,转换成驾考宝典一样的刷题程序吗?

我:女人你成功引起了我的注意,安排!巴啦啦能量,nodejs给我力量!

今年考公、考编的人真的多。我的老婆也报名了社工(哈哈哈也算是个岸吧)。报了机构最后都有很多份考前密卷,但是这些密卷基本上都是pdf。

习惯了用类似驾考宝典一样的刷题软件,有错题集,有对应解析。同时上班时间,晚上关灯,带娃的时候都能刷题。

需求分析

基本需求

  • 客户端 我需要一个客户端接受试卷数据,用驾考宝典的方式展示,答题后展示解析,答错进入错题集。

  • 解析工具
    第二我需要一个解析工具将pdf快速转成我需要的数据结构,提供给小程序使用。

  • ~❌后端❌~
    为了尽快使用,我们直接将生成的数据放在前端就好了,不然还要再开发一个端,同时会把这个事情做的越来越复杂。

约定数据格式

interface ExamProps {
    name: string// 考卷信息  
    type1 | 2 | 3// 1-综合能力 2-法规政策 3-工作实务  
    score: number// 满分  
    time: number// 考试时间,单位分钟  
    question: {  
        type1 | 2 | 3// 1-单选 2-多选 3-简答  
        options: {label: string; value: number}[]; // 选项  
        score: number// 分数  
        answer: number | number[] | string// A-1 B-2 C-3 D-4 E-5 F-6 G-7 H-8  
        analysis?: string// 答案解析  
        sort: number// 题目编号  
    }[]  
}  

不同端方案

客户端

可选择:H5/PC/小程序/APP

什么端都行其实,小程序和APP体验好一些,相对而言小程序安装个人成本低。所以选择小程序。

将对方加入你个人小程序的开发者或者体验者就好了,也不需要上线。

因为我的客户大人需要的是手机刷题,如果你那边是上班摸鱼,选择PC端最好

解析工具

  • 方案一:手动复制粘贴(臭不要脸!!!hetui)
  • 方案二:AI识别(不识别并羞辱了你😜😜😜)
  • 方案三:OCR技术(开整✨✨✨)

技术方案当然选择前端秘技——node.js(关键我也不会别的啊)

PDF数据转换

实现思路

  • PDF解析成图片输出
  • 图片文字识别后保存
  • 识别内容转成对应格式

和大象关进冰箱里一样简单😆

从需求到代码

PDF解析成图片输出

使用node-poppler进行pdf操作

const { Poppler } = require('node-poppler');  
const path = require('path');  
const fs = require('fs');  
  
// 创建 Poppler 实例  
const poppler = new Poppler();  
const pdfName = '1';  
const filePath = path.join(__dirname, 'pdf'`${pdfName}.pdf`);  
// 输出图片路径目录  
const outputDir = path.join(__dirname, 'output'`${pdfName}pdf`);  
const outputFilePath = path.join(outputDir, `page`);  
  
const options = {  
    firstPageToConvert1,  
    lastPageToConvert1,  
    pngFiletrue,  
};  
await poppler.pdfToCairo(filePath, outputFilePath, options);  

这样我们就将指定页面的pdf转行成图片了,但是这是出现了一个难点!

难点:答案页,一张图是左右排版的,ocr解析会有问题。

如图所示:

所以需要对答案页进行裁剪保存输出图片,其实也简单

  • 先获取总的页面数量
const pageCount = await getPdfPageCount(filePath);  
  • 规定需要裁剪的页数
const answerPage = 13 + 1// 封面 + 13页(纯人工)  
  • 裁剪函数

先输出图片,然后使用jimp依赖进行图片裁剪

const Jimp = require('jimp');  
  
const cropImage = async (imagePath, pageIndex) => {  
try {  
    const image = await Jimp.read(`${imagePath}-${pageIndex}.png`);  
    const halfWidth = Math.floor(image.bitmap.width / 2);  

    // 左半部分裁剪  
    await image  
            .clone()  
            .crop(00, halfWidth, image.bitmap.height)  
            .writeAsync(path.join(outputDir, `page-${pageIndex}-1.png`));  

    // 右半部分裁剪  
    await image  
            .clone()  
            .crop(halfWidth, 0, halfWidth, image.bitmap.height)  
            .writeAsync(path.join(outputDir, `page-${pageIndex}-2.png`));  

    console.log(`页面 ${pageIndex} 裁剪成功`);  
    // 删除原图  
    fs.rmSync(`${imagePath}-${pageIndex}.png`)  
catch (err) {  
    console.error(`页面 ${pageIndex} 裁剪失败:`, err);  
}  
};  
  • 循环输出

总结:读取pdf 页数,循环输出每一页的图片,对答案解析页进行裁剪处理。

这就是我们得到的图片了:

### 图片文字识别后保存

因为OCR是个直男,无差别识别,图片上有什么文字都给你识别出来,不管你要不要(喊破喉咙也没用)。

所以我去探究了一下有道云的智能识别,可以划分题目的位置等等。探究了一下,并不是特别的好用,而且免费额度比较少,用不起。

不好用的点:切分题目的只给位置信息不给题目信息,给信息的不切分。据我设想,需要根据位置信息,只能说需求没对上吧~~~

所以直男入选,那么这一步就很简单了,就是识别然后保存下来,下一步再解析。

  • 循环解析图片并保存为json
const fs = require('fs');  
const path = require('path');  
const pdfName = '1';  
const url = 'https://api.textin.com/ai/service/v2/recognize';  
const appId = 'testAppId';  
const secretCode = 'testSecretCode';  
// 图片文件夹路径  
const inputDir = path.join(__dirname, `output/${pdfName}pdf`);  
const outputDir = path.join(__dirname, `output/${pdfName}pdf`);  
  
async function processImages({  
const imageFiles = fs.readdirSync(inputDir).filter(file => /\.(jpg|jpeg|png|bmp)$/i.test(file));  
  
let allResults = [];  
  
for (const file of imageFiles) {  
    const imagePath = path.join(inputDir, file);  
    console.log(`Processing ${imagePath}...`);  
    // ocr识别  
    const itemList = await sendRequest(url, appId, secretCode, imagePath);  
    allResults = allResults.concat(itemList?.map(item=> item.text));  
}  
  
// 将结果保存为 JSON 文件  
const outputFilePath = path.join(outputDir, 'result.json');  
  
fs.writeFileSync(outputFilePath, JSON.stringify(allResults, null2));  
console.log(`All results saved to ${outputFilePath}`);  
}  

得到的数据如图所示

  • ocr对接
    用的是textin的ocr技术

  • 发送请求解析图片

async function sendRequest(url, appId, secretCode, imagePath{  
try {  
    const image = fs.readFileSync(imagePath);  
    const response = await axios.post(url, image, {  
        headers: {  
            'Content-Type''application/octet-stream',  
            'x-ti-app-id': appId,  
            'x-ti-secret-code': secretCode  
        }  
    });  

    if (response.status === 200) {  
        const ret = response.data;  
        return ret.result ? ret.result.lines : [];  
    } else {  
        console.error(`Request failed with status code ${response.status}`);  
        return [];  
    }  
catch (error) {  
    console.error('Request failed:', error);  
    return [];  
}  
}  

总结:我们已经将pdf转成图片并且识别到了图片中的所有文字(胜利在望)

最后一步我们就需要处理这些文字了

首先我们要发觉这些文字的规律,还有其中多余的文字,以及如何解析出题目、选项、关联答案(答案是分开的)

规律如下:

  • 1.去掉,${number}页第${number}页这样的文字
  • 2.匹配到""参考答案"后进入答案解析部分
  • 3.每个题目前面都有序号,选项在两个题目之间
  • 4.每个答案前面都有序号,与题目序号相同

把这些规律交给GPT,让GPT帮你写一段解析代码,输出你想要的格式,这样就大功告成了,

客户端对各位大佬都是洒洒水啦就不班门弄斧了。

最后效果是这样的:

image.png

这UI设计真是简约而不简单呢,哈哈哈

原文:https://juejin.cn/post/7397588804825022518

作者:可汗

Node 社群


我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

   “分享、点赞在看” 支持一波👍

程序员成长指北
专注 Node.js 技术栈分享,从 前端 到 Node.js 再到 后端数据库,祝您成为优秀的高级 Node.js 全栈工程师。一个有趣的且乐于分享的人。座右铭:今天未完成的,明天更不会完成。
 最新文章