평가원 영어 pdf에서 txt 추출하기(python)
게시글 주소: https://orbi.kr/00073761517
영어 pdf에서 txt 추출.txt
2606.txt
평가원 영어 시험지 pdf에서 txt 추출하는 파이썬 코드임
필요해서 만들어 봄
pip install PyMuPDF
라이브러리 설치 필요
코드 원문
================================================================
import fitz
import os
import re
def clean_and_format_text(text: str) -> str:
"""
추출된 텍스트 청크를 문제/선택지 중심으로 재구성하고 모든 예외 규칙에 따라 서식을 정리합니다.
각 정규식은 특정 예외 케이스를 해결하기 위해 논리적으로 설계되었습니다.
"""
# 1단계: 초기 정제 - 후속 처리를 용이하게 하기 위해 텍스트를 한 줄로 정렬하고 기본 서식을 정리합니다.
# 모든 줄바꿈 문자(\n)를 공백으로 변환하여, 여러 줄에 걸쳐 있던 텍스트를 다루기 쉬운 한 줄의 문자열로 만듭니다.
processed_text = text.replace('\n', ' ').strip()
# 단어와 공백으로 떨어진 문장부호(.,?!)를 단어에 붙입니다. (예: "단어 ." -> "단어.")
# \s+는 하나 이상의 공백을, ([.,?!])는 문장부호 그룹을 의미하며, \1은 찾은 그룹(문장부호)을 가리킵니다.
processed_text = re.sub(r'\s+([.,?!])', r'\1', processed_text)
# 두 개 이상의 연속된 공백을 하나의 공백으로 통일하여 일관성을 유지합니다.
processed_text = re.sub(r'\s{2,}', ' ', processed_text)
# 2단계: 구조적 줄바꿈 삽입 - 문맥을 파악하여 문단 구분이 필요한 곳에 줄바꿈 문자를 삽입합니다.
# '[숫자~숫자]...답하시오.' 형식의 장문 독해 안내문 전체를 별도의 문단으로 분리합니다.
# ( )로 묶인 전체 패턴을 그룹(\1)으로 찾아, 그 앞과 뒤에 줄바꿈을 추가합니다.
processed_text = re.sub(r'(\[\d{1,2}~\d{1,2}\].*?답하시오\.)', r'\n\n\1\n', processed_text)
# '[3점]'과 각주 기호 '*'가 붙어있는 특정 케이스를 분리합니다.
processed_text = re.sub(r'(\[\d점\])\s*(\*)', r'\1\n\2', processed_text)
# 한글 또는 닫는 괄호')' 뒤에 대괄호'['가 오는 경우(예: ...상쇄하다[36~37])를 찾아 줄바꿈을 추가합니다.
processed_text = re.sub(r'([가-힣\)])\s*(\[)', r'\1\n\2', processed_text)
# 일반적인 각주 분리 규칙. 단어의 끝으로 간주되는 문자들 뒤에 각주 기호 '*'가 오면 줄바꿈을 삽입합니다.
# [a-zA-Z가-힣0-9.,?!")’\']는 단어의 끝을 구성하는 다양한 문자셋을 의미합니다.
processed_text = re.sub(r'([a-zA-Z가-힣0-9.,?!")’\'])\s+(\*)', r'\1\n\2', processed_text)
# '고르시오.', '것은?', '문장은?'으로 끝나는 문제 제목과 본문을 분리합니다.
# (?:...)는 캡처하지 않는 그룹을 의미하며, 제목 뒤에 선택적으로 오는 '[3점]'까지 하나의 단위로 묶어 처리합니다.
processed_text = re.sub(r'((?:고르시오\.|것은\?|문장은\?)(?:\s*\[\d점\])?)\s*', r'\1\n', processed_text)
# 요약문 기호 '?'를 찾아, 그 앞뒤로 충분한 간격을 주어 별도의 문단처럼 보이게 합니다.
processed_text = re.sub(r'\s*(?)\s*', r'\n\n\1\n\n', processed_text)
# 각주와 선택지(①)가 한 줄에 붙어있는 경우를 분리합니다. (2차 방어 로직)
processed_text = re.sub(r'(\*.+?\w)\s*(①|②|③|④|⑤)', r'\1\n\2', processed_text)
# 원문자 뒤에 공백이 없는 경우(예: ①단어) 공백을 강제로 추가합니다.
# (?!\s)는 '부정형 전방탐색'으로, 뒤에 공백이 오지 않는 경우에만 패턴을 일치시킵니다.
processed_text = re.sub(r'(①|②|③|④|⑤)(?!\s)', r'\1 ', processed_text)
# 문제 번호를 인식하여 그 앞에 두 번 줄바꿈을 삽입합니다.
# (?<!\d)는 '부정형 후방탐색'으로, '숫자+점' 패턴 앞에 다른 숫자가 없는 경우에만 일치시켜 '2022.' 같은 연도를 문제 번호로 오인하는 것을 방지합니다.
processed_text = re.sub(r'(?<!\d)\s*(\d{1,2}\.)', r'\n\n\1', processed_text)
# 선택지 기호(①) 앞에 줄바꿈을 삽입합니다.
# (?<!\()는 '부정형 후방탐색'으로, 원문자 앞에 여는 괄호'('가 없는 '진짜' 선택지에만 적용하여, '(①)'과 같은 문장 삽입형 기호는 제외합니다.
processed_text = re.sub(r'(?<!\()\s(①|②|③|④|⑤)', r'\n\1', processed_text)
# 장문 독해의 문단 표식( (A), (B) 등) 앞에 두 번의 줄바꿈을 삽입하여 문단을 나눕니다.
# 문장 종료 부호(.?!”’) 뒤에 오는 경우만 실제 문단 표식으로 간주하여, 선택지 내부의 '(A)' 등은 제외합니다.
processed_text = re.sub(r'([.?!"]|”|’)\s+(\([A-Z]\))', r'\1\n\n\2', processed_text)
# 3단계: 후처리 - 구조화된 텍스트를 최종적으로 다듬습니다.
# 여러 줄로 나뉜 각주들을 하나의 줄로 통합하며, 각주 사이에 공백을 두 개 삽입하여 가독성을 높입니다. (2회 반복하여 ***까지 처리)
processed_text = re.sub(r'(\*.+)\n(\*+.+)', r'\1 \2', processed_text)
processed_text = re.sub(r'(\*.+)\n(\*+.+)', r'\1 \2', processed_text)
# 3개 이상의 연속된 줄바꿈을 2개로 줄여, 의도적으로 만든 문단 간격(빈 줄 하나)은 보존하고 불필요한 간격만 제거합니다.
final_text = re.sub(r'\n{3,}', '\n\n', processed_text)
return final_text.strip()
def extract_combined_text_from_pdf(input_pdf_path: str) -> str:
# (이전과 동일)
try:
doc = fitz.open(input_pdf_path)
except Exception as e:
print(f"오류: PDF 파일을 열 수 없습니다. 경로: {input_pdf_path}, 오류: {e}")
return ""
all_text_parts = []
MM_TO_POINTS = 72 / 25.4
page_margins = {
0: {'top': 75 * MM_TO_POINTS, 'bottom': 40 * MM_TO_POINTS},
'default': {'top': 54 * MM_TO_POINTS, 'bottom': 40 * MM_TO_POINTS}
}
print(f"'{os.path.basename(input_pdf_path)}' 파일 ({len(doc)}페이지) 텍스트 추출 중...")
for page_num in range(len(doc)):
page = doc.load_page(page_num)
page_rect = page.rect
page_width, page_height = page_rect.width, page_rect.height
margins = page_margins.get(page_num, page_margins['default'])
top_margin, bottom_margin = margins['top'], margins['bottom']
mid_x = page_width / 2
left_rect = fitz.Rect(0, top_margin, mid_x, page_height - bottom_margin)
right_rect = fitz.Rect(mid_x, top_margin, page_width, page_height - bottom_margin)
for rect_area in [left_rect, right_rect]:
raw_text = page.get_text("text", clip=rect_area)
if raw_text.strip():
formatted_text = clean_and_format_text(raw_text)
all_text_parts.append(formatted_text)
# 최종적으로 합치기 전에, 각 파트의 앞뒤 공백을 다시 한번 정리하여 일관성을 높임
cleaned_parts = [part.strip() for part in all_text_parts if part.strip()]
return '\n\n'.join(cleaned_parts)
if __name__ == '__main__':
base_folder = r'D:\영어 문제지'
output_folder = os.path.join(base_folder, 'txt')
if not os.path.exists(output_folder):
os.makedirs(output_folder)
try:
all_files = os.listdir(base_folder)
except FileNotFoundError:
print(f"오류: '{base_folder}' 폴더를 찾을 수 없습니다. 경로를 확인하세요.")
exit()
pdf_files = [f for f in all_files if f.lower().endswith('.pdf')]
if not pdf_files:
print(f"'{base_folder}' 폴더에 처리할 PDF 파일이 없습니다.")
else:
print(f"총 {len(pdf_files)}개의 PDF 파일을 처리합니다.")
for pdf_filename in pdf_files:
input_pdf_path = os.path.join(base_folder, pdf_filename)
combined_text = extract_combined_text_from_pdf(input_pdf_path)
if combined_text:
# === 최종 후처리 단계 (전체 텍스트 대상) ===
# 1. 불필요한 안내 문구 목록
lines_to_remove = [
"1번부터 17번까지는 듣고 답하는 문제입니다. 1번부터 15번까지는 한 번만 들려주고, 16번부터 17번까지는 두 번 들려줍니다. 방송을 잘 듣고 답을 하시기 바랍니다.",
"이제 듣기 문제가 끝났습니다. 18번부터는 문제지의 지시에 따라 답을 하시기 바랍니다.",
"* 확인 사항 ◦답안지의 해당란에 필요한 내용을 정확히 기입(표기)했는지 확인 하시오."
]
for line in lines_to_remove:
combined_text = combined_text.replace(line, "")
# 2. 탭(tab)으로 정렬할 특정 선택지 블록 처리
def tab_format_choices(match):
# 찾은 블록의 줄바꿈을 탭으로 변경
return match.group(0).replace('\n', '\t')
pattern = r'(?:^[①②③④⑤]\s*\([a-e]\)\s*\n){4}^[①②③④⑤]\s*\([a-e]\)$'
combined_text = re.sub(pattern, tab_format_choices, combined_text, flags=re.MULTILINE)
# 3. 위의 처리 과정에서 생길 수 있는 과도한 줄바꿈 정리
combined_text = re.sub(r'\n{3,}', '\n\n', combined_text).strip()
# 최종 결과 저장
base_filename = os.path.splitext(pdf_filename)[0]
output_filename = f"{base_filename}.txt"
output_filepath = os.path.join(output_folder, output_filename)
with open(output_filepath, 'w', encoding='utf-8') as f:
f.write(combined_text)
print(f"-> 최종 결과가 '{output_filepath}' 파일에 저장되었습니다.")
print("\n모든 작업이 완료되었습니다.")
================================================================
0 XDK (+0)
유익한 글을 읽었다면 작성자에게 XDK를 선물하세요.
-
킬러는 20~30초고 준킬러는 어디까지임? 40?
-
수완 실모는 0
객관식 수2 14번 또는 15번은 매우 ㅈ밥으로 내는 경향성이 있음. 막상 21이나...
-
수특만 했는데 수완도 해야하나 잘 모르겠습니다ㅜㅜ
-
병원 장례식장 30m 거리에 흡연장 있는데 개새끼들이 꾸역꾸역 길에 나와서 담배피고...
-
6모 84인데 그냥 실력대로 나온건가 ㅅㅂ 미적 급수하나 건들고 다틀렸네 미적 진짜...
-
일반고 5둥급도 갈수 있나요.. 6등급이 최저던데
-
원래는 많이 풀었는데, 지금풀고잇는 엔제 난이도 제일 높은쪽이라 한문제당 시간이...
-
미적을 공부하면 수1,수2도 느는거 같은데 맞음뇨? 4
그 역은 성립 안하고
-
국어 3등급 학생입니다 현재 기출분석,앱스키마 하고있어요 가끔...
-
볼륨 좀 부담스럽네염... 작년 내신동안 기출벅벅하면서 수능1~2 나오게끔 해놨는데...
-
시중건경 ㄷㄷ
-
공통수학2 아예 안 하고 들어와서 현재~방학 끝까지...
-
이런 경우도 있는지 좀 당황스럽더라고요 자세한건 글로 쓰기 겁나서 쪽지 ㄱ
-
이쁜 애들은 몇명 보이는데 이런애들 볼때마다 존경심 듦ㅋㅋㅋ 아니 어떻게 화장...
-
문제집 있나요? 방학동안 킬러기출만 모아서 도장깨기하듯이 풀고 하프모형태로된 n제랑 병행하려는데
-
작년에 있었던 호감고닉들이 그립구나
-
다들 어케 생각하시나용 기하러라 배웠는데 진심 어렵지도 않고 알아두면 공간도형에서...
-
수학 기준 어느 모고에서 더 많이 얻어간 것 같나요?
-
내신 2점대로 한양대 합격한 생기부 작성 실전 매뉴얼 0
생기부 작성을 고민하시는 분들을 위한 생기부 작성 실전 매뉴얼입니다. 저도...
-
생명 폼회복했나?
-
적분 관련 교과 외 궁금한 거 있는데 답변 해주실 수 있나요? 5
정적분이 닫힌 구간에서 정의가 되는데 한 값으로 한 없이 다가가는 상태, 즉...
-
자체 회전하는 튜브모양 인공위성에서 우주인이 원심력에 의해 중력이 작용하는 것처럼...
-
슈퍼마리오 3 64 선샤인... 스위치2 사고싶다
-
열등감은 제아무리 숨기려 해도 티가 날 수밖에 없는 듯 8
말투나 표정, 행동에서 어떻게든 드러나게 되는거 같음
-
글고 이거 써먹는 기출좀 알려주시면 감사하빈다
-
남편이자 아버지인 분은 걍 하늘 무너져 내렸겠네 하..
-
6모 낮2고 최저러라 수능때까지 안정2 박고싶습니다. 기출은 자이스토리로 1킬제외...
-
동아리에서 진행하고, 1교시 동안 가볍게 할 수 있는 약학과 관련 실험 있을까요?
-
다 의미 없누 사탕 발린 위로 따윈 집워치우누 오늘 밤은 삐딱하누
-
범 강k 현장이 7월 말 쯤 대기 풀릴것같아서 진짜 좋았던 부분 먼저 강의듣고...
-
번따 안 한 사람 중에 어케 버티셧음?..
-
의대 기준으로
-
이제 대충 한달간 생기부 활동하고 나면 드디어 수능공부 매진할 수 있음
-
물2 상대성이론 1
뮬2 상대성이론 공부하는데 문제 푸는 법은 알겠는데 이게 이론적으로 확 와닿지는...
-
브레인크래커 거의 다 들었는데 지금 시점에서 그 다음에 리트 300제랑 기출db...
-
원서적을때 0
교과를 안정만 적으면 6광탈인가? 하향도 꼭 적어야 하나여?
-
서바 과탐 현장은 9모 이후로 대기땜에 들어가기 힘드려나 4
과탐 현장응시 중요하다고 보시나요? 진짜 수능장이랑 환경이 다르긴 한데 그냥...
-
파일 정리하는데 있길래 올려봄 물화생지 12, 생윤 윤사 정법 경제 교과서...
-
ㅈㄱㄴ 좀 더 설명하자면 사탐런 하는중이라서 정말 하나도 모르는데 뭐부터...
-
다시 푸니까 전사인자랑 개념하나에서 틀림 ㅋㅋㅋㅋ
-
ㅈㄱㄴ
-
바뀌는 것만 몇 번을 봤는지... 30학번도 얼마 안 남았고
-
현재 비독원 a 수강중입니다. 처음엔 몇지문 해설 듣고 반했죠. 아 이런 생각을...
-
간쓸개 풀고있는데 가끔 정보량폭탄 수식폭탄 지문나오는데 이거 너무 사설틱한거같아서...
-
뭐가 더 어렵다고봄?
-
뭐가 더 취약한 분들이 많을까..?
-
그레야 내가 적백 받지.
-
통통이 22번까지가 시험임. 미장연 30번까지가 시험임.
천재인가
이런거 어떻게하는거임 다시봤어요
ㅋㅋㅋ 로직 생각해서 ai한테 던져주면 잘 만들어 줍니다
와 ㄷㄷㄷ
국어 코드도 있으신가유
국어는 맞춤법이 워낙 복잡해서 코드 짜도 이상하더라구요
영어는 그냥 단어로 딱딱 나뉘니까 괜찮은데
그래서 국어는 현재는 없습니다
어쩐지 제가 시도했을때도 잘 안되더니..
국어가 문제네요 ㅋㅋ

감사합니다!!!