프로젝트 2: 특정 콘텐츠 추출 및 Markdown으로 다운로드
개요
이 프로젝트는 로컬 서버에 호스팅된 웹 페이지에서 특정 섹션의 콘텐츠를 가져옵니다. "■ Party Representative Lee Jae-myung"로 시작하는 단락부터 다음 "■" 문자가 나타날 때까지 단락을 추출합니다. 추출된 콘텐츠는 Markdown 파일로 다운로드할 수 있습니다.
단계 및 설명
1. 프로젝트 설정
- Node.js와 npm이 설치되어 있는지 확인합니다.
- 프로젝트를 위한 새 디렉토리를 만들고 새로운 Node.js 프로젝트를 초기화합니다.
mkdir extract_content
cd extract_content
npm init -y
2. 필요한 라이브러리 설치
- 서버 생성을 위한
express
, HTTP 요청을 위한axios
, 크로스 오리진 리소스 공유를 처리하기 위한cors
, HTML 파싱을 위한jsdom
을 설치합니다.
npm install express axios cors jsdom
3. server.js
생성
- 이 파일은 URL에서 콘텐츠를 가져오고, 파싱하고, 특정 단락을 추출하여, 콘텐츠를 제공하는 서버 로직을 포함합니다.
const express = require('express');
const axios = require('axios');
const cors = require('cors');
const path = require('path');
const { JSDOM } = require('jsdom');
const fs = require('fs');
const app = express();
const port = 3000;
app.use(cors());
// 루트 경로에서 HTML 파일 제공
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.get('/fetch-content', async (req, res) => {
try {
const response = await axios.get('https://theminjoo.kr/main/sub/news/view.php?brd=230&post=1204092');
const htmlText = response.data;
// HTML 파싱 및 콘텐츠 추출
const dom = new JSDOM(htmlText);
const document = dom.window.document;
const contentSelector = '#container > div.article-body > div > div.board-view__wrap > div.board-view > div.board-view__body > div.board-view__contents';
const contentElement = document.querySelector(contentSelector);
let collectedTexts = [];
let collect = false;
if (contentElement) {
const pTags = contentElement.getElementsByTagName('p');
Array.from(pTags).forEach(p => {
const text = p.textContent.trim();
if (text.startsWith('■')) {
if (text.startsWith('■ Party Representative Lee Jae-myung')) {
collect = true;
} else {
if (collect) {
collect = false;
}
}
}
if (collect) {
collectedTexts.push(text);
}
});
}
const markdownContent = collectedTexts.map(text => `- ${text}`).join('
');
res.send(markdownContent);
} catch (error) {
res.status(500).send('Error fetching content');
}
});
app.get('/download-markdown', async (req, res) => {
try {
const response = await axios.get('https://theminjoo.kr/main/sub/news/view.php?brd=230&post=1204092');
const htmlText = response.data;
// HTML 파싱 및 콘텐츠 추출
const dom = new JSDOM(htmlText);
const document = dom.window.document;
const contentSelector = '#container > div.article-body > div > div.board-view__wrap > div.board-view > div.board-view__body > div.board-view__contents';
const contentElement = document.querySelector(contentSelector);
let collectedTexts = [];
let collect = false;
if (contentElement) {
const pTags = contentElement.getElementsByTagName('p');
Array.from(pTags).forEach(p => {
const text = p.textContent.trim();
if (text.startsWith('■')) {
if (text.startsWith('■ 이재명 당대표')) {
collect = true;
} else {
if (collect) {
collect = false;
}
}
}
if (collect) {
collectedTexts.push(text);
}
});
}
const markdownContent = collectedTexts.map(text => `- ${text}`).join('
');
const filePath = path.join(__dirname, 'content.md');
fs.writeFileSync(filePath, markdownContent);
res.download(filePath, 'content.md');
} catch (error) {
res.status(500).send('Error fetching content');
}
});
app.listen(port, () => {
console.log(`Proxy server running at http://localhost:${port}`);
});
4. index.html
생성
- 이 파일은 Express 서버에 의해 제공되며, 추출된
<p>
태그 콘텐츠를 표시합니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Retrieve Text Content</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
#results {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
}
button, a {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
display: inline-block;
text-decoration: none;
color: white;
background-color: #007BFF;
border: none;
border-radius: 4px;
}
</style>
</head>
<body>
<h1 id="page-title">Loading...</h1>
<div id="results">Loading...</div>
<a id="download-link" href="http://localhost:3000/download-markdown" download="content.md">Download as Markdown</a>
<script>
document.addEventListener('DOMContentLoaded', async function () {
// Container to display results
const resultsContainer = document.getElementById('results');
const pageTitle = document.getElementById('page-title');
try {
// Fetch content from the proxy server
const response = await fetch('http://localhost:3000/fetch-content');
const contentText = await response.text();
// Display the extracted text
resultsContainer.innerHTML = contentText.split('
').map(text => `<p>${text}</p>`).join('');
pageTitle.innerHTML = 'Extracted Content';
} catch (error) {
resultsContainer.textContent = 'Error fetching content.';
pageTitle.innerHTML = 'Error';
}
});
</script>
</body>
</html>
5. 서버 실행
- 서버를 시작하고 브라우저를 열어 추출된 콘텐츠를 확인합니다.
node server.js
- 브라우저를 열고
http://localhost:3000/
로 이동하여 콘텐츠를 확인합니다.
발생한 어려움
- 크로스 오리진 요청 처리: 외부 서버로 HTTP 요청을 하는 동안 CORS 문제가 발생할 수 있습니다. Express에서
cors
라이브러리를 사용하여 이러한 문제를 해결했습니다. - HTML 콘텐츠 파싱:
JSDOM
을 사용하여 HTML 콘텐츠를 파싱하고 조작하는 것이 익숙하지 않을 수 있습니다. 올바르게 요소를 선택하여 필요한 데이터를 추출하는 것이 중요합니다. - 에러 처리: 네트워크 오류나 잘못된 HTML 구조와 같은 문제를 관리하기 위해 적절한 에러 처리가 필요합니다.