如何使用Python从PDF中提取文本并转换为Markdown文本的实操

之前实现了对注册测绘师综合真题PDF转换为文本数据。但是这些文本数据,不能直接导入到数据库中的。

而本文针对这个问题,进行展开。最终实现注册测绘师真题文本处理与答案高亮。

数据结构

首先理清楚数据结构。比如以下这3道题:

1.使用N台(N>3)GPS接收机进行同步观测所获得的GPS边中,独立的GPS边的数量是
( )。
A、N B、N-1 C、N(N+1)/2 D、N(N-1)/2
答案:B

2.我国现行的大地原点、水准原点分别位于( )。
A、北京、浙江坎门 B、北京、山东青岛 C、山西泾阳、浙江坎门 D、陕西泾阳、山东青岛
答案:D

3.大地水准面精化工作中,A、B级GPS观测应采用( )定位模式。
A、静态相对 B、快速静态相对 C、准动态相对 D、绝对
答案:A

需要把以上的文本分为

1.题目

2.选项

3.答案

对于文字的处理,我们通常用到正则式re。re是一个非常强大的库,它又简单又困难。简单是指它容易使用;困难是指如果要实现指定功能,它就变得很困难。

反正通过re,我们可以实现以上需求。

def parse_text(text):
    '''
    用于2011年到2021年测绘真题
    :param text: 文本
    :return: 题目选项答案的列表
    '''
    text = text.replace('、', '.')
    # 正则表达式,用于分割每个问题的段落
    problem_pattern = re.compile(r'(\d+\..*?(?=^(\d+\.|$)))', re.MULTILINE | re.DOTALL)

    # 正则表达式,用于在每个问题的段落中提取题目、选项、答案和解析
    all_pattern =            re.compile(r'^(.*?)\s*(A[.,\、]*.*?E[.,\、]*.*?)(答案:[\w, ]+)(解析:.*)', re.DOTALL)
    no_explanation_pattern = re.compile(r'^(.*?)\s*(A[.,\、]*.*?D[.,\、]*.*?)(答案:[\w, ]+)', re.DOTALL)

    problems = problem_pattern.findall(text)
    parsed_problems = []
    i = 0
    for problem in problems:
        problem = list(problem)[0]
        match_all = all_pattern.match(problem)
        match_no_explanation = no_explanation_pattern.match(problem)

        match = match_all if match_all else match_no_explanation
        i+=1
        if i == 30:
            print()
        if match:
            question = match.group(1).strip()

            #  修改后的正则表达式,确保选项前有空格或\n,并且紧跟一个大写字母和“、”
            origin_option = match.group(2).replace('\n', ' ').strip()
            options_pattern = re.compile(r'(?<=\s|\n)[A-F][.,\、]*\s*[^答案]+')
            options_pattern = re.compile(r'(?<=\s|\n)[A-F][.,\、]\s*\S.*?(?= [A-F]|$)')
            options_pattern = re.compile(r'[A-F][.,\、]*\s*\S.*?(?=\S[A-F]|$)')

            options_matches = options_pattern.findall(origin_option)

            options = [option.strip() for option_match in options_matches for option in option_match.split()]

            answer = match.group(3).replace('答案:', '').strip().replace(' ', '')
            explanation = match.group(4).strip() if match_all else ''

            parsed_problems.append({
                'question': question.replace('\n',''),
                'options': options,
                'answer': answer,
                'explanation': explanation
            })

    return parsed_problems

测试如下:

[{'question': '1.使用N台(N>3)GPS接收机进行同步观测所获得的GPS边中,独立的GPS边的数量是( )。', 'options': ['A.N', 'B.N-1', 'C.N(N+1)/2', 'D.N(N-1)/2'], 'answer': 'B', 'explanation': ''}, 
 {'question': '2.我国现行的大地原点.水准原点分别位于( )。', 'options': ['A.北京.浙江坎门', 'B.北京.山东青岛', 'C.山西泾阳.浙江坎门', 'D.陕西泾阳.山东青岛'], 'answer': 'D', 'explanation': ''},
 {'question': '3.大地水准面精化工作中,', 'options': ['A.B级GPS观测应采用(', ')定位模式。', 'A.静态相对', 'B.快速静态相对', 'C.准动态相对', 'D.绝对'], 'answer': 'A', 'explanation': ''}]

image-20240730162608408

给选项附上颜色

没附上颜色前的文本如下:

image-20240730163112081

附上颜色前的文本如下

image-20240730163139814

需求分析

1.把纯文本进行数据分类

2.按照正确答案,给选项附上颜色

3.批量处理2011年到2022年的注册测绘师综合真题

下面附上python代码,对相关知识点感兴趣的话,自行看下面的代码。

全部代码

#!/usr/bin/env python
# -*- coding: utf-8 -*- 
# @File : highlight_correct_answers.py 
'''

'''
import os
from read_pdf import get_file_name
import time
import re
import  random

index = {
    'A': 0,
    'B': 1,
    'C': 2,
    'D': 3,
    'E': 4,
    'F': 8
}


def generate_random_color():
    r = random.randint(0, 255)
    g = random.randint(0, 255)
    b = random.randint(0, 255)
    return f'#{r:02x}{g:02x}{b:02x}'


colors = [generate_random_color() for _ in range(100)]  # 生成100种随机颜色
def extract_options(options_str):
    # 假设options_str的格式为'A.N,B.N-1,C.N(N+1)/2,D.N(N-1)/2,E.其他内容'
    # 如果没有E,则只处理到D
    options_list = options_str.rstrip().split(' ')

    # 初始化一个字典来存储选项
    options_dict = {}

    # 遍历选项列表
    for option in options_list:
        # 分割选项为字母和内容
        letter, content = option.split('.', 1)
        options_dict[letter.strip()] = content.strip()

        # 如果没有E,则只取A, B, C, D
    # 如果有E,则直接返回所有选项
    if 'E' in options_dict:
        # 如果需要,可以在这里对options_dict进行进一步处理或返回
        return options_dict
    else:
        # 提取A, B, C, D
        return {k: v for k, v in options_dict.items() if k in ['A', 'B', 'C', 'D']}

def read_md_file(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            content = file.read()
            return content
    except FileNotFoundError:
        print(f"文件 {file_path} 未找到。")
        return None
def split_text(text):

    # 正则表达式用于匹配题目、选项、答案和解析
    pattern = r'(?P<question>\d+\..*?\n(?:A|B|C|D).*\n)答案:(?P<answer>[A-D])\n解析:(?P<explanation>.*?(?=\n\d+\.|$))'
    questions = []

    # 使用re.finditer来查找所有匹配项
    for match in re.finditer(pattern, text, re.MULTILINE | re.DOTALL):
        question = match.group('question').strip()
        options = re.findall(r'(A|B|C|D)\..*', question)  # 提取选项(简化处理,可能需要根据实际情况调整)
        answer = match.group('answer')
        explanation = match.group('explanation').strip()

        # 将题目、选项、答案和解析添加到列表中(这里为了简单起见,只添加了题目和答案,但可以扩展为包含选项和解析的字典)
        questions.append({
            'question': question,
            'options': options,
            'answer': answer,
            'explanation': explanation
        })
    return questions

def parse_text(text):
    '''
    用于2011年到2021年测绘真题
    :param text: 文本
    :return: 题目选项答案的列表
    '''
    text = text.replace('、', '.')
    # 正则表达式,用于分割每个问题的段落
    problem_pattern = re.compile(r'(\d+\..*?(?=^(\d+\.|$)))', re.MULTILINE | re.DOTALL)

    # 正则表达式,用于在每个问题的段落中提取题目、选项、答案和解析
    all_pattern =            re.compile(r'^(.*?)\s*(A[.,\、]*.*?E[.,\、]*.*?)(答案:[\w, ]+)(解析:.*)', re.DOTALL)
    no_explanation_pattern = re.compile(r'^(.*?)\s*(A[.,\、]*.*?D[.,\、]*.*?)(答案:[\w, ]+)', re.DOTALL)

    problems = problem_pattern.findall(text)
    parsed_problems = []
    i = 0
    for problem in problems:
        problem = list(problem)[0]
        match_all = all_pattern.match(problem)
        match_no_explanation = no_explanation_pattern.match(problem)

        match = match_all if match_all else match_no_explanation
        i+=1
        if i == 26:
            print()
        if match:
            question = match.group(1).strip()

            #  修改后的正则表达式,确保选项前有空格或\n,并且紧跟一个大写字母和“、”
            origin_option = match.group(2).replace('\n', ' ').strip()
            options_pattern = re.compile(r'[A-F][.,\、]*\s*\S.*?(?=\S[A-F]|$)')

            options_matches = options_pattern.findall(origin_option)

            options = [option.strip() for option_match in options_matches for option in option_match.split()]

            answer = match.group(3).replace('答案:', '').strip().replace(' ', '')
            explanation = match.group(4).strip() if match_all else ''

            parsed_problems.append({
                'question': question.replace('\n',''),
                'options': options,
                'answer': answer,
                'explanation': explanation
            })

    return parsed_problems
def partition_topic(text):
    '''
    用于2011年到2021年测绘真题
    :param text: 文本
    :return: 题目选项答案的列表
    '''
    questions = re.findall( r'(\d+\..*?答案:.*?解析:.*?)(?=^\d+\.|$)', text, re.DOTALL | re.MULTILINE)
    return questions

def main(path, outpath):
    if not os.path.exists(outpath):
        os.makedirs(outpath)
    mdfile = get_file_name(path, '.md')

    i = 0
    for md in mdfile:
        outfile = os.path.join(outpath, os.path.splitext(os.path.basename(md))[0] + '.md')
        if os.path.exists(outfile):
            os.remove(outfile)  # 如果存在,则删除,再生成新的outfile

        md_content = read_md_file(md)  # 读取
        parsed_text = parse_text(md_content)  # 2011-2022
        # parsed_text = parse_text2022(md_content)

        try:
            for text in parsed_text:
                questions = text['question']
                options = text['options']
                answers = text['answer']
                explanation = text['explanation']
                for answer in answers:
                    options[index[
                        answer]] = f'**<span style="color:{random.choice(colors)};">{options[index[answer]]}</span>**'

                with open(outfile, 'a', encoding='utf-8') as file:

                    new_options = []
                    file.write(questions)
                    file.write('<br />')
                    for option in options:
                        file.write(option)
                        file.write(' ')
                    file.write('<br />')
                    file.write('答案:{}'.format(answers))
                    file.write('<br />')
                    file.write('<br />')
                    file.write('<br />')
        except  Exception as e:
            print(e)

        i += 1
        print("\r答案加粗加颜色: [{0:50s}] {1:.1f}% {2}".format('#' * int(i / (len(mdfile)) * 50),
                                                                i / len(mdfile) * 100,
                                                                os.path.basename(outfile)), end="",
              flush=True)
        time.sleep(2)


if __name__ == '__main__':
    path = r'D:\Registered_Surveyor\markdown\test2022'
    outpath = r'D:\Registered_Surveyor\markdown\color1'
    main(path, outpath)

是有瑕疵的,比如上图中的第三题就有提取信息的瑕疵。

为什么会那样呢?因为第三题的题干出现了A、B的字眼,把我们拟定的re规则打乱了。

但是这样的错误是少数的,我不想再debug上面的代码了。。。。

资料分享