做一個(gè)刷題小程序
共 9955字,需瀏覽 20分鐘
·
2024-12-02 08:21
今天為大家分享一篇比較有意思的文章,看看無所不能的程序員是如何滿足老婆的'刁鉆'需求的。^_^
以下是正文:
背景
老婆:難道你不能將機(jī)構(gòu)的pdf的試卷,轉(zhuǎn)換成駕考寶典一樣的刷題程序嗎?
我:女人你成功引起了我的注意,安排!巴啦啦能量,nodejs給我力量!
今年考公、考編的人真的多。我的老婆也報(bào)名了社工(哈哈哈也算是個(gè)岸吧)。報(bào)了機(jī)構(gòu)最后都有很多份考前密卷,但是這些密卷基本上都是pdf。
習(xí)慣了用類似駕考寶典一樣的刷題軟件,有錯(cuò)題集,有對(duì)應(yīng)解析。同時(shí)上班時(shí)間,晚上關(guān)燈,帶娃的時(shí)候都能刷題。
需求分析
基本需求
-
客戶端 我需要一個(gè)客戶端接受試卷數(shù)據(jù),用駕考寶典的方式展示,答題后展示解析,答錯(cuò)進(jìn)入錯(cuò)題集。
-
解析工具
第二我需要一個(gè)解析工具將pdf快速轉(zhuǎn)成我需要的數(shù)據(jù)結(jié)構(gòu),提供給小程序使用。 -
~?后端?~
為了盡快使用,我們直接將生成的數(shù)據(jù)放在前端就好了,不然還要再開發(fā)一個(gè)端,同時(shí)會(huì)把這個(gè)事情做的越來越復(fù)雜。
約定數(shù)據(jù)格式
interface ExamProps {
name: string; // 考卷信息
type: 1 | 2 | 3; // 1-綜合能力 2-法規(guī)政策 3-工作實(shí)務(wù)
score: number; // 滿分
time: number; // 考試時(shí)間,單位分鐘
question: {
type: 1 | 2 | 3; // 1-單選 2-多選 3-簡(jiǎn)答
options: {label: string; value: number}[]; // 選項(xiàng)
score: number; // 分?jǐn)?shù)
answer: number | number[] | string; // A-1 B-2 C-3 D-4 E-5 F-6 G-7 H-8
analysis?: string; // 答案解析
sort: number; // 題目編號(hào)
}[]
}
不同端方案
客戶端
可選擇:H5/PC/小程序/APP
什么端都行其實(shí),小程序和APP體驗(yàn)好一些,相對(duì)而言小程序安裝個(gè)人成本低。所以選擇小程序。
將對(duì)方加入你個(gè)人小程序的開發(fā)者或者體驗(yàn)者就好了,也不需要上線。
因?yàn)槲业目蛻舸笕诵枰氖鞘謾C(jī)刷題,如果你那邊是上班摸魚,選擇PC端最好
解析工具
-
方案一:手動(dòng)復(fù)制粘貼(臭不要臉!!!hetui) -
方案二:AI識(shí)別(不識(shí)別并羞辱了你??????) -
方案三:OCR技術(shù)(開整???)
技術(shù)方案當(dāng)然選擇前端秘技——node.js(關(guān)鍵我也不會(huì)別的啊)
PDF數(shù)據(jù)轉(zhuǎn)換
實(shí)現(xiàn)思路
-
PDF解析成圖片輸出 -
圖片文字識(shí)別后保存 -
識(shí)別內(nèi)容轉(zhuǎn)成對(duì)應(yīng)格式
和大象關(guān)進(jìn)冰箱里一樣簡(jiǎn)單??
從需求到代碼
PDF解析成圖片輸出
使用node-poppler進(jìn)行pdf操作
const { Poppler } = require('node-poppler');
const path = require('path');
const fs = require('fs');
// 創(chuàng)建 Poppler 實(shí)例
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轉(zhuǎn)行成圖片了,但是這是出現(xiàn)了一個(gè)難點(diǎn)!
難點(diǎn):答案頁,一張圖是左右排版的,ocr解析會(huì)有問題。
如圖所示:
所以需要對(duì)答案頁進(jìn)行裁剪保存輸出圖片,其實(shí)也簡(jiǎn)單
-
先獲取總的頁面數(shù)量
const pageCount = await getPdfPageCount(filePath);
-
規(guī)定需要裁剪的頁數(shù)
const answerPage = 13 + 1; // 封面 + 13頁(純?nèi)斯ぃ?nbsp;
-
裁剪函數(shù)
先輸出圖片,然后使用jimp依賴進(jìn)行圖片裁剪
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);
}
};
-
循環(huán)輸出
總結(jié):讀取pdf 頁數(shù),循環(huán)輸出每一頁的圖片,對(duì)答案解析頁進(jìn)行裁剪處理。
這就是我們得到的圖片了:
### 圖片文字識(shí)別后保存
因?yàn)镺CR是個(gè)直男,無差別識(shí)別,圖片上有什么文字都給你識(shí)別出來,不管你要不要(喊破喉嚨也沒用)。
所以我去探究了一下有道云的智能識(shí)別,可以劃分題目的位置等等。探究了一下,并不是特別的好用,而且免費(fèi)額度比較少,用不起。
不好用的點(diǎn):切分題目的只給位置信息不給題目信息,給信息的不切分。據(jù)我設(shè)想,需要根據(jù)位置信息,只能說需求沒對(duì)上吧~~~
所以直男入選,那么這一步就很簡(jiǎn)單了,就是識(shí)別然后保存下來,下一步再解析。
-
循環(huán)解析圖片并保存為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識(shí)別
const itemList = await sendRequest(url, appId, secretCode, imagePath);
allResults = allResults.concat(itemList?.map(item=> item.text));
}
// 將結(jié)果保存為 JSON 文件
const outputFilePath = path.join(outputDir, 'result.json');
fs.writeFileSync(outputFilePath, JSON.stringify(allResults, null, 2));
console.log(`All results saved to ${outputFilePath}`);
}
得到的數(shù)據(jù)如圖所示
-
ocr對(duì)接
用的是textin的ocr技術(shù) -
發(fā)送請(qǐng)求解析圖片
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 [];
}
}
總結(jié):我們已經(jīng)將pdf轉(zhuǎn)成圖片并且識(shí)別到了圖片中的所有文字(勝利在望)
最后一步我們就需要處理這些文字了
首先我們要發(fā)覺這些文字的規(guī)律,還有其中多余的文字,以及如何解析出題目、選項(xiàng)、關(guān)聯(lián)答案(答案是分開的)
規(guī)律如下:
-
1.去掉 第,${number}頁、第${number}頁這樣的文字 -
2.匹配到""參考答案"后進(jìn)入答案解析部分 -
3.每個(gè)題目前面都有序號(hào),選項(xiàng)在兩個(gè)題目之間 -
4.每個(gè)答案前面都有序號(hào),與題目序號(hào)相同
把這些規(guī)律交給GPT,讓GPT幫你寫一段解析代碼,輸出你想要的格式,這樣就大功告成了,
客戶端對(duì)各位大佬都是灑灑水啦就不班門弄斧了。
最后效果是這樣的:
這UI設(shè)計(jì)真是簡(jiǎn)約而不簡(jiǎn)單呢,哈哈哈
原文:https://juejin.cn/post/7397588804825022518
作者:可汗
