WeniVooks

검색

정규표현식 톺아보기 with JavaScript and Python

프로젝트 해답

1. 마크다운 파서 솔루션

1.1 JavaScript 구현
const patterns = {
    headers: /^(#{1,6})\s+(.+)$/gm,
    links: /\[([^\]]+)\]\(([^)]+)\)/g,
    bold: /\*\*([^*]+)\*\*/g,
    italic: /\*([^*]+)\*/g
};
 
 
function parse(markdown) {
    let html = markdown;
    
    // 헤더 변환
    html = html.replace(patterns.headers, (match, hashes, text) => {
        const level = hashes.length;
        return `<h${level}>${text}</h${level}>`;
    });
 
    // 링크 변환
    html = html.replace(patterns.links, 
        (match, text, url) => `<a href="${url}">${text}</a>`);
 
    // 강조 구문 변환 (순서 중요: bold를 먼저 처리)
    html = html.replace(patterns.bold, 
        (match, text) => `<strong>${text}</strong>`);
    html = html.replace(patterns.italic, 
        (match, text) => `<em>${text}</em>`);
 
    return html;
}
 
// 사용 예시
const markdown = `
# 메인 제목
## 부제목
 
이것은 [위니브](https://weniv.co.kr) 튜토리얼입니다.
**중요한** 내용과 *강조할* 내용이 있습니다.
`;
 
console.log(parse(markdown));
const patterns = {
    headers: /^(#{1,6})\s+(.+)$/gm,
    links: /\[([^\]]+)\]\(([^)]+)\)/g,
    bold: /\*\*([^*]+)\*\*/g,
    italic: /\*([^*]+)\*/g
};
 
 
function parse(markdown) {
    let html = markdown;
    
    // 헤더 변환
    html = html.replace(patterns.headers, (match, hashes, text) => {
        const level = hashes.length;
        return `<h${level}>${text}</h${level}>`;
    });
 
    // 링크 변환
    html = html.replace(patterns.links, 
        (match, text, url) => `<a href="${url}">${text}</a>`);
 
    // 강조 구문 변환 (순서 중요: bold를 먼저 처리)
    html = html.replace(patterns.bold, 
        (match, text) => `<strong>${text}</strong>`);
    html = html.replace(patterns.italic, 
        (match, text) => `<em>${text}</em>`);
 
    return html;
}
 
// 사용 예시
const markdown = `
# 메인 제목
## 부제목
 
이것은 [위니브](https://weniv.co.kr) 튜토리얼입니다.
**중요한** 내용과 *강조할* 내용이 있습니다.
`;
 
console.log(parse(markdown));
1.2 Python 구현
import re
 
patterns = {
    'headers': re.compile(r'^(#{1,6})\s+(.+)$', re.MULTILINE),
    'links': re.compile(r'\[([^\]]+)\]\(([^)]+)\)'),
    'bold': re.compile(r'\*\*([^*]+)\*\*'),
    'italic': re.compile(r'\*([^*]+)\*')
}
 
def parse(markdown):
    html = markdown
    
    # 헤더 변환
    def header_replace(m):
        level = len(m.group(1))
        return f'<h{level}>{m.group(2)}</h{level}>'
    
    html = patterns['headers'].sub(header_replace, html)
    
    # 링크 변환
    html = patterns['links'].sub(r'<a href="\2">\1</a>', html)
    
    # 강조 구문 변환
    html = patterns['bold'].sub(r'<strong>\1</strong>', html)
    html = patterns['italic'].sub(r'<em>\1</em>', html)
    
    return html
 
# 사용 예시
markdown = """
# 메인 제목
## 부제목
 
이것은 [위니브](https://weniv.co.kr) 튜토리얼입니다.
**중요한** 내용과 *강조할* 내용이 있습니다.
"""
 
print(parse(markdown))
import re
 
patterns = {
    'headers': re.compile(r'^(#{1,6})\s+(.+)$', re.MULTILINE),
    'links': re.compile(r'\[([^\]]+)\]\(([^)]+)\)'),
    'bold': re.compile(r'\*\*([^*]+)\*\*'),
    'italic': re.compile(r'\*([^*]+)\*')
}
 
def parse(markdown):
    html = markdown
    
    # 헤더 변환
    def header_replace(m):
        level = len(m.group(1))
        return f'<h{level}>{m.group(2)}</h{level}>'
    
    html = patterns['headers'].sub(header_replace, html)
    
    # 링크 변환
    html = patterns['links'].sub(r'<a href="\2">\1</a>', html)
    
    # 강조 구문 변환
    html = patterns['bold'].sub(r'<strong>\1</strong>', html)
    html = patterns['italic'].sub(r'<em>\1</em>', html)
    
    return html
 
# 사용 예시
markdown = """
# 메인 제목
## 부제목
 
이것은 [위니브](https://weniv.co.kr) 튜토리얼입니다.
**중요한** 내용과 *강조할* 내용이 있습니다.
"""
 
print(parse(markdown))

2. 로그 파일 분석기 솔루션

2.1 JavaScript 구현

간단히 실습해볼 수 있는 코드부터 살펴보도록 하겠습니다.

const logLine = `32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"`;
 
// 정규표현식 패턴
const pattern = /(\d+\.\d+\.\d+\.\d+).*\[(\d+\/\w+\/\d+:(\d+):\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"/;
 
// 매칭 수행
const match = logLine.match(pattern);
 
// 변수 할당
const [fullMatch, ip, timestamp, hour, method, path, referer] = match;
 
// 결과 출력
console.log('전체 매칭:', fullMatch);
console.log('IP 주소 (match[1]):', ip);
console.log('타임스탬프 (match[2]):', timestamp);
console.log('시간 (match[3]):', hour);
console.log('HTTP 메서드 (match[4]):', method);
console.log('경로 (match[5]):', path);
console.log('리퍼러 (match[6]):', referer);
const logLine = `32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"`;
 
// 정규표현식 패턴
const pattern = /(\d+\.\d+\.\d+\.\d+).*\[(\d+\/\w+\/\d+:(\d+):\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"/;
 
// 매칭 수행
const match = logLine.match(pattern);
 
// 변수 할당
const [fullMatch, ip, timestamp, hour, method, path, referer] = match;
 
// 결과 출력
console.log('전체 매칭:', fullMatch);
console.log('IP 주소 (match[1]):', ip);
console.log('타임스탬프 (match[2]):', timestamp);
console.log('시간 (match[3]):', hour);
console.log('HTTP 메서드 (match[4]):', method);
console.log('경로 (match[5]):', path);
console.log('리퍼러 (match[6]):', referer);
const data = `32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"
187.234.121.55 - - [29/Nov/2024:08:16:12 +0900] "GET /course/python HTTP/1.1" 200 3267 "https://books.weniv.co.kr/" "Mozilla/5.0"
91.45.234.178 - - [29/Nov/2024:08:17:01 +0900] "POST /shorten HTTP/1.1" 201 342 "https://weniv.link/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:18:23 +0900] "GET /schedule HTTP/1.1" 200 2891 "https://time.weniv.co.kr/" "Safari/605.1.15"
211.89.145.67 - - [29/Nov/2024:08:19:45 +0900] "GET / HTTP/1.1" 200 1523 "https://weniv.co.kr" "Firefox/121.0"
167.89.234.90 - - [29/Nov/2024:08:20:11 +0900] "GET /dashboard HTTP/1.1" 200 5632 "https://canvas.weniv.co.kr/" "Edge/120.0.0.0"
45.178.234.89 - - [29/Nov/2024:08:21:34 +0900] "POST /query HTTP/1.1" 200 892 "https://sql.weniv.co.kr/editor" "Chrome/120.0.0.0"
123.45.167.89 - - [29/Nov/2024:08:22:56 +0900] "GET /book/python-basics HTTP/1.1" 200 4521 "https://books.weniv.co.kr/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:23:12 +0900] "GET /stats HTTP/1.1" 200 2341 "https://weniv.link/dashboard" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:24:45 +0900] "GET /calendar/2024 HTTP/1.1" 200 3421 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
178.90.234.156 - - [29/Nov/2024:08:25:23 +0900] "POST /contact HTTP/1.1" 200 567 "https://weniv.co.kr/about" "Edge/120.0.0.0"
90.234.156.78 - - [29/Nov/2024:08:26:34 +0900] "GET /assignments HTTP/1.1" 200 4532 "https://canvas.weniv.co.kr/" "Firefox/121.0"
145.67.89.234 - - [29/Nov/2024:08:27:56 +0900] "GET /practice HTTP/1.1" 200 3421 "https://sql.weniv.co.kr/learn" "Chrome/120.0.0.0"
67.89.234.156 - - [29/Nov/2024:08:28:12 +0900] "GET /ebook/javascript HTTP/1.1" 200 5632 "https://books.weniv.co.kr/" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:29:45 +0900] "POST /shorten/custom HTTP/1.1" 201 445 "https://weniv.link/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:30:23 +0900] "GET /reminder HTTP/1.1" 200 2341 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:31:34 +0900] "GET /courses HTTP/1.1" 200 6789 "https://weniv.co.kr/learning" "Edge/120.0.0.0"
78.90.234.156 - - [29/Nov/2024:08:32:56 +0900] "POST /submit HTTP/1.1" 200 892 "https://canvas.weniv.co.kr/" "Safari/605.1.15"
90.234.156.78 - - [29/Nov/2024:08:33:12 +0900] "GET /advanced HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/courses" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:34:45 +0900] "GET /shop HTTP/1.1" 200 3421 "https://books.weniv.co.kr/" "Chrome/120.0.0.0"
`;
 
function analyzeLogs(logData) {
    // 정규표현식 패턴 - Referer 정보 포함
    const pattern = /(\d+\.\d+\.\d+\.\d+).*\[(\d+\/\w+\/\d+:(\d+):\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"/;
    
    // 결과를 저장할 객체
    const results = {
        ipCounts: {},
        hourCounts: {},
        serviceCounts: {}
    };
    
    function extractService(referer) {
        if (!referer || referer === '-') {
            return 'direct';
        }
        
        const servicePatterns = [
            { pattern: /sql\.weniv\.co\.kr/, name: '위니브 SQL' },
            { pattern: /books\.weniv\.co\.kr/, name: '위니북스' },
            { pattern: /weniv\.link/, name: '위니브 링크' },
            { pattern: /time\.weniv\.co\.kr/, name: '위니브 타이머' },
            { pattern: /canvas\.weniv\.co\.kr/, name: '위니브 캔버스' },
            { pattern: /weniv\.co\.kr/, name: '위니브 메인' }
        ];
        
        const service = servicePatterns.find(s => s.pattern.test(referer));
        return service ? service.name : 'other';
    }
    
    // 각 로그 라인 분석
    const lines = logData.split('\n').filter(line => line.trim());
    
    lines.forEach(line => {
        const match = line.match(pattern);
        if (match) {
            const [_, ip, timestamp, hour, method, path, referer] = match;
            
            // IP 주소별 카운트
            results.ipCounts[ip] = (results.ipCounts[ip] || 0) + 1;
            
            // 시간대별 카운트
            results.hourCounts[hour] = (results.hourCounts[hour] || 0) + 1;
            
            // 서비스별 카운트
            const service = extractService(referer);
            results.serviceCounts[service] = (results.serviceCounts[service] || 0) + 1;
        }
    });
    
    return results;
}
 
function printResults(results) {
    console.log("=== IP 주소별 접속 횟수 ===");
    Object.entries(results.ipCounts)
        .sort(([,a], [,b]) => b - a)
        .forEach(([ip, count]) => {
            console.log(`${ip}: ${count}회`);
        });
    
    console.log("\n=== 시간대별 접속 통계 ===");
    Object.entries(results.hourCounts)
        .sort(([a], [b]) => Number(a) - Number(b))
        .forEach(([hour, count]) => {
            console.log(`${hour}시: ${count}회`);
        });
    
    console.log("\n=== 서비스별 접속 통계 TOP 5 ===");
    Object.entries(results.serviceCounts)
        .sort(([,a], [,b]) => b - a)
        .slice(0, 5)
        .forEach(([service, count]) => {
            console.log(`${service}: ${count}회`);
        });
}
 
// 사용 예시
const results = analyzeLogs(data);
printResults(results);
const data = `32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"
187.234.121.55 - - [29/Nov/2024:08:16:12 +0900] "GET /course/python HTTP/1.1" 200 3267 "https://books.weniv.co.kr/" "Mozilla/5.0"
91.45.234.178 - - [29/Nov/2024:08:17:01 +0900] "POST /shorten HTTP/1.1" 201 342 "https://weniv.link/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:18:23 +0900] "GET /schedule HTTP/1.1" 200 2891 "https://time.weniv.co.kr/" "Safari/605.1.15"
211.89.145.67 - - [29/Nov/2024:08:19:45 +0900] "GET / HTTP/1.1" 200 1523 "https://weniv.co.kr" "Firefox/121.0"
167.89.234.90 - - [29/Nov/2024:08:20:11 +0900] "GET /dashboard HTTP/1.1" 200 5632 "https://canvas.weniv.co.kr/" "Edge/120.0.0.0"
45.178.234.89 - - [29/Nov/2024:08:21:34 +0900] "POST /query HTTP/1.1" 200 892 "https://sql.weniv.co.kr/editor" "Chrome/120.0.0.0"
123.45.167.89 - - [29/Nov/2024:08:22:56 +0900] "GET /book/python-basics HTTP/1.1" 200 4521 "https://books.weniv.co.kr/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:23:12 +0900] "GET /stats HTTP/1.1" 200 2341 "https://weniv.link/dashboard" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:24:45 +0900] "GET /calendar/2024 HTTP/1.1" 200 3421 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
178.90.234.156 - - [29/Nov/2024:08:25:23 +0900] "POST /contact HTTP/1.1" 200 567 "https://weniv.co.kr/about" "Edge/120.0.0.0"
90.234.156.78 - - [29/Nov/2024:08:26:34 +0900] "GET /assignments HTTP/1.1" 200 4532 "https://canvas.weniv.co.kr/" "Firefox/121.0"
145.67.89.234 - - [29/Nov/2024:08:27:56 +0900] "GET /practice HTTP/1.1" 200 3421 "https://sql.weniv.co.kr/learn" "Chrome/120.0.0.0"
67.89.234.156 - - [29/Nov/2024:08:28:12 +0900] "GET /ebook/javascript HTTP/1.1" 200 5632 "https://books.weniv.co.kr/" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:29:45 +0900] "POST /shorten/custom HTTP/1.1" 201 445 "https://weniv.link/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:30:23 +0900] "GET /reminder HTTP/1.1" 200 2341 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:31:34 +0900] "GET /courses HTTP/1.1" 200 6789 "https://weniv.co.kr/learning" "Edge/120.0.0.0"
78.90.234.156 - - [29/Nov/2024:08:32:56 +0900] "POST /submit HTTP/1.1" 200 892 "https://canvas.weniv.co.kr/" "Safari/605.1.15"
90.234.156.78 - - [29/Nov/2024:08:33:12 +0900] "GET /advanced HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/courses" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:34:45 +0900] "GET /shop HTTP/1.1" 200 3421 "https://books.weniv.co.kr/" "Chrome/120.0.0.0"
`;
 
function analyzeLogs(logData) {
    // 정규표현식 패턴 - Referer 정보 포함
    const pattern = /(\d+\.\d+\.\d+\.\d+).*\[(\d+\/\w+\/\d+:(\d+):\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"/;
    
    // 결과를 저장할 객체
    const results = {
        ipCounts: {},
        hourCounts: {},
        serviceCounts: {}
    };
    
    function extractService(referer) {
        if (!referer || referer === '-') {
            return 'direct';
        }
        
        const servicePatterns = [
            { pattern: /sql\.weniv\.co\.kr/, name: '위니브 SQL' },
            { pattern: /books\.weniv\.co\.kr/, name: '위니북스' },
            { pattern: /weniv\.link/, name: '위니브 링크' },
            { pattern: /time\.weniv\.co\.kr/, name: '위니브 타이머' },
            { pattern: /canvas\.weniv\.co\.kr/, name: '위니브 캔버스' },
            { pattern: /weniv\.co\.kr/, name: '위니브 메인' }
        ];
        
        const service = servicePatterns.find(s => s.pattern.test(referer));
        return service ? service.name : 'other';
    }
    
    // 각 로그 라인 분석
    const lines = logData.split('\n').filter(line => line.trim());
    
    lines.forEach(line => {
        const match = line.match(pattern);
        if (match) {
            const [_, ip, timestamp, hour, method, path, referer] = match;
            
            // IP 주소별 카운트
            results.ipCounts[ip] = (results.ipCounts[ip] || 0) + 1;
            
            // 시간대별 카운트
            results.hourCounts[hour] = (results.hourCounts[hour] || 0) + 1;
            
            // 서비스별 카운트
            const service = extractService(referer);
            results.serviceCounts[service] = (results.serviceCounts[service] || 0) + 1;
        }
    });
    
    return results;
}
 
function printResults(results) {
    console.log("=== IP 주소별 접속 횟수 ===");
    Object.entries(results.ipCounts)
        .sort(([,a], [,b]) => b - a)
        .forEach(([ip, count]) => {
            console.log(`${ip}: ${count}회`);
        });
    
    console.log("\n=== 시간대별 접속 통계 ===");
    Object.entries(results.hourCounts)
        .sort(([a], [b]) => Number(a) - Number(b))
        .forEach(([hour, count]) => {
            console.log(`${hour}시: ${count}회`);
        });
    
    console.log("\n=== 서비스별 접속 통계 TOP 5 ===");
    Object.entries(results.serviceCounts)
        .sort(([,a], [,b]) => b - a)
        .slice(0, 5)
        .forEach(([service, count]) => {
            console.log(`${service}: ${count}회`);
        });
}
 
// 사용 예시
const results = analyzeLogs(data);
printResults(results);
2.2 Python 구현
import re
from datetime import datetime
 
# 테스트할 한 줄의 로그
log_line = '32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"'
 
# 정규표현식 패턴
pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(\d+/\w+/\d+:\d+:\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"'
 
# 매칭 수행
m = re.search(pattern, log_line)
 
# 변수 할당 및 결과 출력
if m:
    ip, timestamp, method, path, referer = m.groups()
    
    print('전체 매칭:', m.group(0))
    print('IP 주소 (group 1):', ip)
    print('타임스탬프 (group 2):', timestamp)
    print('HTTP 메서드 (group 3):', method)
    print('경로 (group 4):', path)
    print('리퍼러 (group 5):', referer)
    
    # 시간 추출 테스트
    try:
        dt = datetime.strptime(timestamp, '%d/%b/%Y:%H:%M:%S')
        print('\n시간 파싱 결과:')
        print('시간:', dt.hour)
        print('전체 datetime:', dt)
    except ValueError as e:
        print('시간 파싱 실패:', e)
else:
    print('매칭 실패!')
import re
from datetime import datetime
 
# 테스트할 한 줄의 로그
log_line = '32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"'
 
# 정규표현식 패턴
pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(\d+/\w+/\d+:\d+:\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"'
 
# 매칭 수행
m = re.search(pattern, log_line)
 
# 변수 할당 및 결과 출력
if m:
    ip, timestamp, method, path, referer = m.groups()
    
    print('전체 매칭:', m.group(0))
    print('IP 주소 (group 1):', ip)
    print('타임스탬프 (group 2):', timestamp)
    print('HTTP 메서드 (group 3):', method)
    print('경로 (group 4):', path)
    print('리퍼러 (group 5):', referer)
    
    # 시간 추출 테스트
    try:
        dt = datetime.strptime(timestamp, '%d/%b/%Y:%H:%M:%S')
        print('\n시간 파싱 결과:')
        print('시간:', dt.hour)
        print('전체 datetime:', dt)
    except ValueError as e:
        print('시간 파싱 실패:', e)
else:
    print('매칭 실패!')
import re
from collections import Counter
from datetime import datetime
 
data = '''32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"
187.234.121.55 - - [29/Nov/2024:08:16:12 +0900] "GET /course/python HTTP/1.1" 200 3267 "https://books.weniv.co.kr/" "Mozilla/5.0"
91.45.234.178 - - [29/Nov/2024:08:17:01 +0900] "POST /shorten HTTP/1.1" 201 342 "https://weniv.link/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:18:23 +0900] "GET /schedule HTTP/1.1" 200 2891 "https://time.weniv.co.kr/" "Safari/605.1.15"
211.89.145.67 - - [29/Nov/2024:08:19:45 +0900] "GET / HTTP/1.1" 200 1523 "https://weniv.co.kr" "Firefox/121.0"
167.89.234.90 - - [29/Nov/2024:08:20:11 +0900] "GET /dashboard HTTP/1.1" 200 5632 "https://canvas.weniv.co.kr/" "Edge/120.0.0.0"
45.178.234.89 - - [29/Nov/2024:08:21:34 +0900] "POST /query HTTP/1.1" 200 892 "https://sql.weniv.co.kr/editor" "Chrome/120.0.0.0"
123.45.167.89 - - [29/Nov/2024:08:22:56 +0900] "GET /book/python-basics HTTP/1.1" 200 4521 "https://books.weniv.co.kr/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:23:12 +0900] "GET /stats HTTP/1.1" 200 2341 "https://weniv.link/dashboard" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:24:45 +0900] "GET /calendar/2024 HTTP/1.1" 200 3421 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
178.90.234.156 - - [29/Nov/2024:08:25:23 +0900] "POST /contact HTTP/1.1" 200 567 "https://weniv.co.kr/about" "Edge/120.0.0.0"
90.234.156.78 - - [29/Nov/2024:08:26:34 +0900] "GET /assignments HTTP/1.1" 200 4532 "https://canvas.weniv.co.kr/" "Firefox/121.0"
145.67.89.234 - - [29/Nov/2024:08:27:56 +0900] "GET /practice HTTP/1.1" 200 3421 "https://sql.weniv.co.kr/learn" "Chrome/120.0.0.0"
67.89.234.156 - - [29/Nov/2024:08:28:12 +0900] "GET /ebook/javascript HTTP/1.1" 200 5632 "https://books.weniv.co.kr/" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:29:45 +0900] "POST /shorten/custom HTTP/1.1" 201 445 "https://weniv.link/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:30:23 +0900] "GET /reminder HTTP/1.1" 200 2341 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:31:34 +0900] "GET /courses HTTP/1.1" 200 6789 "https://weniv.co.kr/learning" "Edge/120.0.0.0"
78.90.234.156 - - [29/Nov/2024:08:32:56 +0900] "POST /submit HTTP/1.1" 200 892 "https://canvas.weniv.co.kr/" "Safari/605.1.15"
90.234.156.78 - - [29/Nov/2024:08:33:12 +0900] "GET /advanced HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/courses" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:34:45 +0900] "GET /shop HTTP/1.1" 200 3421 "https://books.weniv.co.kr/" "Chrome/120.0.0.0"
'''
 
def analyze_logs(log_data):
    # 정규표현식 패턴 - Referer 정보 포함
    pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(\d+/\w+/\d+:\d+:\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"'
    
    # 결과를 저장할 딕셔너리
    results = {
        'ip_counts': Counter(),
        'hour_counts': Counter(),
        'service_counts': Counter()
    }
    
    def extract_service(referer):
        if not referer or referer == '-':
            return 'direct'
            
        service_patterns = {
            r'sql\.weniv\.co\.kr': 'SQL 연습장',
            r'books\.weniv\.co\.kr': '위니브 책방',
            r'weniv\.link': '위니브 링크',
            r'time\.weniv\.co\.kr': '위니브 시간표',
            r'canvas\.weniv\.co\.kr': '위니브 캔버스',
            r'weniv\.co\.kr': '위니브 메인'
        }
        
        for pattern, service in service_patterns.items():
            if re.search(pattern, referer):
                return service
        return 'other'
    
    # 각 로그 라인 분석
    for line in log_data.split('\n'):
        if not line.strip():
            continue
            
        m = re.search(pattern, line)
        if m:
            ip, timestamp, method, path, referer = m.groups()
            
            # IP 주소별 카운트
            results['ip_counts'][ip] += 1
            
            # 시간대별 카운트
            try:
                dt = datetime.strptime(timestamp, '%d/%b/%Y:%H:%M:%S')
                results['hour_counts'][dt.hour] += 1
            except ValueError:
                pass
            
            # 서비스별 카운트
            service = extract_service(referer)
            results['service_counts'][service] += 1
    
    return results
 
def print_results(results):
    print("=== IP 주소별 접속 횟수 ===")
    for ip, count in results['ip_counts'].most_common():
        print(f"{ip}: {count}회")
    
    print("\n=== 시간대별 접속 통계 ===")
    for hour in sorted(results['hour_counts'].keys()):
        print(f"{hour}시: {results['hour_counts'][hour]}회")
    
    print("\n=== 서비스별 접속 통계 TOP 5 ===")
    for service, count in results['service_counts'].most_common(5):
        print(f"{service}: {count}회")
 
# 사용 예시
results = analyze_logs(data)
print_results(results)
import re
from collections import Counter
from datetime import datetime
 
data = '''32.154.21.89 - - [29/Nov/2024:08:15:43 +0900] "GET /login HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/main" "Mozilla/5.0"
187.234.121.55 - - [29/Nov/2024:08:16:12 +0900] "GET /course/python HTTP/1.1" 200 3267 "https://books.weniv.co.kr/" "Mozilla/5.0"
91.45.234.178 - - [29/Nov/2024:08:17:01 +0900] "POST /shorten HTTP/1.1" 201 342 "https://weniv.link/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:18:23 +0900] "GET /schedule HTTP/1.1" 200 2891 "https://time.weniv.co.kr/" "Safari/605.1.15"
211.89.145.67 - - [29/Nov/2024:08:19:45 +0900] "GET / HTTP/1.1" 200 1523 "https://weniv.co.kr" "Firefox/121.0"
167.89.234.90 - - [29/Nov/2024:08:20:11 +0900] "GET /dashboard HTTP/1.1" 200 5632 "https://canvas.weniv.co.kr/" "Edge/120.0.0.0"
45.178.234.89 - - [29/Nov/2024:08:21:34 +0900] "POST /query HTTP/1.1" 200 892 "https://sql.weniv.co.kr/editor" "Chrome/120.0.0.0"
123.45.167.89 - - [29/Nov/2024:08:22:56 +0900] "GET /book/python-basics HTTP/1.1" 200 4521 "https://books.weniv.co.kr/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:23:12 +0900] "GET /stats HTTP/1.1" 200 2341 "https://weniv.link/dashboard" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:24:45 +0900] "GET /calendar/2024 HTTP/1.1" 200 3421 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
178.90.234.156 - - [29/Nov/2024:08:25:23 +0900] "POST /contact HTTP/1.1" 200 567 "https://weniv.co.kr/about" "Edge/120.0.0.0"
90.234.156.78 - - [29/Nov/2024:08:26:34 +0900] "GET /assignments HTTP/1.1" 200 4532 "https://canvas.weniv.co.kr/" "Firefox/121.0"
145.67.89.234 - - [29/Nov/2024:08:27:56 +0900] "GET /practice HTTP/1.1" 200 3421 "https://sql.weniv.co.kr/learn" "Chrome/120.0.0.0"
67.89.234.156 - - [29/Nov/2024:08:28:12 +0900] "GET /ebook/javascript HTTP/1.1" 200 5632 "https://books.weniv.co.kr/" "Safari/605.1.15"
89.234.156.78 - - [29/Nov/2024:08:29:45 +0900] "POST /shorten/custom HTTP/1.1" 201 445 "https://weniv.link/" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:30:23 +0900] "GET /reminder HTTP/1.1" 200 2341 "https://time.weniv.co.kr/" "Chrome/120.0.0.0"
156.78.90.234 - - [29/Nov/2024:08:31:34 +0900] "GET /courses HTTP/1.1" 200 6789 "https://weniv.co.kr/learning" "Edge/120.0.0.0"
78.90.234.156 - - [29/Nov/2024:08:32:56 +0900] "POST /submit HTTP/1.1" 200 892 "https://canvas.weniv.co.kr/" "Safari/605.1.15"
90.234.156.78 - - [29/Nov/2024:08:33:12 +0900] "GET /advanced HTTP/1.1" 200 4521 "https://sql.weniv.co.kr/courses" "Firefox/121.0"
234.156.78.90 - - [29/Nov/2024:08:34:45 +0900] "GET /shop HTTP/1.1" 200 3421 "https://books.weniv.co.kr/" "Chrome/120.0.0.0"
'''
 
def analyze_logs(log_data):
    # 정규표현식 패턴 - Referer 정보 포함
    pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(\d+/\w+/\d+:\d+:\d+:\d+).*\] "(\w+) ([^"]+)" \d+ \d+ "([^"]*)"'
    
    # 결과를 저장할 딕셔너리
    results = {
        'ip_counts': Counter(),
        'hour_counts': Counter(),
        'service_counts': Counter()
    }
    
    def extract_service(referer):
        if not referer or referer == '-':
            return 'direct'
            
        service_patterns = {
            r'sql\.weniv\.co\.kr': 'SQL 연습장',
            r'books\.weniv\.co\.kr': '위니브 책방',
            r'weniv\.link': '위니브 링크',
            r'time\.weniv\.co\.kr': '위니브 시간표',
            r'canvas\.weniv\.co\.kr': '위니브 캔버스',
            r'weniv\.co\.kr': '위니브 메인'
        }
        
        for pattern, service in service_patterns.items():
            if re.search(pattern, referer):
                return service
        return 'other'
    
    # 각 로그 라인 분석
    for line in log_data.split('\n'):
        if not line.strip():
            continue
            
        m = re.search(pattern, line)
        if m:
            ip, timestamp, method, path, referer = m.groups()
            
            # IP 주소별 카운트
            results['ip_counts'][ip] += 1
            
            # 시간대별 카운트
            try:
                dt = datetime.strptime(timestamp, '%d/%b/%Y:%H:%M:%S')
                results['hour_counts'][dt.hour] += 1
            except ValueError:
                pass
            
            # 서비스별 카운트
            service = extract_service(referer)
            results['service_counts'][service] += 1
    
    return results
 
def print_results(results):
    print("=== IP 주소별 접속 횟수 ===")
    for ip, count in results['ip_counts'].most_common():
        print(f"{ip}: {count}회")
    
    print("\n=== 시간대별 접속 통계 ===")
    for hour in sorted(results['hour_counts'].keys()):
        print(f"{hour}시: {results['hour_counts'][hour]}회")
    
    print("\n=== 서비스별 접속 통계 TOP 5 ===")
    for service, count in results['service_counts'].most_common(5):
        print(f"{service}: {count}회")
 
# 사용 예시
results = analyze_logs(data)
print_results(results)
3.1 실무 활용 프로젝트4장 알고리즘 문제