poetry show
poetry show --tree # 의존성 트리
poetry show --latest # 최신 버전도 함께 표시
poetry show --outdated # 업데이트가 가능한 패키지의 목록 표시
poetry show --no-dev # 개발 의존성을 제외하고 표시
4️⃣ 가상환경 생성 및 활성화
poetry env activate
Poetry 2.0 업데이트에 따른 주요 변경사항
poetry export:
Poetry 1.2 버전부터 poetry export 명령은 이제 기본 기능이 아니라 플러그인으로 제공됩니다.
poetry export를 사용하려면 poetry-plugin-export 플러그인을 별도로 설치해야 합니다.
poetry shell:
poetry shell 명령어도 더 이상 Poetry의 기본 명령어로 제공되지 않습니다.
새로운 명령어인 poetry env activate를 사용하여 가상환경 활성화를 해야합니다.
poetry shell 명령어를 사용하고 싶다면, poetry-plugin-shell 플러그인을 설치하여 사용할 수 있습니다.
AWS SES는 마케팅 이메일, 거래 이메일 및 기타 대규모 이메일 발송을 쉽게 처리할 수 있는 서비스로 저렴한 비용으로 대규모 이메일 발송이 가능합니다. SMTP(Simple Mail Transfer Protocol)를 통해 직접 통합하거나 AWS SDK를 사용하여 애플리케이션에서 쉽게 사용할 수 있습니다. SES는 이메일 발송뿐 아니라 수신도 가능하며, 이메일을 인증하는 기능도 제공합니다.
주요 기능:
• 도메인 인증 : 발송 도메인에 대한 검증 및 설정을 지원하여 이메일의 신뢰성을 높입니다. • DKIM 서명 : DKIM(Domain Keys Identified Mail)을 통해 이메일이 변조되지 않았음을 수신 서버에 증명할 수 있습니다. • 전송 상태 모니터링 : 이메일 발송 성공 여부, 반송 상태 등을 실시간으로 모니터링할 수 있습니다. • 대량 이메일 발송 : AWS 인프라를 활용해 대규모 이메일 발송이 가능합니다.
2. Amazon SES 요금
Amazon SES는 사용한 만큼 비용 지불이 되며, 이메일 발신 및 수신, 데이터 사용량 및 추가 기능에 대한 요금은 별도로 부과됩니다.
도메인 : 확인된 도메인의 모든 하위 도메인이나 이메일 주소에서 이메일을 보낼 수 있습니다.
이메일 주소 : 이메일 주소 자격 증명은 확인된 이메일 주소만 메일을 보내는 데 사용할 수 있습니다.
사전 요구 사항:
AWS 계정 : AWS SES를 사용하려면 먼저 AWS 계정이 필요합니다.
도메인 정보 : 이메일을 발송할 도메인이 필요합니다. 이 도메인에 대해 DNS 설정을 추가해야 하기 때문에 도메인 관리 권한이 필요합니다.
4. AWS SES 도메인 인증 프로비저닝
도메인을 사용하여 이메일을 발송 하려면 도메인을 먼저 검증해야 합니다. SES는 DNS 레코드를 통해 도메인이 실제 소유자의 것인지 확인합니다.
4.1 Terraform Provider 설정
provider "aws" {
region = "ap-northeast-2"
}
4.2 SES 도메인 인증 리소스 생성:
resource "aws_ses_domain_identity" "example" {
domain = "example.com"
}
# Route53에 도메인 검증을 위한 TXT 레코드 설정
resource "aws_route53_record" "ses_verification_record" {
zone_id = "Z3P5QSUBK4POTI" # Route53 Hosted Zone ID
name = "_amazonses.example.com"
type = "TXT"
ttl = 300
records = [aws_ses_domain_identity.example.verification_token]
}
4.3 DKIM(Domain Keys Identified Mail) 설정
DKIM(Domain Keys Identified Mail)은 발송된 이메일이 수신 도메인에서 변조되지 않았음을 증명하는 방식입니다. DKIM 서명을 추가하면 이메일의 신뢰도를 높일 수 있습니다. DKIM을 사용하려면 별도의 CNAME 레코드를 DNS에 추가해야 합니다.
# DKIM 설정을 위한 SES 이메일 인증 자격 증명 생성
resource "aws_ses_domain_dkim" "example_dkim" {
domain = aws_ses_domain_identity.example.domain
}
# Route53에서 DKIM을 위한 CNAME 레코드 설정
resource "aws_route53_record" "ses_dkim_records" {
count = length(aws_ses_domain_dkim.example_dkim.dkim_tokens)
zone_id = "Z3P5QSUBK4POTI" # Route53 Hosted Zone ID
name = "${aws_ses_domain_dkim.example_dkim.dkim_tokens[count.index]}._domainkey.example.com"
type = "CNAME"
ttl = 300
records = ["${aws_ses_domain_dkim.example_dkim.dkim_tokens[count.index]}.dkim.amazonses.com"]
}
DKIM 설정을 적용한 후에는 AWS SES 콘솔에서 DKIM 상태가 verified로 표시 되는지 확인합니다.
4.4 Mail From 도메인 설정
반송 메일이 있을 경우 설정된 Mail From 도메인으로 해당 메일을 처리합니다.
# Mail From 도메인 설정
resource "aws_ses_domain_mail_from" "example_mail_from" {
domain = aws_ses_domain_identity.example.domain
mail_from_domain = "mail.example.com"
}
# Route53에서 Mail From 도메인을 위한 MX 레코드 설정
resource "aws_route53_record" "mail_from_mx" {
zone_id = "Z3P5QSUBK4POTI" # Route53 Hosted Zone ID
name = "mail.example.com"
type = "MX"
ttl = 300
records = ["10 feedback-smtp.{region}.amazonses.com"]
}
# Route53에서 Mail From 도메인을 위한 SPF 레코드 설정
resource "aws_route53_record" "mail_from_spf" {
zone_id = "Z3P5QSUBK4POTI"
name = "mail.example.com"
type = "TXT"
ttl = 300
records = ["v=spf1 include:amazonses.com -all"]
}
사기 및 남용을 방지하기 위해 신규로 생성시에는 SandBox로 구성되어, 서비스 제한이 있습니다. 생성 후에는 프로덕션으로 전환하도록 요청하여합니다.
aws ses send-email \
--from "no-reply@example.com" \
--destination "ToAddresses=user@recipient.com" \
--message "Subject={Data=Email Send Test},Body={Text={Data=This is a test email}}"
결론
AWS SES를 프로비저닝하는 과정은 생각보다 복잡해 보일 수 있지만, 도메인 검증, DKIM 설정, Mail From 설정 등 단계별로 차근차근 설정하면 손쉽게 신뢰도 높은 대규모 이메일 발송 인프라를 구축할 수 있습니다. 추가적으로, 이메일 발송 후 결과를 지속적으로 모니터링하여 도메인 평판을 유지하는 것이 중요합니다.
프로비저닝 구성이 완료되면 AWS SystemManager > Session Manager 탭에서 생성 결과를 확인할 수 있습니다.
2.6. EC2에 접속하기
AWS Console과 CLI를 통해 접속할 수 있습니다.
AWS Management Console를 통한 접속
Console을 통해 SSM 세션을 접속합니다.
AWS CLI를 통한 접속
다음 명령어를 통해 SSM 세션을 시작합니다.
aws ssm start-session --target <instance-id>
2.7. 결과
AWS Systems Manager (SSM)과 Session Manager를 활용하여 프라이빗 EC2 서버를 프로비저닝하고, SSM을 통해 안전하게 접근하는 방법을 다루었습니다. 이를 통해 SSH 키 관리의 번거로움을 줄이고, 보안을 강화할 수 있습니다. SSM과 Session Manager를 사용하면 원격 명령 실행, 파일 전송 등 다양한 기능을 활용할 수 있으므로, 이를 적극적으로 활용해보시기 바랍니다.
Python 애플리케이션을 개발할 때, 환경 변수를 사용하는 것은 매우 중요한 부분입니다. API 키, 데이터베이스 접속 정보와 같은 민감한 데이터를 코드에 직접 코딩하지 않고 환경 변수로 관리하는 것이 좋습니다. 이번 블로그에서는 python-dotenv 라이브러리를 사용하여 Python 환경 변수를 관리하는 방법에 대해 알아보겠습니다.
1. Python Dotenv 설치하기
먼저, python-dotenv 라이브러리를 설치 합니다. 이 라이브러리는 .env 파일에 저장된 환경 변수를 읽어 Python 애플리케이션에서 사용할 수 있도록 해줍니다.
# pipenv
pipenv install python-dotenv
2. .env 파일 생성하기
프로젝트 루트 디렉토리에 .env 파일을 생성합니다. 이 파일에는 환경 변수와 그 값을 키=값 형식으로 작성합니다. .env 파일에는 민감한 정보가 포함될 수 있으므로, .gitignore 파일을 생성하고 .env 파일을 추가하여 github 등에 포함되지 않도록 하여 줍니다.
이제 Python 코드에서 python-dotenv을 사용하여 .env 파일에 정의된 환경 변수를 로드할 수 있습니다. 이를 위해 dotenv 모듈의 load_dotenv 함수를 사용합니다.
import os
from dotenv import load_dotenv
# Load environment variables from the .env file (if present)
load_dotenv()
# Access environment variables as if they came from the actual environment
SECRET_KEY = os.getenv('SECRET_KEY')
DATABASE_URL = os.getenv('DATABASE_URL')
API_KEY = os.getenv('API_KEY')
# Example usage
print(f'SECRET_KEY: {SECRET_KEY}')
print(f'DATABASE_URL: {DATABASE_URL}')
print(f'API_KEY: {API_KEY}')
python-dotenv 라이브러리를 사용하면 환경 변수를 쉽게 관리하고 애플리케이션 설정을 안전하게 유지할 수 있습니다. 이 방법을 통해 코드 내에 민감한 정보를 반영 하지 않고, 다양한 환경에서 동일한 코드를 사용할 수 있습니다. python-dotenv을 사용하여 환경 변수를 효율적으로 관리해보시기 바랍니다.
Terraform Backend는 Terraform State 파일의 저장하는 위치를 정의합니다. State는 Terraform에서 프로비저닝 결과를 추적하고 이후 프로비저닝 수행 시 비교하는 과정에 사용되므로, Terraform 사용 빈도와 대상의 규모가 커질수록 계획적인 관리가 요구 됩니다.
Backend 구성 목적
State Backend는 관리 / 공유 / 격리의 목적으로 사용됩니다
목적
내용
관리
State는 local 구성이 기본으로 현재 State인 terraform.tfstate와 이전 상태인 terraform.tfstate.backup만이 보관되므로 지속적인 State 백업을 위해서 Remote 저장소 필요 Remote 저장소에 state 파일을 관리하는 경우, 여러 사용자가 동시에 state 파일을 변경하는 경우 문제가 발생할 수 있으므로 동시에 같은 state 파일을 접근하는 것을 막아 의도치 않는 변경을 방지
공유
다수의 작업자가 동일한 State로 접근하여 프로비저닝하기 위한 공유 스토리지 필요
격리
State 파일에 저장될 수 있는 민감 데이터에 접근 권한을 환경별로 접근권한을 격리
사용 가능한 Backend
기본적으로 Terraform은 local상태를 디스크에 local 파일로 저장하는 Backend를 사용합니다. AWS 리소스를 프로비저닝하는 경우 S3, Google Cloud의 경우 GCS가 적합 할 수 있습니다. HashiCorp는 Terraform Cloud를 통해 State 백엔드 기능을 제공합니다.
(기타)
Consul
Postgres Database (pg)
Terraform Workspace
Terraform Workspace는 State를 관리하는 단위입니다. 기본적으로 default Workspace가 생성됩니다. terraform plan 을 새로운 Workspace에서 수행하면, Terraform은 다른 Workspace에 있는 리소스에 접근하지 않습니다. 기존에 있던 리소스를 관리하려면 Workspace를 전환한 후에 변경해야 합니다.
Terraform Backend 구현(AWS)
Terraform을 사용하여 인프라스트럭처를 관리할 때, Terraform의 상태 파일(backend)을 보관하는 AWS 계정과 실제 리소스가 프로비저닝되는 AWS 계정을 분리하는 것은 일반적인 보안 및 관리 방법입니다. 이러한 접근 방식은 Terraform 상태 파일의 보안을 강화하고, 팀 간의 접근 권한을 분리 하며, 여러 프로젝트 또는 환경 간의 충돌을 방지하는 데 도움이 됩니다.
AWS 인프라 관리 시, Multi-Account 관리를 통해 독립적인 환경을 구현하고 보안 및 관리 용이성을 가질 수 있도록 권고되고 있습니다. Terraform Backend 환경은 공유 자원을 관리하는 Shared AWS Account에 관리 되며, 각 환경 및 Workspace별로 state를 저장 관리합니다.
+------------------+----------------+
| AWS Account Name | AWS Account ID |
+------------------+----------------+
| Shared | 111111111111 |
| Dev | 222222222222 |
| Qa | 333333333333 |
| Prod | 444444444444 |
| Test | 555555555555 |
+------------------+----------------+
S3, DynamoDB 생성
상태를 Amazon S3의 버킷에 저장하고, Dynamo DB를 활용하여 Apply 시 State Locking을 통해 State의 일관성을 확보합니다. 실수로 삭제하거나 사람의 실수가 발생한 경우 상태를 복구할 수 있도록 버킷(Bucket)의 버전 관리를 활성화 합니다.
# Create S3 Bucket for Terraform State
resource "aws_s3_bucket" "terraform_state" {
bucket = "tf-state-bucket"
tags = {
Name = "tf-state-bucket"
Environment = "" # Global, Shared, Dev, QA, Prod??
}
}
# Provides a resource for controlling versioning on an S3 bucket
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
# Provides a S3 bucket server-side encryption.
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256" # (SSE-S3)
}
}
}
# Provision DynamoDB Table for state locking
resource "aws_dynamodb_table" "this" {
name = "terraform-state-lock"
hash_key = "LockID"
billing_mode = "PAY_PER_REQUEST"
attribute {
name = "LockID"
type = "S"
}
}
State Configuration
State는 <workspace_key_prefix>/<workspace_name>/<key> 형식으로 저장됩니다. Terraform Backend 환경은 공유 자원을 관리하는 shared-layer 에 관리되며, 각 환경 및 Workspace별로 state를 저장 관리합니다.
# Base 이미지
FROM python:3.12-slim
# 2. 작업 디렉토리를 /app으로 설정
WORKDIR /app
# 3. 현재 디렉토리의 requirements.txt 파일을 컨테이너의 /app으로 복사
COPY requirements.txt /app/
# 4. requirements.txt에 명시된 패키지 설치
RUN pip3 install -r requirements.txt
# 5. 실행할 streamlit_app.py를 컨테이너 /app으로 복사
COPY streamlit_app.py /app/
# 6. 컨테이너가 수신할 포트 오픈
EXPOSE 8501
# 7. 컨테이너 헬스 체크
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
#8. 실행될 컨테이너 구성
ENTRYPOINT [ "streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0" ]
Streamlit 버전 1.10.0 이상에서는 Streamlit 앱을 Linux 배포판의 루트 디렉터리에서 실행할 수 없습니다. 따라서 WORKDIR은 루트가 아닌 별도 디렉토리를지정해야 합니다. (예. /app) (참고) github Issue:5239
Docker image 빌드
생성한 Dockerfile 을 streamlit 이미지 이름으로 빌드합니다.
docker build -t streamlit .
docker images 로 생성된 이미지 를 확인합니다.
REPOSITORY TAG IMAGE ID CREATED SIZE
streamlit latest bb9ac485406e 2 minutes ago 634MB
Docker 컨테이너 실행
컨테이너의 포트 8501을 서버의 8501 포트에 게시합니다.
docker run -p 8501:8501 streamlit
출력된 URL로 접속합니다.
Collecting usage statistics. To deactivate, set browser.gatherUsageStats to False.
You can now view your Streamlit app in your browser.
URL: http://0.0.0.0:8501
Streamlit은 데이터 애플리케이션을 빠르고 쉽게 만들 수 있도록 설계된 파이썬 기반의 오픈 소스 라이브러리입니다. 이 도구는 데이터 시각화와 머신 러닝 모델의 프로토타이핑 및 배포를 단순화하여 사용자가 복잡한 데이터 작업을 쉽게 수행할 수 있게 합니다.
Streamlit의 특징
코드 중심의 접근 방식: Streamlit 애플리케이션은 순수 파이썬 코드로 작성되므로 별도의 웹 프론트엔드 기술 없이도 데이터 앱을 만들 수 있습니다.
간편한 사용법: 몇 줄의 코드만으로도 인터랙티브한 요소(슬라이더, 버튼, 차트 등)를 추가할 수 있으며, 앱의 업데이트와 실행이 매우 빠릅니다.
빠른 프로토타이핑: 데이터 과학 프로젝트의 초기 아이디어를 빠르게 시각화하고 공유할 수 있어 프로토타이핑과 반복 개발 과정이 용이합니다.
확장성: Streamlit 앱은 컨테이너화하여 쉽게 배포할 수 있고, 다양한 데이터 소스와 라이브러리를 지원합니다.
장점
빠른 개발 속도: 복잡한 웹 앱을 빠르게 개발할 수 있으며, 데이터 과학자가 웹 개발에 대한 깊은 지식 없이도 사용할 수 있습니다.
쉬운 배포: Streamlit Sharing을 통해 GitHub에서 직접 앱을 호스팅하고 공유할 수 있습니다.
강력한 커뮤니티 지원: 오픈 소스 프로젝트로서 활발한 커뮤니티 지원과 지속적인 업데이트가 이루어집니다.
인터랙티비티: 사용자 입력에 반응하는 동적인 데이터 애플리케이션을 쉽게 만들 수 있습니다.
단점
고급 사용자 정의 제한: Streamlit은 사용의 용이성에 중점을 두기 때문에, 복잡한 사용자 인터페이스나 고도로 맞춤화된 앱 디자인을 구현하기 어려울 수 있습니다.
성능 문제: 대규모 데이터셋이나 복잡한 연산을 처리하는 앱의 경우, 성능 저하가 발생할 수 있습니다.
웹 개발 기능의 제한: 전통적인 웹 개발 툴셋에 비해 제공하는 웹 개발 기능이 제한적일 수 있습니다. 복잡한 웹 애플리케이션을 구축하려는 경우 다른 프레임워크를 고려해야 할 수도 있습니다.
Streamlit App 실행하기
# streamlit 라이브러리설치
pip install streamlit
# Streamlit으로 실행
streamlit run your_script.py
# Python 모듈로 실행 → Python으로 실행하는 경우, PyCharm과 같은 IDE를 구성할 때 유용할 수 있습니다.
python -m streamlit run your_script.py
Data 표시방법
Streamlit에서 Data (tables, arrays, data frames 등)를 표시하는 방법은 magic CLI 와 st.write() 를 활용하는 방법이 있다. st.write()는 텍스트에서 테이블까지 다 작성이 가능하다.
① magic command line
Streamlit은 변수나 데이터가 선언되어 있으면 자동으로 st.write()를 사용하여 앱을 빌드 합니다.
###
# first app
# DataFrame 으로 테이블을 생성
###
import streamlit as st
import pandas as pd
df = pd.DataFrame({
'first column': [1, 2, 3, 4],
'second column': [10, 20,30, 40]
})
df
② st.write()
st.write()는 텍스트, 데이터, 차트 등 arguments 에 따라 작성 할 수 있다.
import streamlit as st
import pandas as pd
st.write("Here's our first attempt at using data to create a table:")
st.write(pd.DataFrame({
'first column': [1, 2, 3, 4],
'second column': [10, 20,30, 40]
}))
데이터를 표시하는 데 사용할 수 있는 st.dataframe() 및 st.table()과 같은 함수를 사용하여 다양한 style을 적용 할 수 있다.
DataFrame에 Style 적용하기
① DataFrame에 Highligt 적용
import streamlit as st
import pandas as pd
import numpy as np
df = pd.DataFrame(
np.random.randn(10, 20), # 표준 정규분포 확률을 따르는 난수를 생성
columns = ('col %d' % i for i in range(20))
)
# 열의 최대값에 하이라이트 표시
st.dataframe(df.style.highlight_max(axis=0))
import streamlit as st
import pandas as pd
import numpy as np
df = pd.DataFrame(
np.random.randn(10, 20), # 표준 정규분포 확률을 따르는 난수를 생성
columns = ('col %d' % i for i in range(20))
)
st.table(df)
import streamlit as st
import pandas as pd
import numpy as np
df = pd.DataFrame(
np.random.randn(20, 3), # 표준 정규분포 확률을 따르는 난수를 생성
columns = ['a', 'b', 'c']
)
st.line_chart(df)
import streamlit as st
import pandas as pd
import numpy as np
st.markdown("### 1. slider widget")
x = st.slider('x')
st.write(x, '제곱은', x * x)
st.markdown("### 2. text input widget")
st.text_input("Your name", key="name")
# You can access the value at any point with:
st.session_state.name
st.markdown("### 3. checkbox widget")
if st.checkbox('Once you checked, dataframe is displyed'):
chart_data = pd.DataFrame(
np.random.randn(20, 3),
columns=['a', 'b', 'c'])
chart_data
st.markdown("### 4. selectbox widget")
df = pd.DataFrame({
'first column': [1, 2, 3, 4],
'second column': [10, 20, 30, 40]
})
option = st.selectbox(
'Which number do you like best?',
df['first column'])
'You selected: ', option
import streamlit as st
import time
# 사이드바에 selctbox 구성하기
add_selectbox = st.sidebar.selectbox(
'How would you like to be contacted?',
('Email', 'Home phone', 'Mobile phone')
)
# 사이드바에 slide 구성하기
add_slider = st.sidebar.slider(
'Select a range of values',
0.0, 100.0, (25.0, 75.0)
)
# 위젯을 나란히 배치하기
left_column, right_column = st.columns(2)
left_column.title('Left Column')
left_column.button('Press me')
right_column.title('Right Column')
with right_column:
chosen = st.radio(
'Sorting hat',
("Gryffindor", "Ravenclaw", "Hufflepuff", "Slytherin")
)
st.write(f"You are in {chosen} house!")
# Progress Bar
st.title('Progress Bar')
'Starting a long computation...'
latest_iteration = st.empty()
bar = st.progress(0)
for i in range(100):
latest_iteration.text(f'Iteration {i+1}')
bar.progress(i+1)
time.sleep(0.1)
'...and now we\'re done!'
멀티페이지 App 만들기
Streamlit 1.10 이상 부터는 멀티 페이지를 구성할 수 있습니다.
서브페이지 구성 제약
Sub 페이지의 기본 구성은 pages 디렉토리에 위치해야 합니다.
각 Streamlit 앱의 이름은 파일 이름에 따라 결정되므로 앱 이름을 변경하려면 파일 이름을 변경해야 합니다. (이모티콘 추가도 가능)
각 Python 파일의 시작 부분에 숫자를 추가하여 페이지 순서를 지정할 수 있습니다. 파일 이름 앞에 1을 추가하면 Streamlit은 해당 파일을 목록의 첫 번째로 배치합니다.
각 페이지에는 파일 이름으로 정의된 고유한 URL이 있습니다.
멀티페이지 구성
멀티페이지는 메인이 되는 Home.py 와 내부의 1_page_one.py, 2_Page_two.py, 3_🥉_three.py 3개의 서브페이지로 이동 되도록 구성합니다.
페이스 구성 소스트리
Home.py # "streamlit run"으로 실행할 메인 파일
└─── pages/
└─── 1_page_one.py
└─── 2_Page_two.py
└─── 3_🥉_three.py
① 기본 사이드바 탐색 숨기기
파일명을 기준으로 사이드바 메뉴를 별도 이름으로 지정할 수 있도록 변경합니다. 사용자 정의 탐색 메뉴를 생성할 때 를 사용하여 기본 사이드바 탐색을 숨겨야 합니다. 소스코드 루트 .streamlit/config.toml작업 디렉터리에 다음 파일을 추가합니다 .
pdf 를 추출하기 위해 pdfplumber.open(file) 함수를 이용하여 pdf를 추출합니다.
import os
import pdfplumber
import pandas as pd
file_path = os.getcwd()+"/contents/"
pdf_name = "file.pdf"
pdf_file_path = file_path + pdf_name
# PDF 파일 열기
pdf = pdfplumber.open(pdf_file_path)
pages = pdf.pages
print("총 페이지 수 : ", len(pages))
tables = []
# 1. 페이지에서 표 데이터 추출하기
for each in pages:
table = each.extract_tables()
tables.extend(table)
3. 데이터 프레임으로 변환
# 2. 데이터 프레임으로 변환
df = pd.DataFrame(tables[1:], columns=[tables[0]])
# 3. 데이터프레임을 엑셀 파일로 저장
excel_file_path = file_path + "file.xlsx"
df.to_excel(excel_file_path, index=False, engine='openpyxl')
print(f'DataFrame이 {excel_file_path}에 저장 되었습니다.')