点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群
今天为大家分享一篇比较有意思的文章,看看无所不能的程序员是如何满足老婆的'刁钻'需求的。^_^
以下是正文:
背景
老婆:难道你不能将机构的pdf的试卷,转换成驾考宝典一样的刷题程序吗?
我:女人你成功引起了我的注意,安排!巴啦啦能量,nodejs给我力量!
今年考公、考编的人真的多。我的老婆也报名了社工(哈哈哈也算是个岸吧)。报了机构最后都有很多份考前密卷,但是这些密卷基本上都是pdf。
习惯了用类似驾考宝典一样的刷题软件,有错题集,有对应解析。同时上班时间,晚上关灯,带娃的时候都能刷题。
需求分析
基本需求
客户端 我需要一个客户端接受试卷数据,用驾考宝典的方式展示,答题后展示解析,答错进入错题集。
解析工具
第二我需要一个解析工具将pdf快速转成我需要的数据结构,提供给小程序使用。~❌后端❌~
为了尽快使用,我们直接将生成的数据放在前端就好了,不然还要再开发一个端,同时会把这个事情做的越来越复杂。
约定数据格式
interface ExamProps {
name: string; // 考卷信息
type: 1 | 2 | 3; // 1-综合能力 2-法规政策 3-工作实务
score: number; // 满分
time: number; // 考试时间,单位分钟
question: {
type: 1 | 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 = {
firstPageToConvert: 1,
lastPageToConvert: 1,
pngFile: true,
};
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(0, 0, 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, null, 2));
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帮你写一段解析代码,输出你想要的格式,这样就大功告成了,
客户端对各位大佬都是洒洒水啦就不班门弄斧了。
最后效果是这样的:
这UI设计真是简约而不简单呢,哈哈哈
原文:https://juejin.cn/post/7397588804825022518
作者:可汗
我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。
“分享、点赞、在看” 支持一波👍