728x90
반응형

 

자바 개발자라면 API 호출은 일상 다반사입니다. 프론트엔드에서 주로 사용되는 API 호출 방식과 달리, 백엔드에서는 Java를 이용한 호출이 많이 이루어집니다. 특히 Apache HttpClient와 HttpURLConnection을 비교해볼 때, 각각의 장단점과 사용법을 알아보는 것이 중요합니다. 이 글에서는 두 라이브러리를 활용한 API 호출 방법을 자세히 설명하며, 왜 Apache HttpClient가 더 간결하고 효율적인지에 대해 탐구해보겠습니다.

 


 

HttpURLConnection
HttpURLConnection

 

HttpURLConnection을 이용한 API 호출

Java의 기본 라이브러리인 HttpURLConnection을 사용하면, 다음과 같은 방식으로 GET 요청을 보낼 수 있습니다.

 

HttpURLConnection을 이용한 기본 GET 요청 예제 코드

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpGetRequestExample {
    public static void main(String[] args) {
        try {
            // 기본 URL
            String baseUrl = "http://example.com/api";
            
            // 파라미터 추가
            String params = "?param1=value1&param2=value2";
            String urlString = baseUrl + params;
            
            // URL 객체 생성
            URL url = new URL(urlString);
            
            // HttpURLConnection 객체 생성
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            
            // 요청 방식 선택 (GET)
            connection.setRequestMethod("GET");
            
            // 요청 헤더 추가 (옵션)
            connection.setRequestProperty("User-Agent", "Mozilla/5.0");
            
            // 응답 코드 가져오기
            int responseCode = connection.getResponseCode();
            System.out.println("GET Response Code :: " + responseCode);
            
            // 응답 코드가 200 OK인 경우, 응답 내용 읽기
            if (responseCode == HttpURLConnection.HTTP_OK) {
                BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String inputLine;
                StringBuffer response = new StringBuffer();
                
                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }
                in.close();
                
                // 응답 내용 출력
                System.out.println(response.toString());
            } else {
                System.out.println("GET request not worked");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

Apache HttpClient의 등장

 

반면, Apache HttpClient 라이브러리를 사용하면, 코드가 훨씬 간결해집니다. Maven을 사용하는 프로젝트의 경우, 다음과 같이 의존성을 추가해야 합니다.

 

Apache HttpClient를 이용한 GET 방식은 다음과 같습니다.

 

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

 

import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClientGetExample {
    public static void main(String[] args) {
        // HttpClient 객체 생성
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            
            // URIBuilder를 사용하여 URL 및 파라미터 구성
            URIBuilder uriBuilder = new URIBuilder("http://example.com/api");
            uriBuilder.addParameter("param1", "value1");
            uriBuilder.addParameter("param2", "value2");
            
            // HttpGet 객체 생성
            HttpGet httpGet = new HttpGet(uriBuilder.build());
            
            // 요청 헤더 추가 (옵션)
            httpGet.addHeader("User-Agent", "Mozilla/5.0");
            
            // 요청 실행 및 응답 받기
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                // 응답 본문을 String으로 변환
                String result = EntityUtils.toString(response.getEntity());
                
                // 결과 출력
                System.out.println(result);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

 

 

Apache HttpClient를 사용하면 URIBuilder를 통해 URL과 파라미터를 쉽게 구성할 수 있고, HttpGet 객체를 생성하여 요청을 보낼 수 있습니다. 이는 코드를 간결하게 만들 뿐만 아니라, 개발자가 HTTP 통신의 세부사항에 덜 집중할 수 있게 해줍니다.

 


반응형

POST 요청 방식 비교

 

POST 요청에 대해서도 비교해봅시다. HttpURLConnection을 사용할 경우, OutputStream을 통해 직접 데이터를 전송해야 합니다.

 

HttpURLConnection을 이용한 POST 요청 예제 코드

 

import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;

public class PostExample {
    public static void main(String[] args) {
        String targetURL = "http://example.com/api/resource";
        HttpURLConnection connection = null;

        try {
            // URL 객체 생성 및 연결 설정
            URL url = new URL(targetURL);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            connection.setDoOutput(true); // OutputStream을 사용하여 요청 본문에 데이터를 쓸 것임을 지정

            // 요청 본문에 JSON 데이터 작성
            String jsonInputString = "{\"name\": \"John\", \"age\": 30}";
            try (OutputStream os = connection.getOutputStream()) {
                byte[] input = jsonInputString.getBytes("utf-8");
                os.write(input, 0, input.length);
            }

            // 응답 받기 및 출력
            try (Scanner scanner = new Scanner(connection.getInputStream())) {
                while (scanner.hasNextLine()) {
                    System.out.println(scanner.nextLine());
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}

 

 

반면, Apache HttpClient를 사용하면, 객체를 JSON으로 변환하여 HttpPost 객체에 전달하는 방식으로 간결하게 처리할 수 있습니다.

 

Apache HttpClient를 이용한 POST 요청 예제 코드

 

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class JsonExample {
    public static void main(String[] args) throws Exception {
        // 객체 생성
        MyObject myObject = new MyObject();
        myObject.setName("John Doe");
        myObject.setAge(30);

        // 객체를 JSON으로 변환
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(myObject);

        // HTTP POST 요청 설정
        HttpPost post = new HttpPost("http://example.com/api");
        StringEntity entity = new StringEntity(json);
        post.setEntity(entity);
        post.setHeader("Accept", "application/json");
        post.setHeader("Content-type", "application/json");

        // 요청 실행
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            httpClient.execute(post);
        }
    }

    // 자바 객체 정의
    public static class MyObject {
        private String name;
        private int age;

        // getters and setters
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public int getAge() { return age; }
        public void setAge(int age) { this.age = age; }
    }
}

 


 

 

Apache_HttpClient
Apache_HttpClient

마무리

이 글에서는 Java에서 API 호출을 위한 두 가지 방법, 즉 HttpURLConnection과 Apache HttpClient의 사용법을 비교해보았습니다. Apache HttpClient의 사용이 더 간결하고 유연하다는 것을 확인할 수 있었습니다.

Java 개발에 있어서 이러한 라이브러리의 선택과 활용은 프로젝트의 효율성을 크게 향상시킬 수 있습니다.

 

더 궁금한 점이 있으시다면 댓글로 질문해주세요. 감사합니다.

728x90
반응형
728x90
반응형

새 창을 열고 닫힐 때 특정 기능을 실행하는 방법 ?

웹 사이트를 개발하다 보면, 사용자의 편의성을 높이기 위해 새창을 열고, 해당창이 닫힐 때 특정 동작을 수행하고 싶을 때가 있습니다. 이번 포스팅에서는 이러한 상황을 'React'를 활용해 어떻게 구현할 수 있는지 알아보도록 하겠습니다.

 

먼저 기본이 되는 몇 가지 JavaScript 메소드에 대해 알아보겠습니다:

 

  • window.open() : 새 창을 여는 메소드로, 이를 통해 반환받은 창 객체 참조를 저장할 수 있습니다.
  • setInterval() : 주기적으로 코드를 실행하게 해주는 타이머 함수입니다.
  • windowObject.closed : 창이 닫혔는지 확인하는 속성으로, 창이 닫히면 true를 반환합니다.
  • clearInterval() : 기능을 사용하여 메모리 누수를 방지하려면 상위 구성 요소가 마운트 해제될 때 간격을 지워야 합니다

code_developer
code_developer

 


 

반응형

 


 

그럼 전체 코드를 한번 봅시다.

 

import React, { useEffect, useState } from 'react';

function ParentComponent() {
  const [childWindow, setChildWindow] = useState(null);

  // 하위 창이 닫힐 때 실행할 콜백 함수
  const onChildClose = () => {
    console.log('Child window closed');
    // 하위 창이 닫힌 후에 실행할 코드
  };

  // 하위 창을 여는 기능
  const openChildWindow = () => {
    const windowFeatures = 'width=600,height=400,resizable,scrollbars=yes,status=1';
    const childWin = window.open('/child-path', 'ChildWindow', windowFeatures);
    setChildWindow(childWin);
  };

  useEffect(() => {
    let intervalId;

    if (childWindow) {
      // 매초 자식 창이 닫혀 있는지 확인한다.
      intervalId = setInterval(() => {
        if (childWindow.closed) {
          clearInterval(intervalId);
          onChildClose();
        }
      }, 1000);
    }

    // 마운트 해제 시 Interval 해제
    return () => {
      if (intervalId) {
        clearInterval(intervalId);
      }
    };
  }, [childWindow]);

  return (
    <div>
      <button onClick={openChildWindow}>Open Child Window</button>
    </div>
  );
}

export default ParentComponent;

 

 

이 코드는 React의 useStateuseEffect 훅을 사용하여 새 창을 열고, 해당 창이 닫힐 때 onChildClose 함수를 실행합니다. setInterval 함수를 사용하여 1초마다 새 창의 상태를 확인하고, 닫힌 경우에는 clearInterval로 타이머를 해제한 후, 콜백 함수를 호출합니다.

 

이와 같이 구현하면 새 창을 열고 닫힐 때 원하는 기능을 실행할 수 있으며, 이는 웹 애플리케이션의 사용성을 향상시키는 데 도움이 됩니다. 오류나 더 좋은 구현 방법에 대한 의견이 있다면, 피드백을 주시기 바랍니다. 

 

 


 

 

 

IT 대모험 아직 끝나지 않았습니다.

언제나 공부하는 마음으로 새로운 언어, 새로운 환경에 적응해 나가길 바랍니다.

그럼 다음 또 다른 포스팅으로 찾아 뵙겠습니다~ 

728x90
반응형
728x90
반응형

이전 포스팅에서 express의 미들웨어와 라우터 차이점에 대해서

 

미들웨어는 서버와 클라이언트 간의 중간 매개 역할을 담당합니다

그래서 서버 api 요청할 경우 미들웨어에서 공통업무를 할 수 있게 합니다

에러 페이지, 존재하지 않는 페이지 등

 

조금 더 상세한 정보를 알고 싶으시면 아래의 링크를 확인해주시길 바랍니다

 

 

 

2022.09.16 - [IT_Web/Nodejs] - Node express 서버의 미들웨어, 라우터 개념 제대로 알기 및 next함수 활용하기

 

Node express 서버의 미들웨어, 라우터 개념 제대로 알기 및 next함수 활용하기

이전 포스팅에서 express 서버 미들웨어 적용에 대해서 미들웨어가 무엇이며, 미들웨어를 어떻게 활용하는지에 대해서 알아보았습니다 미들웨어를 사용함에 있어 라우팅이 호출될 때 공통적으로

tantangerine.tistory.com

 


 

Express 라우터 url 파라미터 및 쿼리스트링 컨트롤 하기

 

라우터에서 서버 api요청 시 url의 파라미터와 쿼리 스트링을 제어할 수 있습니다

 

이 방법은 페이지를 이동하거나

서버에 요청하여 통신을 할 때 필요한 값을 전달할 때 자주 사용합니다

 

 


 

파라미터(parameters)란

 

번역하면 매개 변수라고 하며

url의 주소에 어떠한 특정한 값을 변수에 담아 유동적으로 

변화하는 값을 사용합니다

그래서 아래와 같이 '/top/:page'를 사용하면

/top/ 이후의 값은 변수에 담겨 라우터에서 사용이 가능합니다

 

 


 

쿼리스트링(Query String)

 

Query만 번역하면 의문,

String 컴퓨터 자료형으로 해석하면 문자를 의미합니다

그래서 이 두 가지를 합치면 의문 문자라고 해서

'/top/:page?id=컴퓨터&name=삼성'

위의 방식으로 의문(?)부터 시작된 문자를 쿼리 스트링이라고 합니다

 

 


코드 분석

 

그럼 코드를 보면서 나머지를 설명하겠습니다

아래와 같이 코드를 작성하고 npm start를 시작합니다

 

npm init, npm i express, npm i -d nodemon 등 설치는 

이전 포스팅을 찾아보시면 나와있으니 확인 부탁드립니다

 

 

const express = require('express');

const app = express();
app.set('port', process.env.PORT || 3000);

app.get('/top/:page', (req, res, next) => {
  console.log('top params: ', req.params)
  console.log('top queryString: ', req.query)
  res.send(`top: ${req.params.page} / id: ${req.query.id} name: ${req.query.name}`)
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

 

 

 

위와 같이 실행하면 아래와 같이 이미지가 브라우저 및 콘솔 창에 노출됩니다

http://localhost:3000/top/head를 주소창에 입력했을 경우 

console.log함수에 노출되는 정보를 확인할 수 있습니다 

 

 

 

express_파라미터_쿼리스트링_1
express_파라미터_쿼리스트링 브라우저 확인

 

 

req.params, req.query로 접근해서 파라미터와 쿼리스트링을 가져올 수 있습니다

그리고 undefined가 발생하는 값을

브라우저 주소창에 http://localhost:3000/top/head?id=컴퓨터&name=삼성

입력하게 된다면 id, name 값이 할당될 것입니다

그 값은 아래의 이미지에서 확인할 수 있습니다

 

 

express_파라미터_쿼리스트링_2
express_파라미터_쿼리스트링 데이터

 

 

위의 데이터를 확인하고 아래와 같이 노출할 수 있는

코드를 작성할 수 있습니다

 

 

express_파라미터_쿼리스트링 브라우저 노출확인

 

 


이렇게 오늘은 파라미터와 쿼리스트링을 활용하는 방법을 알아보았습니다

 

그렇게 어렵지 않으며 nodeexpress 말고도 자바에서도 비슷한 방식으로

사용하고 있는 것만 알고 계시면 될 것 같습니다

 

하루에 1포스팅을 실천하기 위해 

공부도 나름 동기부여도 되는 것 같습니다

 

그럼 같이 파이팅 하시고 힘내세요!

우리의 IT 대모험 멀고도 머니깐요

 

 

728x90
반응형
728x90
반응형

이전 포스팅에서 express 서버 미들웨어 적용에 대해서

 

미들웨어가 무엇이며, 미들웨어를 어떻게 활용하는지에 대해서 알아보았습니다

미들웨어를 사용함에 있어 라우팅이 호출될 때 공통적으로 처리하거나

에러 미들웨어를 만들어 에러 발생 시 특정 함수를 실행하는 등

공통적인 부분을 담당하고 있는 미들웨어를 적용해보았습니다

 

더 상세한 정보를 알고 싶으시다면 아래의 링크를 확인해보세요

 

 

2022.09.15 - [IT_Web/Nodejs] - Node에서 express 서버 미들웨어 적용하기 그리고 middleware를 쓰는 이유가 뭘까??

 

Node에서 express 서버 미들웨어 적용하기 그리고 middleware를 쓰는 이유가 뭘까??

이전 포스팅 express 패키지 활용하여 서버 구축 및 html 서빙에 대해서 express에서 html 서빙 방법은 정말 간단합니다 express 내에서 html관련 패키지를 사용하고 있어 개발자들은 단지 메서드를 호출

tantangerine.tistory.com

 

 

express에서 미들웨어와 라우터 개념 제대로 알기

 

다른 개발자 분들은 use함수가 미들웨어가 아니라

get 함수 등(이외에도 많은 것들이 있습니다 post 등)

함수 내의 콜백 함수가 미들웨어라고 말하시는 분들이 있습니다

 

충분히 그렇게 생각할 수도 있지만

제 생각은 조금 다릅니다

 

미들웨어는 use 함수이며

라우터는 get 함수가 맞다고 생각합니다

 

그 이유는 미들웨어 격인 use 함수는 

라우터 함수가 호출될 때를 감지해서 우선 실행되는 함수입니다

클라이언트와 서버 간에 중간 매개 역할을 하여 컨트롤이 가능합니다

그래서 use 함수 자체가 미들웨어라고 할 수 있습니다

 

그리고 next 함수는 라우터와 미들웨어에서 편의성을 위한 기능일 뿐이라는 것입니다

 

그리고 엄연히 따지면 get 함수도 라우터가 아닌 서버 api 호출 문으로도 해석이 가능합니다

하지만 지금 현재 브라우저 주소 url서버의 연동하여 페이지를 전송받기 때문

라우터라고 부르는 것이 더 합리적일 것입니다 

 

 

 

express 서버 미들웨어와 라우터에 있는 콜백 함수 next 활용법

 

콜백 함수 next 함수를 호출하면

 

최우선 호출되는 것은 자신의 또 다른 콜백 함수가 호출됩니다

하지만 자신의 콜백 함수를 호출하지 않고 다음 미들웨어나 라우터 함수를 호출하기 위해서는 

next('route')로 인자 값을 'route'로 입력하는 것입니다

 

하지만 이때 미들웨어인 use함수는 다음 미들웨어나 라우터 함수를 호출하는 것이 아닌

자신의 콜백 함수를 다시 호출한다는 점이 미들웨어와 라우터가 다른 기능입니다

 

* 미들웨어에서 next('route')를 호출하면 자기 콜백 함수를 우선 호출하고

그다음 라우터를 실행하게 됩니다 

 

 

그럼 아래의 코드를 실행해 봅시다

 

 

const express = require('express');
const path = require('path');

const app = express();
app.set('port', process.env.PORT || 3000);

app.use((req, res, next) => {
  try {
    console.log('express server: use ::: 1')
    next('route')
  } catch (error) {
    console.log('express server: error ::: use ::: 1', error)
    next(error)
  }
}, (req, res, next) => {
  console.log('express server: use ::: 2')
   next()
})

app.get('/', (req, res, next) => {
  try {
    console.log('express server: main ::: 1')
    if(true){
      next('route')
    } else {
      next()
    }
  } catch (error) {
    console.log('express server: error ::: main ::: 1', error)
    next(error)
  }
}, (req, res, next) => {
  console.log('express server: main ::: 2')
  next()
})

app.get('/', (req, res, next) => {
  try {
    res.json({ page: 'main'})
    console.log('express server: main ::: 3')
  } catch (error) {
    console.log('express server: error ::: main :: 3', error)
    next(error)
  }
});

app.get('/top', (req, res, next) => {
  try {
    console.log('express server: top ::: 3')
    res.json({ page: 'top'})
  } catch (error) {
    console.log('express server: error ::: top', error)
    next(error)
  }
});

app.use((req, res, next) => { 
  res.status(404).send('존재하지 않는 페이지 입니다(404)')
});

app.use((error, req, res, next) => {
  res.send('error 발생')
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

 

 

그럼 아래처럼 화면이 노출됩니다

 

미들웨어에서 next()를 사용해서 use ::: 1, use ::: 2 함수가 실행되며

그다음 main ::: 1 함수가 실행되고

main ::: 2는 건너뛰고

 main ::: 3 함수가 실행됩니다

 

 

 

 

이렇게 미들웨어와 라우터 제어하는 함수를 알아보았습니다

next함수 활용법과 라우터와 미들웨어 둘의 차이점을 알아보았습니다

 

라우터와 미들웨어에 존재하는 next함수에 대해 이야기하고 넘어가야겠습니다

express 공부한 지 얼마 안 되었지만

 

그렇게 좋은지 알 수가 없었습니다

 

next함수만 보더라도 라우터 get함수의 콜백 함수 next와 

미들웨어 use함수의 콜백 함수 next는 활용방식이 너무나도 다릅니다

 

use함수 내에서는 next('route') 통하지 않고

자기 내부의 콜백 함수를 다시 호출하게 됩니다

 

이렇게 동일한 네이밍을 가진 함수인자 값으로만

기능이 구분되는 것은 좋지 않은 방식입니다

 

그리고 use 함수의 에러 미들웨어 기능

동일한 use 함수에서 매개변수 인자 값의 개수 4개면

에러 미들웨어의 기능을 구현하게 된다는 것입니다

 

이 경우 인자 값을 누락하게 된다면 에러가 발생하거나 

전혀 생각지도 않은 기능이 구현된다는 것입니다 

 

애초에 use 함수가 아닌 확실하게 구분할 수 있는 네이밍이었다면

더 좋을 것 같다같다는 생각이었습니다

 

 


 

 

express 서버를 공부하면서

모듈을 조합해서 만든 라이브러리라는 것을 느꼈습니다

하지만 이런 것도 공부가 되리라 생각합니다

 

여러 언어를 배우면서 생각의 전환을 하거나

약간 내가 생각지도 못한 것들을 생각하게 한다는 것이지요

그러면서 저의 실력도 늘어나는 것이 아닐까 합니다

 

그럼 어려운 공부 조금 더 힘내셔서

우리의 IT 대모험을 헤쳐나가시길 기대합니다

 

그럼 다음 포스팅도 기대해주세요!

 

 

 

728x90
반응형
728x90
반응형

이전 포스팅 express 패키지 활용하여 서버 구축 및 html 서빙에 대해서

 

express에서 html 서빙 방법은 정말 간단합니다

express 내에서 html관련 패키지를 사용하고 있어

개발자들은 단지 메서드를 호출하는 것만으로 html 서빙을 할 수 있습니다

 

조금 더 자세한 내용을 알고 싶으시다면 아래의 링크를 클릭하시길 바랍니다

 

2022.09.14 - [IT_Web/Nodejs] - Node활용해서 express 서버로 HTML 노출 및 서빙하기

 

Node활용해서 express 서버로 HTML 노출 및 서빙하기

이전 글 Npm이 무엇이며 활용법에 대해서 node에서 npm을 빼고는 말을 할 수 없을 정도로 중요한 부분을 차지합니다 그 이유는 많은 사람들이 개발한 오픈 소스를 관리해 주기 때문이지요 npm을 잘

tantangerine.tistory.com

 

 

지금 글은 이전 포스팅과 이어지는 부분이 있으니

이전 포스팅이  궁금하시거나 필요한 부분이 있으면 위의 링크를 클릭하시길 바랍니다

 


 

express 서버 미들웨어 적용하기

 

일단 미들웨어란 무엇일까요??

 

사전적 의미로는 양 쪽을 연결하여 데이터를 주고받을 수 있도록

중간 매개 역할을 하는 소프트웨어라고 할 수 있습니다

 

 express 서버의 미들웨어는 중간 매개 역할이 핵심이라고 할 수 있습니다

아래의 app.get() 메서드를 라우팅이라고 합니다

 

라우팅을 할 경우 에러가 발생할 경우

특정 api url이 호출될 경우

존재하지 않은 api url 호출될 경우

 

특정 함수를 실행,

에러 페이지를 노출,

특정 페이지를 노출 등

 

여러 가지의 공통 작업을 할 수 있습니다

 

 

express 서버 미들웨어 적용하기

 

아래의 코드를 보시면 app.use()가 정의되어있습니다

이 함수는 라우팅을 호출할 경우 app.use()가 실행합니다

 

즉, 라우팅 get()은 함수의 인자 값이

클라이언트에서 보낸 api 호출 url이 동일한 라우팅이 실행됩니다

 

이때 라우팅이 호출되면 라우팅 get()보다 

우선 use()인 미들웨어가 호출을 감지하여 우선 실행합니다

그리고 next()를 실행하게 되면서 다음 미들웨어나 라우팅이 실행됩니다

 

이때 미들웨어의 위치도 중요합니다

 

기본적으로 실행 순서는 위에서부터 아래까지 실행합니다

그래서 라우팅이 호출될 때마다 미들웨어인 use()최상단부터 하단까지 실행된다고 생각하시면 됩니다

 

하지만 라우팅보다 미들웨어가 아래에 있다면 라우팅이 실행이 끝나는 순간

더 이상 실행하지 않고 종료가 됩니다

 

아래의 코드를 보면서 더 설명을 이어가겠습니다

 

 

 

const express = require('express');
const path = require('path');

const app = express();
app.set('port', process.env.PORT || 3000);

app.use((req, res, next) => {
  console.log('Hello, Express ::: 1');
  next()
},(req, res, next) => {
  console.log('Hello, Express ::: 2');
  next()
},(req, res, next) => {
  console.log('Hello, Express ::: 3');
  next()
})

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, '/index.html'));
});

app.use((req, res) => { 
  res.send('페이지 호출 오류')
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

 

 

위의 코드를 작성하고 npm start를 입력하고

인터넷 브라우저 주소창에 "http://localhost:3000/"를 입력하게 되면

 

아래와 같이 커맨드 창이 노출됩니다

 

express_미들웨어_적용
express 라우팅 적용 결과

 

오른쪽에 보시면 최상단에 위치한 미들웨어부터 실행되며

그다음 라우팅이 호출되어 

주소창에 http://localhost:3000/"/"이 부분이 라우팅에 감지되어 위와 같은 결과가 노출됩니다

하지만 여기서 "/top"를 입력하면 아래와 같이 노출됩니다

 

 

express_미들웨어_적용2
express 미들웨어 적용 결과

 

top라는 라우팅 호출 주소가 동일하지 않아

최하단에 위치한 미들웨어가 실행되어 페이지 호출 오류 문구가 노출됩니다

 

이 결과 라우팅이 실행되지 않으면 미들웨어가 반응한다는 것을 알 수 있습니다

 

그리고 미들웨어 또한 호출 url주소를 감지할 수 있습니다

아래와 같이 코드를 변경해보도록 하겠습니다

 

express_미들웨어_적용3
express 미들웨어 호출 url 주소제어

 

위와 같이 코드를 변경하고 실행한 다음

브라우저 주소창 "http://localhost:3000/"를 입력합니다

 

위의 결과와는 다르게 커맨드 창에 최상단 미들웨어가 실행되지 않은 현상을 확인할 수 있습니다

 

express_미들웨어_적용6
express 미들웨어 url 주소 함수 결과

 

브라우저 주소창에 "http://localhost:3000/top"를 입력하면

아래와 같이 커맨드 창이 노출되는 것을 확인할 수 있습니다

 

express_미들웨어_적용4
express 미들웨어 url 주소제어 결과

 


 

 

 

미들웨어 에러 미들웨어 적용하기

 

이번에는 라우팅 내에서 Error가 발생할 경우 

제어하는 에러 미들웨어를 만들어 보겠습니다

 

 

const express = require('express');
const path = require('path');

const app = express();
app.set('port', process.env.PORT || 3000);

app.get('/', (req, res) => {
  try {
    csole.log('Hello, Express ::: 1');
    res.sendFile(path.join(__dirname, '/index.html'));
  } catch (e) {
    console.log('Error, Express ::: 00', e)
    throw new Error(e)
  }
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

 

 

위의 코드를 실행하고 브라우저도 이전과 같은 주소를 입력합니다

그럼 아래와 같은 화면을 노출합니다

 

console.log()를 철자가 틀리게 일부러 코드 작성하였습니다

그래서 에러가 발생하였습니다

 

express에서는 기본적으로 에러가 발생하면 아래와 같이 화면을 처리합니다

하지만 아래와 같은 에러 페이지서버의 디렉터리 구조가 상세히 명세되어있습니다

그렇기 때문에 보안상 취약할 수밖에 없기 때문에 직접 처리해야 합니다

 

 

express_에러_미들웨어_적용
express 라우터 에러 발생 페이지

 

에러 미들웨어는 아래와 같이 코드를  최하단에 추가합니다

*app.listen() 위에 추가하셔야 합니다

 

코드 추가할 경우 error와 next매개변수의 위치를 꼭 지켜주셔야

에러 미들웨어를 구현할 수 있습니다

 

 

express_에러_미들웨어_적용2
에러 미들웨어 코드 적용

 

아래와 같이 에러 페이지를 노출됩니다

 

 

express_에러_미들웨어_결과
에러 미들웨어 결과 페이지

 

 


 

미들웨어 http 상태 코드 제어하기

 

아래와 같이 코드를 추가합니다

미들웨어는 next를 추가하는 것을 꼭 확인하세요 

 

 

express_미들웨어_http_상태코드제어_적용
미들웨어 http 상태 코드 제어 코드

 

위와 같이 status(404)라고 호출한다면

아래와 같이 404로 http 상태 코드가 변한 것을 확인할 수 있습니다

미들웨어를 실행하면 에러가 아니기 때문에

위의 코드를 적용하지 않는다면 200번 코드가 표시됩니다

 

express_미들웨어_http_상태코드제어
미들웨어 http 상태 코드 적용 결과

 

 

 

이것을 활용한다면 클라이언트에 모든 http 상태 코드일괄적으로 설정해서 보낼 수 있습니다

 

그렇게 일괄적으로 보내게 되면 개발하는데 어려움이 있을 수 있습니다

 

다만, 운영 단계의 서비스 경우에는

모든 에러가 404로 보내게 되면

해커들에게는 지금 어떤 에러가 발생하는지 알 수 없기 때문입니다

 

보안으로는 좋은 면이 있습니다

 

 


 

 

node에서 express는 아주 많이 사용되고 있으며,

서브 웹 서버로도 많이 사용하고 있습니다

 

SI 개발자라면 node보다는 자바를 많이 사용하며,

최근 react로 개발하는 프로젝트가 많아지면서 

node의 중요성도 조금씩 높아지고

자연스레 express의 서브 웹 서버로써 활용도가

조금씩  높아지고 있습니다

 

SI 개발자라면 어떤 언어라도 공부를 해야겠지요

그러니 흥미가 가는 언어는 아무거나 공부를 하셔도 됩니다!

 

도움 안 되는 공부는 없으니까요

 

그렇게 공부하다 보면 코드를 간결하게 되며 함수적 프로그래밍도

자연스레 몸으로 습득할 수 있을 것입니다

 

그럼 우리의 IT 대모험을 위해서

파이팅 하시길 바랍니다

 

그럼 다음 포스팅에서 뵙겠습니다

728x90
반응형
728x90
반응형

이전 글 Npm이 무엇이며 활용법에 대해서

node에서 npm을 빼고는 말을 할 수 없을 정도로 중요한 부분을 차지합니다

그 이유는 많은 사람들이 개발한 오픈 소스를 관리해 주기 때문이지요

 

npm을 잘 활용한다고 하면 패키지를 어떻게 잘 관리할지에 대해서

조금은 생각을 하고 있는 사람이 아닐까 합니다

 

더 자세한 사항을 알고 싶으시다면 아래의 링크를 클릭하시길 바랍니다 

 

 

2022.09.10 - [IT_Web/Nodejs] - Node에서 중요한 NPM은 무엇이며, 그 활용법을 알아보자

 

Node에서 중요한 NPM은 무엇이며, 그 활용법을 알아보자

이전 포스팅 puppeteer 활용해서 웹크롤링에 대해서 웹크롤링은 업무적으로도 필요한 경우가 있습니다 그리고 배우는 것만으로도 충분히 재미를 느낄 수 있다고 생각합니다 그리고 웹크롤링은 비

tantangerine.tistory.com

 


 

Nodejs 활용해서 express 서버 구축하기

 

우선 처음 프로젝트를 만들 때 무엇을 해야 할지 모를 경우가 많습니다

일단 처음 node를 설치하였다면 프로젝트를 진행할 폴더에서

npm init을 하여 간단한 물음에 답을 합니다

 

그렇게 하면 package.json 생성되고

필요한 패키지를 설치하여 프로젝트를 진행합니다

 

그럼 파일이 생성되어 아래와 같은 구조를 가지게 될 것입니다

(index.html은 제가 미리 만든 파일입니다)

 

express_파일구조
파일 구조

 

그럼 express를 간단히 구현해 보도록 하겠습니다

 

먼저 개발에 유용한 패키지 nodemon을 설치하여 진행하겠습니다 

 

npm i -D nodemon

 

nodemon은 파일을 저장할 때마다 다시 npm start를 하지 않고

소스코드를 반영할 수 있게 하는 패키지입니다

 

아래와 같이 scripts를 설정하시고 진행하시길 바랍니다

 

express_package_파일정보
package.json 파일 정보

 


 

express 패키지 사용하기

 

커맨드 창에서 아래와 같이 패키지를 설치합니다

 

npm i express

 

아래와 같이 express패키지를 불러옵니다

require()node에서 모듈을 불러올 경우 사용할 수 있습니다

 

프로젝트 내에서 모듈을 불러올 수도 있으며,

npm이 관리하는 패키지일 경우에는

패키지명을 인자 값으로 할당해서 패키지를 불러올 수 있습니다

 

아래와 같이 서버를 만들고 

app.set()를 만들어서 port번호를 설정하게 됩니다

 

*env는 추후에 다시 설명해드리겠습니다

 

const express = require('express');
const path = require('path');

const app = express();
app.set('port', process.env.PORT || 3000);

app.get('/', (req, res) => {
  console.log('req: ', req)
  console.log('res: ', res)
  res.send(`Wellcome, Express Server`);
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

 


 

express로 html 서빙하기

 

 

그럼 인터넷 브라우저를 열어 주소창에 "http://localhost:3000/" 입력합니다

 

아래와 같이 화면이 노출될 것입니다

왜 이렇게 노출될까요?

 

express_html_노출
express 노출 화면

 

아래의 app.get()는 라우터라는 명칭으로 서버에서 선언하여 사용합니다

이때 api 호출 방식은 get방식, post방식이 있습니다

 

*get방식 post방식의 차이는 추 후에 다시 설명해 드리겠습니다

 

그 라우터는 첫 번째 인자 값에 api 호출받을 주소를 지정하여

get방식을 호출하는 api 주소가 동일할 경우  두 번째 인자 값인 콜백 함수가 실행됩니다 

 

그 콜백 함수에서 (req, res) 두 개의 매개변수가 보일 것입니다

 

이 두 개의 매개변수의 정보들을 보면 화면이 왜 그렇게 노출되는지에 대한

궁금증을 풀 수 있을 것 같습니다 

 

아래의 코드에 제가 console.log를 호출해 놓았습니다

그럼 콘솔 창에 어떤 정보가 있는지 확인해 봅시다

 

 

express_get_변경
express 콘솔 찍어보기

 

 

아래는 req에 대한 정보가 json Object형으로  담겨있습니다

req정보는 클라이언트에서 요청할 때 보낸 정보들이 담겨있는 것을 알 수 있습니다

아래에 statusCode, url, params, query, route 등등 유용한 정보들이 있습니다

 

express_req_정보
express req 정보

 

 

그럼 실제 클라이언트에서는 어떻게 보내고 있는지 한번 확인해볼까요?

F12키를 눌러서 개발자 모드에서 api요청 보낸 정보들이 헤더에 담겨있는 것을 확인할 수 있습니다

 

 

express_클라이언트_요청
express 클라이언트 api요청 상세정보

 

그리고 두 번째 매개변수 res의 정보를 보겠습니다

res는 express에서 자체적으로 사용하는 패키지와 모듈이 있습니다

 

 

당연 req에서 받은 정보들도 res에서도 사용할 수 있도록 저장되어있으며

html을 기본 코드들이 내장되어있어

res.send()에서 매개변수 인자 값으로 문자열을 할당할 경우

그 인자 값의 정보들이 내장된 html 코드와 같이 페이지를 노출하게 됩니다

 

결과 값은 아래와 같습니다

 

express_html_자체서빙하기
express 자체 내장 html 서빙 화면

 

이 것을 이용해서 html 파일을 미리 만들어서 사용할 수 있습니다

위에 제가 미리 만들어 놓은 html은 확인하셨을 것입니다

 

 index.html을 아래와 같이 코드 작업을 합니다

 

 

<html>
<head>
  <meta charset="UTF-8" />
  <title>Express Server</title>
</head>
<body>
  <h1>Express Server</h1>
  <p>환영합니다.</p>
</body>
</html>

 

그리고 아래처럼 res.sendFile()를 활용해서 경로를 적용해서 매서드를 호출합니다

path node의 패키지로 프로젝트의 경로를 찾아 할당하기 유용한 패키지입니다

 

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, '/index.html')); // 변경코드
});

 

코드를 변경했다면 아래와 같이 변경된 화면을 확인할 수 있습니다

 

 

express_html서빙하기
HTML 파일 읽기로 HTML 서빙결과

 

 


 

 

express 서버 구현하는 것을 알아보았습니다

아직 미들웨어, DB 연동, 세분화된 라우터, 세션, 쿠키 활용 등

아직 갈길이 멀기만 합니다

 

블로그에 포스팅을 업로드할 때마다

콘텐츠를 걱정하는 블로거들이 많다고 합니다

하지만 저는 그런 걱정은 별로 안 하는 것 같습니다

그저 제가 공부한 것들이나 알고 있는 것들을 풀기만 하면 되니깐요

 

그래서 요즘은 블로그에 글을 쓰려고 공부하는지 공부하려고 글을 쓰는지

알 수 없을 정도가 되었습니다

 

하지만 그것 또한 저 한태는 좋은 영향을 주는 것 같습니다

동기부여와 더 진취적으로 변해가는 저의 모습을 보니깐요

 

1일 1 포스팅이 왜 어려운지 요즘 많이 생각하게 됩니다

 

앞으로도 꾸준할 수 있도록 노력하겠습니다

그러니 다 같이 파이팅 하시길 바랍니다

 

우리의 IT 대모험은 아직 끝나지 않았으니까요!

 

그럼 다음 글에서 뵙겠습니다

728x90
반응형
728x90
반응형

이전 포스팅 puppeteer 활용해서 웹크롤링에 대해서

웹크롤링은 업무적으로도 필요한 경우가 있습니다

그리고 배우는 것만으로도 충분히 재미를 느낄 수 있다고 생각합니다

그리고 웹크롤링은 비동기식으로 해결해야 해서 Promise를 자주 쓰며 async, await도 자주 활용합니다

그래서 이 개념을 잡는 공부도 가능하니 꼭 한번 공부해보세요

 

더 자세한 사항이 궁금하다면 아래의 링크를 클릭하시면 됩니다

 

 

2022.08.20 - [IT_Web/Nodejs] - nodejs 웹 크롤링을 Puppeteer 및 Promise 활용 완벽 파헤치고 CSV 파일 쓰기

 

nodejs 웹 크롤링을 Puppeteer 및 Promise 활용 완벽 파헤치고 CSV파일 쓰기

이전 포스팅에 대하여 이전 포스팅에서는 간단하게 웹 크롤링을 하는 방법을 알아보았습니다 axios와 cheerio를 사용해서 웹 크롤링을 해보았습니다 하지만 axios는 한계가 있습니다 싱글 페이지로

tantangerine.tistory.com

 

 

npm이란?

Node Package Manager의 약자로 여러 사람들이 만든 모듈을 모아둔 저장소입니다

npm에 저장된 모듈을 프로젝트 내에서 설치를 하여 간편하게 구현이 가능합니다

이러한 모듈들이 오픈 소스 생태계를 만들고 있습니다

이 모듈은 노드로 구성되어 있으며,

그것을 패키지라 명합니다.

 

 

npm 활용 패키지 설치하기

 

npm i [패키지] / npm i -g [패키지]  / npm i -D [패키지]

위 3가지 설치 방법이 있습니다

각기 다른 방법은 저장 위치가 다르며 그 사용법도 조금씩 다르게 구성됩니다

 

첫 번째 방법으로 설치하면 dependencies 저장되어 json 형태로 패키 지명이 입력됩니다

그래서 어떤 패키지가 설치되었는지 확인이 가능합니다

 

-D: 설치된 패키지는 개발에서만 사용될 수 있도록 devDependencies에 분리되어 관리가 됩니다

 

-g: 글로벌 설치를 하면 dependencies와 devDependencies에서 설치된 이력을 찾을 수 없고

사용할 경우에도 npm을 붙여서 사용하기보다는 패키지명만 커맨드 창에 입력하면 사용할 수 있습니다

하지만 패키지를 커맨드 창에서 입력해 사용하는 일은 많이 없습니다

그래서 설치 이력에도 안 남아서 관리인원이 변경되면 유지 보수하기에 많은 어려움이 있기 때문에

최근에는 많이 사용하지 않는 추세입니다 

 

package_json파일
package.json 파일

 

그리고 npm init을 하고 설치를 하면 아래와 같이 파일 구조가 형성됩니다

node_modules는 패키지가 설치되면 그 관련 모듈들이 설치되어

그 파일들의 패키지명이 나열되어있는 것을 확인할 수 있습니다

 

또한

package-lock.json이라는 파일을 확인할 수 있습니다

그 파일은 패키지의 버전들이 고정되어 관리하는 파일

 

package_lock_json파일
package-lock.json파일 형성 구조

 

 

npm에서 패키지 버전 관리 SemVer(유의적 버저닝) 방식에 대하여

 

Major(주 버전), Minor(부 버전), Patch(수 버전)의 형태로 버전이 관리된다

아래와 같이 버전이 관리되어있다면

 

express : "^4.18.1" 

 

Major 버전인 4에 해당하는 위치의 수는

어떠한 수정으로 하위 버전과 호환이 안될 경우 버전이 올라갑니다

 

Minor 버전인 18에 해당하는 위치의 수는

어떠한 수정으로 하위 버전과 호환이 되는 경우 버전이 올라갑니다

 

Patch 버전인 1에 해당하는 위치의 수는

기능에 버그를 해결했을 때 올라갑니다

 

 

 

버전 기호 사용법

 

이쯤 되면 "^" 이러한 기호가 궁금증을 유발합니다

 

^4.18.1: 패키지 업데이트 시 Minor 버전까지만 업데이트됩니다

즉, Major는 고정되고 그 이하 버전만 업데이트됩니다.

 

~4.18.1: 패키지 업데이트 시 Patch 버전까지만 업데이트됩니다.

즉, Minor이상은 고정되고 그 이하 버전만 업데이트됩니다.

 

그리고 <=, >=, <, >는 이상, 이하, 초과, 미만으로 사용할 수 있습니다

하지만 사용하지 않습니다

 

알파/베타/RC 버전이 존재할 수도 있습니다

(1.1.1-alpha.0, 2.0.0-beta.1, 2.0.0-rc.0)

 

설치할 때 버전을 지정해서 설치할 수 있습니다

npm i express@3.6.2

 

@latest는 최신을 의미합니다

그래서 아래와 같이 사용할 수 있습니다

npm i express@latest

 

 

npm 명령어 알아보기

 

npm outdated를 커맨드 창에 입력합니다

 

아래와 같이

현재 버전과 현재 상용 버전 그리고 최신 버전이 함께 노출됩니다

그럼 버전을 확인한 다음에

package.json에 패키지의 버전을 변경하여

npm update를 하게 되면 버전이 반영됩니다

 

npm_outdated
npm outdated 결과

 

버전 관리 방법은 이외에도 npm-check를 사용하면 조금 더 편하게 버전을 관리할 수 있습니다

npm i -g npm-check으로 패키지를 설치합니다

 

npm-check를 입력하면 

아래의 화면처럼 노출이 됩니다

MAJOR UP / NOTUSED? / NEW_VER! / MINOR UP / PKG ERR 등으로

패키지 버전 상태를 체크할 수 있습니다

 

 

npm_check
npm-check 결과 화면

 

그리고 버전을 간편하게 업데이트하는 방법도 있습니다

npm-check -u를 입력하게 되면 아래와 같은 화면이 노출되면서 버전을 관리할 수 있습니다

스페이스 바를 누르면 (*) 패키지가 선택되며 최종적으로 엔터키를 누르면

아래의 상용 버전으로 업데이트가 됩나다

 

npm_check_u
npm-check -u 입력 화면

 

 

 

아래의 버전을 업데이트하는 모습입니다

그리고 업데이트 결과를 보여주며 업데이트가 끝이 나게 됩니다

 

npm_check_installing
npm-check 설치중 화면

 

npm_check_업데이트결과화면
npm-check 업데이트 결과 화면

 

npm uninstall [패키 지명]: 패키지를 삭제하는 명령어입니다

 

npm search 검색어: npm 패키지에서 검색할 수 있는 사항들을 검색할 수 있습니다

 

npm_search_화면
npm search expo 입력 화면

 

npm info [패키 지명]으로 패키지의 정보를 확인할 수 있습니다

패키지를 만든 개발자 메일현재 상용 배포된 버전 상태

현재 패키지와 연관되어 있는 패키지 정보도 알 수 있습니다

키워드로 expo가 보입니다 npm search [keyword]로 검색 정보를 위와 같이 얻을 수 있습니다

 

npm_info_화면노출
npm info expo 입력시 화면노출

 

npm ls는 프로젝트 내에

어떤 패키지를 사용하고 있는지 트리구조로 볼 수 있습니다

 

아래와 같이 노출됩니다

 

npm_ls_화면노출
npm ls 화면 노출

 

이렇게 npm에 대해서 알아보았습니다

프로젝트를 진행하면서 npm에 대해서 제대로 모르고 사용한 것은 아닌가 하고 생각하게 되었습니다

이번 기회에 조금 더 깊이 알아보았으니 더 활용해보아야겠네요 

 

그럼 오늘 추석인데도 불구하고 블로그를 쓰는 저에게 칭찬을 주며

IT 대모험은 끝나지 않음을 확인하였습니다

 

그럼 모두들 즐거운 추석 되시고 

다음 글에서 뵙겠습니다

 

 

728x90
반응형
728x90
반응형

이전 글에서 엑셀 데이터를 읽어와 DB에 저장하기

 

때로는 현업과 아님 타 부서에서 엑셀 데이터를 어드민 페이지에서 볼 수 있게 해달라고 한다면,

그리고 엑셀 파일에 수백 가지의 데이터가 있다면,

어떻게 하겠습니까? 

수 작업으로 데이터에 저장하기는 어려운 일입니다

 

그래서 엑셀을 업로드해서 자바에서 읽을 수 있는 방법을 모색해보았습니다

 

자세한 사항이 알고 싶으시면 아래의 링크를 클릭하시면 됩니다

 

 

2022.08.05 - [IT_Web/Java] - Java 엑셀 데이터 읽어오기 및 다중 insert 한방에 하기

 

Java 엑셀 데이터 읽어오기 및 다중 insert 한방에 하기

오늘은 엑셀 데이터를 업로드해서 자바에서 데이터를 받아서 insert를 하든 아니면 다시 그 데이터를 다시 조합해서 웹에 다시 전달하는 등 핸들링하는 방법을 알아보겠습니다 그리고 클래스명

tantangerine.tistory.com

 

메일 보내기 앞서 Velocity란? 무엇인가?

메일을 보내기 위해서는 HTML 태그가 필요하며

head태그와 body에 각종 태그를 삽입하여 템플릿을 만들어서 

일정한 코드를 삽입해서 로고나 이미지를 보여줄 수 있습니다

하지만 태그만 필요한 것이 아닌 DB에서 관리하는 데이터들도

같이 범용성 있게 노출시켜야 합니다

그러기 위해서 vm파일에 데이터를 같이 합성하여 사용하기 위해

velocity를 사용합니다

 

 

velocity 설정 및 추가하기

Velocity를 활용하기 위해 pom.xml에 추가합니다

 

<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity</artifactId>
    <version>1.7</version>
    <scope>system</scope>
    <systemPath>${webapp.lib}/velocity-1.7.jar</systemPath>
</dependency>

 

 

bean과 필요한 값을 설정합니다

로그인 및 비밀번호와 패스워드를 설정합니다

그리고 메일을 보낼 서비스단도 추가합니다

 

 

<!-- smtp 메일전송 -->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> 
    <property name="host" value="mail.######.net"/> 
    <property name="port" value="25" /> 
    <property name="username" value="xxxxxx@naver.net"/> 
    <property name="password" value="#####"/> 
    <property name="javaMailProperties"> 
        <props> 
          <prop key="mail.smtp.auth">true</prop>
          <prop key="mail.debug">true</prop>
          <prop key="mail.smtp.auth.mechanisms">LOGIN</prop>
        </props> 
    </property> 
</bean>

<bean id="mailer" class="kr.co.xxxxxx.common.mail.Mailer">
    <property name="velocityEngine" ref="velocityEngine" /><!--velocityEngine bean참조 -->
</bean>

<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
    <property name="resourceLoaderPath" value="WEB-INF/mailtemplate/"/>
</bean>

 

 

위의 코드에 "WEB-INF/mailtemplate/"를 확인할 수 있습니다

그 저장소에는 mailtemplate.vm이 지정되어있습니다

아래와 같이 설정되어 메일 형식을 지정하여 보낼 수 있습니다

 

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title></title>
</head>
<body>
  <!-- header -->
  <table width="600" border="0" align="center" cellpadding="0" cellspacing="0" style="text-align:left;font-size:16px;font-family:Malgun Gothic, '맑은고딕', Dotum, '돋움', Tahoma, Helvetica Neue, Helvetica, sans-serif;background-color:#fff;margin:0 auto">
    <tbody>
      <tr><td height="100">&nbsp;</td></tr>
      <tr>
        <td>
          <table width="600" border="0" align="center" cellpadding="0" cellspacing="0">
          <tbody>
          <tr>
            <td width="139" align="left"><a href="#"><img src="images/logo.png" alt="autobell" border="0"/></a></td>
            <td width="133">&nbsp;</td>
          </tr>
          </tbody>
          </table>
        </td>
      </tr>
    </tbody>
  </table>
  <!-- //header -->

  <!-- content -->
  <table width="600" border="0" align="center" cellpadding="0" cellspacing="0" style="text-align:left;font-size:16px;font-family:Malgun Gothic, '맑은고딕', Dotum, '돋움', Tahoma, Helvetica Neue, Helvetica, sans-serif;background-color:#fff;margin:0 auto">
    <tbody>
      <tr><td height="70">&nbsp;</td></tr>  
      <tr>
        <td>
          <table width="600" border="0" align="left" cellpadding="0" cellspacing="0" style="text-align:left;font-size:16px;font-family:Malgun Gothic, '맑은고딕', Dotum, '돋움', Tahoma, Helvetica Neue, Helvetica, sans-serif;background-color:#fff">
            <tbody>
            <!-- title -->
            <!-- //title -->

            <!-- text type1 -->
            <tr><td height="50">&nbsp;</td></tr>
            <tr>
              <td style="color:#222;font-size:18px;line-height:1.6">
                '${NAME}'님
            </td>
            </tr>
            <!-- //text type1 -->

            <!-- text type1 -->
            <tr><td height="50">&nbsp;</td></tr>
            <tr>
              <td style="color:#222;font-size:18px;line-height:1.6">
                번호 : '${NO}'<br />
                성명 : '${NAME}' <br />
                도착예정시간 : '${TIME}'<br />
                연락처: '${PHONE}'
              </td>
            </tr>
            <!-- //text type1 -->

            <!-- design line -->
            <tr><td height="56">&nbsp;</td></tr>
            <tr>
              <td>
                <table width="600" border="0" align="left" cellpadding="0" cellspacing="0">
                  <tbody>
                    <tr>
                      <td height="1" width="72" style="border-top:1px solid #d8d8d8">&nbsp;</td>
                      <td height="1">&nbsp;</td>
                    </tr>
                  </tbody>
                </table>
              </td>
            </tr>
            <!-- //design line -->

            <!-- text type2 -->
            <tr><td height="40">&nbsp;</td></tr>
            <tr>
              <td style="color:#222;font-size:14px;line-height:1.6">
            </td>
            </tr>
            <!-- //text type2 -->

            <!-- link -->
            <tr><td height="25">&nbsp;</td></tr>
            <tr>
              <td>
              </td>
            </tr>
            <!-- //link -->
            </tbody>
          </table>
        </td>
      </tr>
      <tr><td height="56">&nbsp;</td></tr>
    </tbody>
  </table>
  <!-- //content -->

  <!-- footer -->
  <table width="600" border="0" align="center" cellpadding="0" cellspacing="0" style="text-align:left;font-size:16px;font-family:Malgun Gothic, '맑은고딕', Dotum, '돋움', Tahoma, Helvetica Neue, Helvetica, sans-serif;background-color:#fff;margin:0 auto">
    <tbody>
    <tr>
      <td>
        <table width="600" border="0" align="center" cellpadding="0" cellspacing="0" style="text-align:left;font-size:16px;font-family:Malgun Gothic, '맑은고딕', Dotum, '돋움', Tahoma, Helvetica Neue, Helvetica, sans-serif;background-color:#fff;border-top:1px solid #aaa">
          <tbody>
            <tr><td height="30">&nbsp;</td></tr>
            <tr>
              <td style="font-size:14px;color:#777;line-height:1.5"><br />
            </tr>
            <tr><td height="27">&nbsp;</td></tr> 
            <tr>
              <td style="font-size:12px;color:#777;line-height:1.5">본 메일은 발신전용 메일로 회신이 되지 않습니다.<br />
            </tr>
          </tbody>
        </table>
      </td>
    </tr>
    <tr><td height="100">&nbsp;</td></tr>
    </tbody>
  </table>
  <!-- //footer -->
  
</body>
</html>

 

 

그럼 Velocity 설정 및 추가는 끝이 났습니다

이제는 데이터를 저장할 VO를 보며 어떤 데이터를 받을지 지정해봅시다

 

VO가 정의되어있습니다

템플릿을 미리 만들어놓고 적용한다면 true를 할당하여 메일링 값에 적용합니다

그리고 수신자, 발신자, 제목, 내용도 함께 할당되어 

서비스단의 메서드를 호출할 것입니다

 

 

 

public class MailVO {
    private boolean isTemplate = true;  // 템플릿적용 여부
    private String from = "";           // 발신자
    private String to = "";             // 수신자
    private String bcc = "";            // 숨은 참조자
    private String subject = "";        // 제목
    private String contents = "";       // 내용
    private String templateName = "";   // 메일템플릿명
    private String logoImageUrl = "";   // 로고이미지 경로
    private String successYn = "";
    private Map<String, Object> contentsMap; // Mapping Contents
    
    
    ....
}

 

 

아래와 같이 서비스단을 호출하게 됩니다 

로고, 제목, 내용을 정의하여 mailer.sendMail() 호출합니다

 

 

@Override
public void sendMailTemplate(MailVO mailVO) throws Exception {
    try {
        mailVO.setTemplateName(StringUtils.isBlank(mailVO.getTemplateName()) 
                                ? "commonTemplate.vm" 
                                : mailVO.getTemplateName());

        Map<String, Object> contentsMap = new HashMap<String, Object>();
        contentsMap.put("isTemplate", mailVO.isTemplate());
        contentsMap.put("subject", mailVO.getSubject());
        contentsMap.put("logoImageUrl", StringUtils.isBlank(mailVO.getLogoImageUrl()) 
                                        ? "/images/common/h1-logo.svg" 
                                        : mailVO.getLogoImageUrl());
        contentsMap.put("contents", mailVO.getContents());
        if(mailVO.getContentsMap() != null) {
            contentsMap.putAll(mailVO.getContentsMap());
        }
        
        mailVO.setContentsMap(contentsMap);
        mailer.sendMail(mailVO);
    } catch (Exception e) {
        logger.error("[ R E S ]/{} {}", "", StackTraceUtil.getStackTraceString(e));
    }
}

 

 

그리고 mailSender 객체로 MimeMessage의 객체를 만들어서 merge 작업하여

msg.setText()에 값을 정의하여

mailsender.send()로 메일을 발송합니다

 

그렇게 되면 메일 발송은 완료됩니다

 

 

@Autowired
private VelocityEngine velocityEngine;

@Autowired
private JavaMailSender mailSender;

@Autowired
private CommonLogService commonLogService;

public void setVelocityEngine(VelocityEngine velocityEngine) {
    this.velocityEngine = velocityEngine;
}

public void sendMail(MailVO mailVO) {
    MimeMessage msg = mailSender.createMimeMessage();
    mailVO.setSuccessYn("N");
    try {

        String veloTemplate = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, mailVO.getTemplateName(), "UTF-8", mailVO.getContentsMap());
        msg.setText(veloTemplate, "UTF-8", "html");
        if(!mailVO.getBcc().equals("")){
            msg.setRecipients(Message.RecipientType.BCC, mailVO.getBcc());
        }

        mailSender.send(msg);
        mailVO.setSuccessYn("Y");
    } catch (ParseErrorException | MethodInvocationException  e) {
        logger.error("[ R E S ]/{}{}", StackTraceUtil.getStackTraceString(e));
        return;
    } catch (ResourceNotFoundException e) {
        logger.error("[ R E S ]/{}{}", StackTraceUtil.getStackTraceString(e));
        return;
    } catch (MessagingException e) {
        logger.error("[ R E S ]/{}{}", StackTraceUtil.getStackTraceString(e));
        return;
    } catch (MailException e) {
        logger.error("[ R E S ]/{}{}", StackTraceUtil.getStackTraceString(e));
        return;
    } catch (Exception e) {
        logger.error("[ R E S ]/{}{}", StackTraceUtil.getStackTraceString(e));
        return;
    }

    try {
        commonLogService.insertMailLog(mailVO);
    } catch(Exception ex) {
        ex.printStackTrace();
    }

}

 

 

 

 

오늘은 자바를 활용해서 메일을 발송하는 방법을 알아보았습니다

node와 Java의 방식은 정말 다르기에

두 개 중 무엇이 좋다고 할 수 없지만

 

프로젝트 환경과 규모에 따라 다르게 대응하겠지요

그러니 많은 언어를 알고 있다는 것은 큰 장점일 수 있습니다

하지만 구글링만으로도 구현할 수 있다면

괜찮습니다 우선 빨리 구현하고 나서

난중에 다시 복습하면서 왜 이렇게 되는지 알아보는 게 핵심입니다

 

프로젝트 진행할 때는 결과를 빨리 보여주고

개별적으로 다시 코드 리뷰를 하면서 리팩터링과 모듈화 등을 하면서

자기 실력을 쌓아가는 것이 좋을 것 같습니다

 

그럼 다음 글도 기대해주시고

우리들의 IT 대모험은 끝나지 않았으니 힘내시길 바랍니다

 

 

 

 

728x90
반응형
728x90
반응형

이전 글에서는 웹크롤링에서 간단하게 DB 연동하기에 대해서

 

DB 연동으로 웹크롤링을 보다 효율적으로 사용하는 방법을 알아보았습니다

DB 연동으로 정보를 저장해서 데이터 관리는 물론 중복된 데이터까지

관리가 가능하니 웹 크롤링을 보다 능률적으로 실행할 수 있을 것입니다

 

더 자세히 알고 싶으시다면 아래의 링크를 클릭해주세요

 

 

2022.09.03 - [IT_Web/Nodejs] - 웹 크롤링 puppeteer로 mysql 및 sequelize 활용하여 DB 연동하기

 

웹 크롤링 puppeteer로 mysql 및 sequelize 활용하여 DB연동하기

이전 글에서 간략하게 프록시가 무엇이며 프록시를 활용해서 IP를 변경하는 방법을 알아보았습니다 더 자세한 정보를 알고 싶으시다면 아래의 링크를 클릭하시길 바랍니다. 2022.09.02 - [IT_Web/Nodej

tantangerine.tistory.com

 

 

로그인 유지 및 파일 업로드 알아보기

 

 

오늘은 puppeteer를 활용해서 파일 업로드

로그인 정보를 유지하는 방법을 알아보려고 합니다

 

 

 

그럼 우선 전체 코드를 보시겠습니다

아래의 코드를 보시면 진행이 많이 되었습니다

이전 포스팅을 하나씩 참고하시면 완성하실 수 있으니 관심 있으시다면 확인해보세요

 

 

 

const puppeteer = require('puppeteer');
const writekeyboardList = require('./writekeyboardList');
const callElement = require('./mouseHandleHtml');
const dotenv = require('dotenv'); // npm i dotenv
const db = require('./models');
const fs = require('fs')

dotenv.config();

const setCrawler = async (production, isMouse = false) => {
  await db.sequelize.sync();
  let browser = await puppeteer.launch({
    headless: false,
    args:['--window-size=1920,1080'],
    userDataDir: 'C:\Users\kimsangjin\AppData\Local\Google\Chrome\User Data'
  });
  let page = await browser.newPage();
  await page.setViewport({
    width: 1080,
    height:1080,
  })

  if (!production && isMouse) {
    await callElement(page)
  }

  await page.on('dialog', async (dialog) => { 
    console.log(`dialog`, dialog) 
    console.log(`type: ${dialog.type()} / message: ${dialog.message()}`) 
    if(dialog.type() === 'prompt') {
      await dialog.accept('https://www.tistory.com/')
    }
    if(dialog.type() === 'alert'){
      await dialog.dismiss(); 
    }
    if(dialog.type() === 'confirm'){
      await dialog.accept();
    }
  })

  const setNewProxy = async(crawlerUrl) => {
    const newProxies = await page.evaluate(async() => {
      const ips = Array.from(document.querySelectorAll('tr > td:first-of-type > .spy14')).map((v) => v.innerText)
      const types = Array.from(document.querySelectorAll('tr > td:nth-of-type(2)')).map((v) => v.innerText)
      const anonymity = Array.from(document.querySelectorAll('tr > td:nth-of-type(3)')).map((v) => v.innerText)
      const latencies = Array.from(document.querySelectorAll('tr > td:nth-of-type(6) > .spy1')).map((v) => v.innerText)
      return ips.map((v, i) => {
        return {
          ip: v,
          type: types[i],
          latency: latencies[i],
          anonymity: anonymity[i].toString()
        }
      });
    });
    const fristProxies =  newProxies.filter((v) => v.type.startsWith('HTTP')).sort((p, c) => p.latency - c.latency)
    console.log('fristProxies', fristProxies)
    await Promise.all(fristProxies.map(async (v) => {
      return await db.Proxy.create({
        ip: v.ip,
        type: v.type,
        latency: v.latency,
        anonymity: anonymity.toString()
      });
    }))
    await page.close();
    await browser.close();
    browser = await puppeteer.launch({
      headless: false,
      args:['--window-size=1920,1080', '--disable-notifications', `--proxy-server=${fristProxies[0].ip}`]
    });
    page = await browser.newPage();
    await page.goto(crawlerUrl);
    await page.waitForSelector('.tf_keyword')
    await inputValue('.tf_keyword', '내아이피'),
    await Promise.all([
      buttonClick(['.ico_pctop.btn_search', 'ico_ksearch.btn_ksearch']),
      page.waitForNavigation(),
    ]);
    await setTime(4000)
    await page.close();
    await browser.close();
    await db.sequelize.close()
  }

  const handleWriteKeyboard = async (selector, id) => {
    const writeList = writekeyboardList()
    const idOfList = [...id]
    if(selector){
      await page.click(selector);
      console.log(`idOfList`, idOfList)
      for (const id of idOfList) {
        const inputValue = await writeList.filter(element => element.ShiftLeft === id || element.key === id )
        if (inputValue.length > 0) {
          const code = inputValue[0].code
          const ShiftLeft = inputValue[0].ShiftLeft === id ? 'ShiftLeft' : ''
          if (ShiftLeft) {
            await setTime(selector, 0, 'key')
            await page.keyboard.down(ShiftLeft);
            await setTime(selector, 0, 'key')
            await page.keyboard.press(code);
            await setTime(selector,0, 'key')
            await page.keyboard.up(ShiftLeft);
          } else {
            await page.keyboard.press(code);
          }
          await setTime(selector,0, 'key')
        }
      }
    } else {
      await page.keyboard.press(id);
    }
  }

  const getSelectEl = async(selector) => {
    const selectedEl = await page.evaluate((selector) => {
      console.log('selector', selector)
      const temEl = document.querySelector(selector)
      console.log('selector', temEl)
      return temEl
    }, selector)
        console.log('selector', selectedEl)
    return selectedEl
  }

  const fileUpload = async() => {
    const pathFile = 'C:\Users\kimsangjin\Desktop'
    const fileExitst = await fs.existsSync(`${pathFile}/travis_1.png`)
    if(fileExitst){
      const input = await page.$("#m_attach_file");
      await input.uploadFile(`${pathFile}/freelanceDeveloper.png`)
      return true
    }
    return false
  }

  const inputValue = async (selector, value) => {
    await page.evaluate((selector, value) => {
      document.querySelector(selector).value = value
    }, selector, value)
    await setTime(selector)
  }

  const buttonClick = async (selector, nextSelector = '', type = '') => {
    await page.evaluate((selector) => {
      if (document.querySelector(selector[0])) {
        document.querySelector(selector[0]).click();
      } else {
        document.querySelector(selector[1]).click();
      }
    }, selector)
    if(type === 'login'){
      // waitForRequest(요청 대기), waitForResponse(응답 대기)
      const res = await page.waitForResponse(res => {
        return res.url().includes('graphql')
      })
      const result = await res.json()
      console.log('login', result);
      if(await result.extensions.is_final){
        await setTime(type, 2000)
        await page.keyboard.press('Escape')
      }
    } else if(type === 'logOut'){
      await page.waitForNavigation()
    } else {
      if(nextSelector){
        await page.waitForSelector(nextSelector)
      }
      if(type === 'wait') await setTime(selector)
    }
  }

  const setTime = async (selector = 'normal', waitTime = 0, type= '') => {
    const setTime = await page.evaluate(async(waitTime, type) => {
      let timeList =[]
      if(type === 'key'){
         timeList = await [569, 311, 257, 175, 306, 123, 428, 497, 298, 211, 379, 108, 512, 139, 249];
      } else {
        timeList = await [3975, 4547, 5231, 6397, 7894, 3394, 4206, 5147, 6932, 7430, 3561, 4896, 5877, 6407, 7133];
      }
      let randomTime = await timeList[Math.floor(Math.random() * timeList.length)]
      if (waitTime > 0) randomTime = waitTime
      await new Promise(r => setTimeout(r, randomTime))
      return randomTime
    }, waitTime, type)
    console.log(`waitTime[${selector}]: `, setTime)
    return setTime
  } 


  return { fileUpload, getSelectEl, buttonClick, inputValue, handleWriteKeyboard, setTime, setNewProxy, page, browser }
}


const crawler = async () => {
  try {
    const { getSelectEl, buttonClick, inputValue, handleWriteKeyboard, setTime, setNewProxy, page, browser } = await setCrawler(JSON.parse(process.env.PRODUCTION));
    const id = await process.env.EMAIL;
    const password = await process.env.PASSWORD;
    await page.goto('https://office.hiworks.com/polarium.co.kr/home#loginUrl=mail/webmail/m_list/b0');
    await setTime()
    const isLogin = await getSelectEl('.menu.office')
    if(isLogin){
      console.log('이미 로그인이 되어있습니다.')
      await buttonClick(['.menu.office'], '.main-btn');
    } else {
      await page.waitForSelector('#office_id');
      await setTime()
      await handleWriteKeyboard('#office_id', id)
      await setTime()
      await handleWriteKeyboard('#office_passwd', password)
      await setTime()
      await buttonClick(['.int_jogin'], '.main-btn');
    }
      await page.waitForSelector('.main-btn')
      await buttonClick(['.main-btn'], '#to_addr');
      await page.waitForSelector('#to_addr')
      await inputValue('#to_addr', '김상진');
      await setTime('', 500)
      await page.keyboard.press('Enter');
      await page.waitForSelector('#m_attach_file')
      await fileUpload()
      await setTime('', 500)
      await buttonClick(['.detail_select > a']);
  } catch (e) {
    console.log(e);
  }
}

crawler()

 

로그인 유지 알아보기

먼저 로그인 유지 방법을 알아보도록 하겠습니다

 

 

아래의 userDateDir를 추가하면 간단하게 로그인 유지가 됩니다

하지만 크롬만 가능하니 크롬을 설치하고 아래의 경로대로 설정하시면 간단히 됩니다

 

puppeteer-로그인유지
로그인 유지 코드

 

아래처럼 로그인 유지가 된다면

로그인에만 있는 태그를 선택해서 있으면 로그인이 되었다는 의미로

분기를 하여 코드 작성할 수 있습니다

 

 

 

puppeteer-로그인분기
로그인 코드 분기

 

 

 

 

puppeteer 활용하여 파일 업로드 하기

 

이제는 어느 정도 하실 수 있다고 가정하겠습니다

그래서 여러 함수를 세팅해서 사용하고 있으니 참고하시길 바랍니다

 

로그인 후

fileUpload함수를 아래와 같이 실행합니다

 

 

puppeteer-파일업로드함수호출
파일업로드 실행

 

 

우선 파일 위치가 필요합니다

그 위치에 현재 올리려는 파일이 있는지 존재 여부를 확인하여

있을 때만 파일 업로드를 실행합니다

 

이때 이름은 db에 저장된 파일을 올리도록 하는 것이 좋을 듯합니다

다음에 기회 되면 적용하도록 하고

지금은 특정 파일만 올리도록 하겠습니다

 

 

puppeteer-파일업로드선언문
파일업로드 선언문

 

 

 

 

아래의 이미지를 보면 파일 첨부라는 글이 보이지만

그 밑에 input type='file'로 정의된 것을 확인할 수 있습니다

그것을 위에 처럼 input을 가져와서 .uploadFile()와 경로를 지정해서 호출하면

 

파일이 업로드되는 것을 확인할 수 있습니다

이때 꼭 page.$()활용해서 해야 한다는 점 확인하세요

 

 

 

태그 현재 상황
태그 현재 상황입니다

 

 

 

이렇게 로그인 유지와 파일 업로드를 알아보았습니다

node.js에서 메일 보낼 수 있는 라이브러리가 존재합니다

그러니 그것도 활용하는 것이 좋을 듯합니다

크롤링보다는 라이브러리를 사용하는 것이 간편하겠지요

 

그럼 오늘도 한걸음 나아가셨길 바라며

IT대모험을 즐기며 다음 포스팅에서 찾아뵙겠습니다

그럼 파이팅 하세요!

728x90
반응형
728x90
반응형

* SVN 설치 및 사용방법

1. 아마존에 있는 visual SVN repository에 폴더를 만들고 아이디 비밀번호 등록하고 폴더에 접근할 권한 설정

 

2. 이클립스나 STShelp> eclips marketplace 들어가서 svn으로 검색 subclipse 설치

    Window> show view에서에서 SVNrepositories

    SVNrepositories에서 우클릭 New > RepositoryLocation


3. 창이 뜨면 URL에 서버 저장소입력

권한 확인을 위해서 아이디 비밀번호 입력하는 창이 노출됩니다

 

1번에서 만든 아이디 비밀번호를 입력해하면 레파지토리에 폴더가 생성됩니다

(에러가 발생하면 권한 설정에 문제이니 VisualSVN에서 아이디를 만들거나 권한 설정을 확인해주세요.)

 

4. 프로젝트를 svn에 올려보겠습니다.

올릴 프로젝트를 우클릭한 후 Team > share project

Commit을 누르고 창이 뜨면 comment는 수정내용을 기입하면 됩니다! Ex) project setting,

(성공하면 올린 사람(마지막 수정자) 아이디가 파일 옆에 뜨고, SVN repositories에도

파일이 올라온 것을 확인할 수 있습니다.)

 

5. 이제 올린 파일을 내려받아 보겠습니다

SVN 레파지토리에서 받을 파일 우클릭 > checkout 하면 폴더가 통째로 local

받아집니다. 올린 파일이 안 보일 경우 레파지토리 우클릭 > refresh 하면 보일 거예요

 

6. 새로운 파일을 만들고 수정을 한 후 commit을 해볼게요

Local 프로젝트 안에 새로운 폴더를 만들고 local 프로젝트 > 우클릭 > team > synchronize with repository

SVN 안에 있는 파일이랑 local에 있는 파일과의 싱크를 볼수 있습니다


하나의 폴더와 파일이 생성됐어요.
4번에서 했던 것처럼 우클릭해서commit 해주시면 됩니다!

 

7. 수정한 프로젝트를 update 해보겠습니다

local 프로젝트 > 우클릭 > team > synchronize with repository

우클릭 > update 하면 수정된 부분이 local에 적용이 됩니다!

 

8. 이제 누가 뭘 수정했는지 보겠습니다 Team > show history

한 두 개 뜰 테고 하나하나 눌러보면 언제 누가 어떤 부분 수정했는지 까지 다 볼 수 있어요

 

9. Ingnore을 설정해보겠습니다.
Ignore 기능은 무시하고 올리는 파일들을 설정하는 건데,

예를 들면 설정 파일을 계속 올리고 내리고 할 필요도 없고,

수정을 막 하면 안 되니 설정 파일 같은 것들을 ignore 설정해놓으면

그 파일들만 무시하고 받을 수 있습니다

Ex) pom.xml

Window > preference > team 검색 > ignore resource > add pattern

 

<<SVN사용 시>>

전체 커밋은 하지않는게 좋습니다

다른 사람이 수정 및 추가한 파일 날아가고SVN에 자기 local에 있는 프로젝트 그대로 올라가게 되니 주의하시길바랍니다

처음 시작할때 꼭 update를 하고 commit을 하길바랍니다

에러가 날 경우 대부분 team > refresh/clean up 하거나

본인이 수정한 부분 메모장에 옮겨둔 후 team > update to Head

team > syncronise with repositories > override update 한 후 다시 수정하고 commit,

team > markresolve, edit conflict 충돌 해결해주기

충돌이 나면 우선, 메모장 두 개 켜서 본인 local 소스랑 svn 소스 옮겨놓고 시작하세요 날아갈지도 모릅니다!!

* GitHub설치 https://shxrecord.tistory.com/115?category=677810

* GitHub사용법 https://shxrecord.tistory.com/116

 

* 서버에 접속할 수 있는 고정 IP로 변경하여 접속해라고 할 수 있다 https://extrememanual.net/12161

- 이 웹사이트 접속하여 변경 방법을 기억하자

 

728x90
반응형

'IT_Web > Project_Setting' 카테고리의 다른 글

GitHub 사용법  (0) 2020.03.09
GitHub 설치방법  (0) 2020.03.09
자바 설치 및 환경 설정 스키마 계정생성  (0) 2020.03.09
728x90
반응형

이전 글에서 간략하게 프록시가 무엇이며

프록시를 활용해서 IP를 변경하는 방법을 알아보았습니다

 

더 자세한 정보를 알고 싶으시다면 아래의 링크를 클릭하시길 바랍니다.

 

 

2022.09.02 - [IT_Web/Nodejs] - 웹 크롤링 puppeteer 프록시 IP 적용하기 그런데 대체 프록시 란? 뭘까?

 

웹 크롤링 puppeteer 프록시 IP 적용하기 그런데 대체 프록시 란? 뭘까?

이전 글 웹 크롤링 마우스 조작하기에 대해서 태그를 도저히 못 찾겠다면 마우스를 조작하여 직접 마우스 클릭하는 방법을 구현해서 웹크롤링을 합니다 하지만 마우스 조작하는 방법은 대도록

tantangerine.tistory.com

 

 

 

웹 크롤링은 정보를 추출하여 파일을 만들어 업무 효율높는데 목적이 있습니다

하지만 그 정보를 DB에 보관한다면 중복검사와 데이터 관리에 있어 보다 더 효율적으로

관리가 가능합니다

 

그래서 DB 연동은 웹크롤링을 보다 편하게 하기 위해서는 필수 조건이라고 생각합니다

 

그럼 DB연동 방법을 알아보도록 하겠습니다

 

mysql 설치방법은 다른 여러 블로그에 나와있습니다

그러니 생략하고 sequelize 라이브러리를 설치하는 방법을 알아보겠습니다

 

sequelize 라이브러리 설치하기

아래의 코드를 실행시켜 라이브러리를 설치합니다

 

npm i -g sequelize-cli
npm i --save sequelize
sequelize init
npm i mysql2

 

 

4개의 라이브러리를 설치하고 나면 디렉터리에 새로운 폴더가 추가가 되어있을 것입니다

 

config 폴더와 models폴더가 추가됩니다 

 

 

웹크롤링-DB연동
웹크롤링 DB연동

 

 

DB 연결 함수 및 config.json 설정값 변경하기

 

 

models의 index파일은 DB를 연결하는 설정 함수들이 포함되어있습니다

대문자 sequelize와 Sequelize가 다르니 꼭 확인 하시길바랍니다

 

 

웹크롤링-DB연동2
웹크롤링 연동 설정함수

 

 

config.jsonDB 연결을 위한

host주소 및 DB 패스워드, username 등이 정의되어있습니다

 

그래서 위의 이미지와 같이 사용하는 코드들이 작성되어있습니다

 

아래는 DB config파일입니다

 

웹크롤링-config
웹크롤링 config

 

이제 테이블을 만들어야 합니다

하지만 sequelize를 활용해서 models 폴더에 파일을 만들면 

테이블을 만들 수 있습니다

 

 

위의 디렉터리 구조에서 proxy.js가 보일 것입니다

그 파일을 보면 아래와 같이 칼럼이 정의되어있습니다

null값을 허용할지와 컬럼이 저장될 자료형이 어떤 것 인지도 정할 수 돼있으며

테이블마다 id는 자동부여됩니다

 

 

웹크롤링-proxy테이블
웹크롤링 proxy테이블

 

 

DB 조회 문 함수

sequelizeORM방식이라서 sql을 몰라도 DB를 다를 수 있지만 

DB를 조금 더 자세하게 알고 싶다면 sql을 필히 공부해야 합니다

너무 sequelize 믿지 마시고 sql을 조금씩 공부를 하시길 바랍니다

 

그럼 데이터를 조회해보는 

몇 가지 함수를 찾아보았습니다

한번 확인해보시고 더 많은 조회 문을 찾아보시길 바랍니다

 

 

 

await db.sequelize.sync(); //db를 연결하는 함수입니다

await Promise.all(fristProxies.map(async (v) => { // 테이블 insert문
  return await db.Proxy.create({ // Proxy파일이 module.export되어 데이터를 가져온다
    ip: v.ip,
    type: v.type,
    latency: v.latency,
    anonymity: anonymity.toString() //''만으로 문자열이 구성되면 에러가 발생한다
  });								// ""을 꼭 확인해야합니다
}))

const fastestProxy = await db.Proxy.findOne({ // 한개의 행을 가져오기
  order: [['latency', 'ASC']]
});

 

 

이것으로 DB 연동은 끝났습니다

간단하게 DB 연동을 할 수 있으니 꼭 사이드 프로젝트를 만들면서

자기의 역량을 키울 수 있는 계기가 되었으면 합니다

 

이것으로 웹크롤링 준비단계는 모두 끝이 났습니다

아래는 제일 처음 웹크롤링 포스팅입니다

관심 있으신 분들은 한번 읽어보시길 바랍니다

아니면 카테고리를 클릭해서 전체 글을 보셔도 되겠지요?

 

 

2022.08.17 - [IT_Web/Nodejs] - node.js로 엑셀 및 CSV 파일 데이터 간단하게 가져오기

 

node.js로 엑셀 및 CSV 파일 데이터 간단하게 가져오기

node.js 관련은 처음 소개하는 것 같습니다 다음에 node.js로 서버 구축하는 것도 알아볼 테니 많은 관심 부탁드립니다 우선 node 다운로드 링크 주소는 사이드바에 생성해 놓을 테니 확인해 보시길

tantangerine.tistory.com

 

 

그럼 다음 포스팅도 기대해주시길 바랍니다

 

우리의 길고 긴 IT 대모험은 항상 밝기만을 기원하겠습니다

그럼 다음 포스팅에서 만나요~ 

728x90
반응형
728x90
반응형

이전 글 웹 크롤링 마우스 조작하기에 대해서

 

태그를 도저히 못 찾겠다면 마우스를 조작하여

직접 마우스 클릭하는 방법을 구현해서 웹크롤링을 합니다

하지만 마우스 조작하는 방법은 대도록이면 안 사용하는 것이 좋겠지요?

 

더 상세한 정보가 궁금하시다면 아래의 링크를 클릭해주세요

 

 

2022.09.01 - [IT_Web/Nodejs] - 웹 크롤링 마우스 조작해서 클릭하기 태그를 도저히 못 찾겠다면!?

 

웹 크롤링 마우스 조작해서 클릭하기 태그를 도저히 못찾겠다면!?

이전 포스팅 얼럿창 컨트롤하기에 대하여 웹 크롤링 중 갑자기 얼럿창이 노출될 경우 닫지 않고서는 다음 진행이 어렵습니다 그래서 puppeteer는 이벤트 리스너와 같은 기능을 하는 함수를 가지고

tantangerine.tistory.com

 

 

프록시 란 컴퓨터 네트워크에서 다른 서버 상의 자원을 찾는

클라이언트로부터 요청을 받아 중계하는 서버를 말합니다

사용자의 입장에서는 자신의 웹 서핑 기록을 익명 화하기 위해 웹 프록시를 사용하기도 합니다

 

이러한 프록시 서버를 이용하여 우리의 IP를 다른 새로운 IP로 변경을 하여 웹크롤링을 시도합니다

그렇게 되면 익명의 유저로 또 다시 접근이 가능하게 됩니다

 

 

웹 프록시에서 새로운 IP 크롤링하여 웹 페이지 접속하기

 

setNewProxy 함수를 활용해서 다른 IP를 받는 작업을 합니다

 

먼저 나의 IP가 어떤 값으로 설정되어있는지

Daum 홈페이지 검색창 input에 value값을 할당하여

변경해가는 IP주소가 변경되는 과정을 볼 수 있을 것입니다

 

 

const puppeteer = require('puppeteer');
const writekeyboardList = require('./writekeyboardList');
const dotenv = require('dotenv'); // npm i dotenv
dotenv.config();

const setCrawler = async (production, isMouse = false) => {
  let browser = await puppeteer.launch({
    headless: false,
    args:['--window-size=1920,1080']
  });
  let page = await browser.newPage();
  await page.setViewport({
    width: 1080,
    height:1080,
  })
  
  const setNewProxy = async(crawlerUrl) => {
    const newProxies = await page.evaluate(async() => {
      const ips = Array.from(document.querySelectorAll('tr > td:first-of-type > .spy14')).map((v) => v.innerText)
      const types = Array.from(document.querySelectorAll('tr > td:nth-of-type(2)')).map((v) => v.innerText)
      const anonymity = Array.from(document.querySelectorAll('tr > td:nth-of-type(3)')).map((v) => v.innerText)
      const latencies = Array.from(document.querySelectorAll('tr > td:nth-of-type(6) > .spy1')).map((v) => v.innerText)
      return ips.map((v, i) => {
        return {
          ip: v,
          type: types[i],
          latency: latencies[i],
          anonymity: anonymity[i]
        }
      });
    });
    const fristProxies =  newProxies.filter((v) => v.type === 'HTTP' && v.anonymity === 'HIA').sort((p, c) => p.latency - c.latency)[0]
    await page.close();
    await browser.close();
    browser = await puppeteer.launch({
      headless: false,
      args:['--window-size=1920,1080', '--disable-notifications', `--proxy-server=${fristProxies.ip}`]
    });

    page = await browser.newPage();
    await page.goto(crawlerUrl);
    await page.waitForSelector('.tf_keyword')
    await inputValue('.tf_keyword', '내아이피'),
    await Promise.all([
      buttonClick(['.ico_pctop.btn_search', 'ico_ksearch.btn_ksearch']),
      page.waitForNavigation(),
    ]);
  }

  const handleWriteKeyboard = async (selector, id) => {
    const writeList = writekeyboardList()
    const idOfList = [...id]
    if(selector){
      await page.click(selector);
      console.log(`idOfList`, idOfList)
      for (const id of idOfList) {
        const inputValue = await writeList.filter(element => element.ShiftLeft === id || element.key === id )
        if (inputValue.length > 0) {
          const code = inputValue[0].code
          const ShiftLeft = inputValue[0].ShiftLeft === id ? 'ShiftLeft' : ''
          if (ShiftLeft) {
            await page.keyboard.down(ShiftLeft);
            await setTime(selector, 500)
            await page.keyboard.press(code);
            await setTime(selector,500)
            await page.keyboard.up(ShiftLeft);
          } else {
            await page.keyboard.press(code);
          }
          await setTime(selector,500)
        }
      }
    } else {
      await page.keyboard.press(id);
    }
  }

  const inputValue = async (selector, value) => {
    await page.evaluate((selector, value) => {
      document.querySelector(selector).value = value
    }, selector, value)
    await setTime(selector)
  }

  const buttonClick = async (selector, nextSelector, type) => {
    await page.evaluate((selector) => {
      if (document.querySelector(selector[0])) {
        document.querySelector(selector[0]).click();
      } else {
        document.querySelector(selector[1]).click();
      }
    }, selector)
    if(type === 'login'){
      // waitForRequest(요청 대기), waitForResponse(응답 대기)
      const res = await page.waitForResponse(res => {
        return res.url().includes('graphql')
      })
      const result = await res.json()
      console.log('login', result);
      if(await result.extensions.is_final){
        await setTime(type, 2000)
        await page.keyboard.press('Escape')
      }
    } else if(type === 'logOut'){
      await page.waitForNavigation()
    } else {
      if(nextSelector){
        await page.waitForSelector(nextSelector)
      }
      if(type === 'wait') await setTime(selector)
    }
  }

  const setTime = async (selector = 'normal', waitTime = 0) => {
    const setTime = await page.evaluate(async(waitTime) => {
      const timeList = await [3975, 4547, 5231, 6397, 7894, 3394, 4206, 5147, 6932, 7430, 3561, 4896, 5877, 6407, 7133];
      let randomTime = await timeList[Math.floor(Math.random() * timeList.length)]
      if (waitTime > 0) randomTime = waitTime
      await new Promise(r => setTimeout(r, randomTime))
      return randomTime
    }, waitTime)
    console.log(`waitTime[${selector}]: `, setTime)
    return setTime
  } 

  await page.goto('https://www.daum.net/');
  await page.waitForSelector('.tf_keyword')
  await inputValue('.tf_keyword', '내아이피'),
  await Promise.all([
    buttonClick(['.ico_pctop.btn_search']) ,
    page.waitForNavigation(),
  ]);
  
  await page.goto('http://spys.one/free-proxy-list/KR/');


  return { buttonClick, inputValue, handleWriteKeyboard, setTime, setNewProxy, page, browser }
}


const crawler = async () => {
  try {
    const { buttonClick, inputValue, handleWriteKeyboard, setTime, setNewProxy, page, browser } = await setCrawler(JSON.parse(process.env.PRODUCTION));
    const id = await process.env.EMAIL;
    const password = await process.env.PASSWORD;

    await setNewProxy('https://www.daum.net/')

    // await page.close();
    // await browser.close();

  } catch (e) {
    console.log(e);
  }
}

crawler()

 

 

위의 코드가 상당히 길어서 조금 부담될 수 있지만

 

이전 포스팅에서 알아보았던 것들을 재사용한 것이니

궁금하시면 이전 포스팅이나 카테고리를 클릭하시길 바랍니다

 

아래의 코드는 실질적인 크롤링 실행문이 있습니다

단지 setCrawler()가 실행되어 매서드 및 화면 노출을 하는 초기 세팅값을 설정합니다

 

Daum 홈페이지에서 검색창에 내 아이피를 인자 값으로 넣어

value를 할당하고

검색 버튼을 클릭해서 검색을 하여 IP주소를 확인합니다

 

그리고 setNewProxy()호출해서 다른 IP주소를 할당합니다

 

그럼 아래에서 setNewProxy함수에 대해 알아보도록 하겠습니다

 

 

웹크롤링-시작부
웹크롤링 시작부

 

 

 

 

우선 코딩 분석 전에 페이지의 형태를 확인해 봅시다

IP주소 그리고 proxy type, Anonymity, Latency가 보입니다

 

Proxy address는 IP주소이며,

Proxy type 우리가 흔히 알고 있는 http 관련 사항입니다

Anonymity는 ANM, HIA, NOA 이 있으며 이것은 익명성을 보장하는 정도라고 생각하시면 됩니다

HIA가 제일 높은 수치로 익명성을 보장받을 수 있습니다

Latency는 속도를 의미하며 수치가 낮을수록 빠르다고 할 수 있습니다

 

그럼 4가지의 값들을 가져오기 위해 웹크롤링을 진행합니다

 

 

웹크롤링-프록시
웹크롤링 프록시 페이지

 

 

 

4가지 타입의 태그에 접근하여 innerText 값을 가져옵니다

그것을 배열로 만들어서 newProxies 변수에 담아서 filter함수를 적용해서 제일 빠른 IP주소를 가져옵니다

가져온 IP주소를 변수에 담으면 기존 페이지와 브라우저를 닫고

새 브라우저와 새 페이지를 프록시 서버 설정하여 활성화합니다 

 

이때 Daum 홈페이지에 다시 한번 내 아이피를 확인하면

변경된 IP주소를 확인할 수 있습니다

 

그리고 페이지 중간중간 이동할 경우 waitForSelector()을 사용해서 새로운 페이지의 지정된 태그를 기다리게 됩니다

그리고 buttonClick함수가 실행되고 페이지가 이동된다면

Promise.all()을 사용하여 순차적으로 넣어 줍니다

 

* inputValue, buttonClick 함수의 설명은 이전 포스팅을 참고하세요 

 

 

웹크롤링-함수부
웹크롤링 함수부

 

 

이렇게 설정하면 프록시 IP주소를 가져올 수 있습니다

 

한번 직접 코딩을 해서 웹크롤링을 돌려보는 것이 더욱 이해가 빠르다고 생각합니다

그럼 오늘도 이렇게 공부를 하였습니다

 

IT 개발이란 것은 영원히 공부를 해야 하지 않을까? 합니다

새로운 언어들이 나오고 웹의 새로운 환경들을 나오면서

더욱 우리는 공부를 해야 할 듯합니다

 

아직 IT 대모험이 끝나지 않았으니까

앞으로도 힘내시길 바랍니다

 

그럼 다음 포스팅도 기대해주세요

 

 

 

 

 

 

 

728x90
반응형
728x90
반응형

이전 포스팅 얼럿창 컨트롤하기에 대하여

 

웹 크롤링 중 갑자기 얼럿창이 노출될 경우 닫지 않고서는

다음 진행이 어렵습니다

그래서 puppeteer이벤트 리스너와 같은 기능을 하는 함수를 가지고 있습니다 

그 함수는  page.on 이며, 여러 인자 값을 받아 여러가지 이벤트를 핸들링할 수 있습니다

 

더 상세한 정보를 알고 싶으시면 아래의 글을 참고해주시길 바랍니다

 

 

2022.08.31 - [IT_Web/Nodejs] - 웹 크롤링 puppeteer로 alert, prompt, confirm 창 얼럿창 제어 및 컨트롤 하기

 

웹 크롤링 puppeteer로 alert, prompt, confirm 창 얼럿창 제어 및 컨트롤 하기

이전 포스팅 키보드 코드 입력하기에 대해서 puppeteer로 웹 크롤링할 때 키보드를 입력하는 방법을 알아보았습니다 키보드의 자판만큼이나 코드도 엄청 많았습니다 그래서 그 문서를 Array형 Object

tantangerine.tistory.com

 

 

웹 크롤링 puppeteer 활용해서  마우스 조작하기

 

웹 크롤링에서 마우스를 조작하는 이유는 무엇일까요?

간단한 매크로를 만들거나, 태그를 정말 찾기 어려울 때 어쩔 수 없이 사용하게 될 것입니다

마우스를 조작해야 될 경우가 없는 게 좋겠지만 해야 할 경우가 생길 수 있으니

알아두면 편하겠죠?

 

그럼 코드를 보면서 그 방법을 알아보도록 하겠습니다

 

 

const puppeteer = require('puppeteer');
const callElement = require('./mouseHandleHtml');
const dotenv = require('dotenv'); // npm i dotenv
dotenv.config();

const setCrawler = async (production) => {
  const browser = await puppeteer.launch({
    headless: production,
    args:['--window-size=1920,1080']
  });
  const page = await browser.newPage();
  await page.setViewport({
    width: 1080,
    height:1080,
  })
  await page.goto('https://facebook.com');

  if (!production) {
    await callElement(page)
  }

  const setTime = async (selector = 'normal', waitTime = 0) => {
    const setTime = await page.evaluate(async(waitTime) => {
      const timeList = await [3975, 4547, 5231, 6397, 7894, 3394, 4206, 5147, 6932, 7430, 3561, 4896, 5877, 6407, 7133];
      let randomTime = await timeList[Math.floor(Math.random() * timeList.length)]
      if (waitTime > 0) randomTime = waitTime
      await new Promise(r => setTimeout(r, randomTime))
      return randomTime
    }, waitTime)
    console.log(`waitTime[${selector}]: `, setTime)
    return setTime
  } 

  return { setTime, page: page, browser: browser }
}


const crawler = async () => {
  try {
    const production = await process.env.PRODUCTION;
    console.log('production', production, typeof(JSON.parse(production)))
    const { setTime, page, browser } = await setCrawler(JSON.parse(production));

    await setTime('', 2000)
    await page.mouse.move(100, 100);
    await page.mouse.down()
    await setTime('', 2000)
    await page.mouse.move(1000, 400);
    await setTime('', 2000)
    await page.mouse.click(1000, 400);
    await setTime('', 2000)

  } catch (e) {
    console.log(e);
  }
}

crawler()

 

env파일이 아래와 같이 설정되어있다고 합시다

PRODUCTION이 true 될 경우 실 운영 버전으로 개발이 다 끝난 경우에 설정하는 값이라고 합시다

지금은 false로 개발을 진행한다고 설정하고 웹 크롤링 과정을 지켜보며 개발을 하게 됩니다

 

 

웹크롤링-env
웹 크롤링 env 파일

 

production 변수에 envPRODUCTION을 할당합니다

그 설정된 값을 자료형을 boolean으로 변경하고

setCrawler() 함수에 매개변수 인자 값으로 production을 할당합니다

 

 

웹크롤링-env관리
웹 크롤링 env 관리하기

 

빨간 테두리에는 할당된 인자 값인 production을 headles를 false로 할당해서

웹 크롤링하는 과정을 화면으로 지켜볼 수 있게 false로 지정합니다

headless 머리가 없다는 뜻으로 false로 머리가 있다는 뜻이 됩니다

그럼 화면을 볼 수 있다는 것이겠지요?

 

 

 

 

위의 노랑 테두리는 

production이 false이면 if문을 안을 실행하게 됩니다

그래서 callElement()에 page 객체를 인자 값으로 호출하게 됩니다

즉, 운영이 아닌 환경설정값으로 마우스를 노출시킬 수 있는 Element를 생성하는 함수를 호출하게 됩니다

운영이라면 if문을 실행하지 않고 지나치게 될 것입니다

 

callElement는 아래와 같이./mouseHandleHtml 파일에서 함수를 가져오게 되어있습니다

 

웹크롤링-마우스조작
웹크롤링 마우스 조작 노출 element 생성

 

 

아래의 코드로 정의된 callElement함수는 

마우스가 어디에 있는지 화면에서 확인할 수 있는 html, CSS을 생성하게 됩니다

위와 말했듯이 개발에만 생성하게 되고  그 이유는 웹 크롤링하는 과정을 보면서 개발하기 위해서입니다

 

 

module.exports = async function  callElement(page) {
  await page.evaluate(() => {
      (() => {
          const box = document.createElement('div');
          box.classList.add('mouse-helper');
          const styleElement = document.createElement('style');
          styleElement.innerHTML = `.mouse-helper {
              pointer-events: none;
              position: absolute;
              z-index: 1000000000;
              top: 0;
              left: 0;
              width: 20px;
              height: 20px;
              background: rgba(0,0,0,.4);
              border: 1px solid white;
              border-radius: 10px;
              margin-left: -10px;
              margin-top: -10px;
              transition: background .2s, border-radius .2s, border-color .2s;
            }
            .mouse-helper.button-1 {
              transition: none;
              background: rgba(0,0,0,0.9);
            }
            .mouse-helper.button-2 {
              transition: none;
              border-color: rgba(0,0,255,0.9);
            }
            .mouse-helper.button-3 {
              transition: none;
              border-radius: 4px;
            }
            .mouse-helper.button-4 {
              transition: none;
              border-color: rgba(255,0,0,0.9);
            }
            .mouse-helper.button-5 {
              transition: none;
              border-color: rgba(0,255,0,0.9);
            }
            `;
          document.head.appendChild(styleElement);
          document.body.appendChild(box);
          document.addEventListener('mousemove', event => {
            box.style.left = event.pageX + 'px';
            box.style.top = event.pageY + 'px';
            updateButtons(event.buttons);
          }, true);
          document.addEventListener('mousedown', event => {
            updateButtons(event.buttons);
            box.classList.add('button-' + event.which);
          }, true);
          document.addEventListener('mouseup', event => {
            updateButtons(event.buttons);
            box.classList.remove('button-' + event.which);
          }, true);
          function updateButtons(buttons) {
            for (let i = 0; i < 5; i++)
              box.classList.toggle('button-' + i, !!(buttons & (1 << i)));
          }
        })();
    });
}

 

 

 

그리고 실질적으로 웹 크롤링을 실행하는 함수를 보시면

웹 크롤링 화면 설정과 필요 기능 함수를 선언하는 setCrawler()가 있습니다

 

그리고 setTime으로 잠시 대기하여 마우스를 움직이며 드래그하는 모습을 볼 수 있습니다

 

 

 

웹크롤링-마우스조작2
웹크롤링 마우스 조작 코드

 

 

아래의 이미지를 보시면 드래그를 한 후 그 자리 좌표에서 클릭을 하면

드래그가 없어지는 것을 확인할 수 있습니다

 

 

 

 

 

 

 

이렇게 웹 크롤링 마우스 조작 법을 알아보았습니다

그렇게 어렵지 않고 함수 기능만 알고 있다면

간편하게 구현할 수 있다는 것을 알게 되었습니다

 

저는 웹 크롤링을 공부하는 이유는 여러 가지 정보를 아침에 모아서 보려고 합니다

그래서 같이 공부하고 끝이 나면

개인적인 프로젝트를 진행하는 과정도 포스팅을 하도록 하겠습니다

 

후~ 그럼 오늘도 고생하셨습니다

 

공부가 힘들고 어렵겠지만 조금만 힘내시면 

그 시기는 지나갈 것이라 생각합니다

 

난중에는 너무 자연스럽게 코딩하는 자신의 모습을 볼 수 있을 것입니다

그럼 그때까지 파이팅 하시고

 

다음 포스팅도 기대해주세요!

감사합니다

728x90
반응형
728x90
반응형

이전 포스팅 키보드 코드 입력하기에 대해서

puppeteer로 웹 크롤링할 때 키보드를 입력하는 방법을 알아보았습니다

키보드의 자판만큼이나 코드도 엄청 많았습니다

그래서 그 문서를 Array형 Object객체로 만들어 자동으로 입력할 수 있게 만들었습니다

같은 함수를 계속 나열해서 쓰지 않아도 되니 코드도 간결해지고 좋았던 것 같습니다

그리고 무엇보다도 사용자의 유저 최적화를 할 수 있는 장점이 있지요

id를 입력하는 경우 키보드 코드로 입력 안 할 경우에는 한 번의 순간에 ID 값이 입력되니

분명 유저처럼 보이지는 않을 것입니다

 

그래서 봇으로 보이지 않게 하기 위한 방법이니 아주 유용할 것 같습니다

 

더 자세한 사항은 아래의 링크를 클릭해서 확인해주세요

 

 

 

2022.08.30 - [IT_Web/Nodejs] - 웹 크롤링 puppeteer 활용 및 keyboard press 함수로 키보드 코드 입력하기

 

웹 크롤링 puppeteer 활용 및 keyboard press 함수로 키보드 코드 입력하기

이전 포스팅 웹 크롤링 로그인 로그아웃에 대하여 로그인과 로그아웃에 대하여 알아보았습니다 코드를 함수형으로 만들어서 이전 코드보다 간결하게 만들어 보았지요 그리고 웹 크롤링에서는

tantangerine.tistory.com

 

웹 크롤링 puppeteer로 alert, prompt, confirm 창 컨트롤 하기

 

웹 크롤링 도중 alert, confirm, prompt창이 노출되면 아무것도 하지 못하게 된다

이럴 경우 창을 제어하는 함수를 구현해보려고 합니다

아래와 같이 setCrawler 함수에 여러 가지가 세팅되어있다

천천히 분석해보시길 바라며 상세한 설명은 아래에서 하겠습니다

 

const puppeteer = require('puppeteer');
const writekeyboardList = require('./writekeyboardList');
const dotenv = require('dotenv'); // npm i dotenv
dotenv.config();

const setCrawler = async () => {
  const browser = await puppeteer.launch({
    headless: false,
    args:['--window-size=1920,1080']
  });
  const page = await browser.newPage();
  await page.setViewport({
    width: 1080,
    height:1080,
  })
  await page.goto('https://facebook.com');

  await page.on('dialog', async (dialog) => { 
    console.log(`type: ${dialog.type()} / message: ${dialog.message()}`) 
    if(dialog.type() === 'prompt') {
      await dialog.accept('https://www.tistory.com/')
    }
    if(dialog.type() === 'alert'){
      await dialog.dismiss(); 
    }
    if(dialog.type() === 'confirm'){
      if (dialog.message() === '확인을 누르시면 tistory홈페이지로 이동합니다.') {
        await dialog.accept();
      } else {
        await dialog.dismiss();
      }
    }
  })

.... 이하 생략 이전 포스팅 참고 바랍니다.

 

 

 

아래의 코드는 실질적으로 웹 크롤링을 실행하는 함수들이 선언되어있습니다.

보시면 setCrawler()가 보이시죠? 브라우저를 띄우고 새 탭 화면을 노출시키고 화면의 비율을 맞추는 등

기본적인 환경설정을 하고 나서 선언된 함수들을 return 해서 사용하고 있습니다

 

오늘 포스팅에서는 사용하지는 않지만 이전 포스팅을 참고해주세요

 

그럼 본론으로 alert, confirm, prompt 컨트롤하는 함수를 선언해서 확인해보겠습니다

우선 한 개씩 하기 때문에 나머지는 주석을 하면서 진행해 보도록 하겠습니다

 

const crawler = async () => {
  try {
    const { buttonClick, inputValue, handleWriteInuput, setTime, page, browser } = await setCrawler();
    await page.evaluate(async() => {
      alert('alert창을 닫으면 티스토리 홈페이지로 이동합니다.')
      location.href = 'http://www.tistory.com'
    })

    await page.evaluate(() => {
      if (confirm('확인을 누르시면 tistory홈페이지로 이동합니다.')) {
        location.href = 'http://www.tistory.com'
      }
    })

    await page.evaluate(() => {
      const promptData = prompt('url 주소를 입력하세요.')
      location.href = promptData
    })

  } catch (e) {
    console.log(e);
  }
}

crawler()

 

 

아래와 같이 코드가 구현되어있습니다

먼저 빨간 테두리에 page.on()가 있습니다

이 함수는 EventListener로서 어떠한 이벤트 가 발생할 때 감지해서 

이벤트가 발생한 함수에 영향을 주는 함수입니다

 

매개변수('dialog', function)로 받아 콜백 함수에서 실행을 이어갑니다

그래서 콜백 함수 내에서 dialog에 대해 type, message를 분기로 사용해서

 

 

웹크롤링-이벤트리스너
웹크롤링 이벤트 리스너 함수

 

 

alert은 창이 뜨면 그냥 바로 창을 바로 제거하는 함수를 사용합니다

alert창이 사라지고 다음 단계를 진행할 수 있게 되며,

선언된 alert함수 그 이후의 선언된 함수가 실행되어

 

웨크롤링-alert제어
웹크롤링 alert창 제어

 

 

confirm창이 승낙할 경우 confirm함수 true, 거절할 경우 false 반환되게

dialog.accept()와 dialog.dismiss()를 호출하여 confirm()가 선언된 곳에 값을 전달합니다

 

웹크롤링-confirm제어
웹크롤링 confirm 창 제어

 

 

prompt창은 input이 활성화되면서 글자를 입력할 수 있으며,

그 창에 노출되고 dialog.accept() 인자 값으로 'https://www.tistory.com'로 할당하게 되면

홈페이지가 이동하게 됩니다

 

 

웹크롤링-prompt제어
웹크롤링 prompt창 제어

 

 

 

오늘은 여기까지 웹크롤링 중 EventListener를 사용하는 page.on()를 선언해서

alert, confirm, prompt 활성화될 경우 제어하는 방법을 알아보았습니다

 

얼럿 창이 갑자기 활성화된다면 웹크롤링이 갑자기 멈출 수도 있기 때문에

이벤트 리스너가 꼭 필요할 것입니다

 

오늘도 이렇게 한걸음 나아갔다고 생각합니다

우리의 IT대모험은 아직 남았으니 파이팅 하자고요~!!

 

그럼 다음 포스팅도 기대해주세요~

 

 

 

728x90
반응형
728x90
반응형

이전 포스팅 웹 크롤링 로그인 로그아웃에 대하여

로그인과 로그아웃에 대하여 알아보았습니다

코드를 함수형으로 만들어서 이전 코드보다 간결하게 만들어 보았지요

그리고 웹 크롤링에서는 무엇보다

태그를 찾는것중요하여 같이 알아보았습니다

 

좀 더 상세히 알고 싶으시다면 아래의 링크를 클릭하시길 바랍니다

 

 

2022.08.29 - [IT_Web/Nodejs] - 웹 크롤링 로그인, 로그아웃 그리고 waitForResponse 응답대기 활용하기 및 태그찾기, 태그 값 할당

 

웹 크롤링 로그인, 로그아웃 그리고 waitForResponse 응답대기 활용하기 및 태그찾기, 태그 값 할당

이전 포스팅에서 웹 크롤링 인피니트 스크롤링이 무엇인가 알아보았으며, 인피니트 스크롤링이 적용된 페이지에 대해서 이미지를 다운하고 파일로 만드는 방법을 알아보았습니다 인스타그램,

tantangerine.tistory.com

 

 

앞서 웹 크롤링에서 중요한것은 태그를 찾는것이라 했습니다

하지만 중요한것이 하나 더있습니다

그것은 바로 비동기입니다

하지만 비동기에서도 순ㅍ차적으로 실행할지 비순차적으로 할것인지가 중요합니다

웹 크롤링할 경우 순차적으로 진행할 경우가 많기 때문에 비동기식이며 순차적인 방법이 중요합니다

 

이전 포스팅에서는 로그인할경우 아이디를 inputvalue에 할당하여 한번에 아이디의 문자열이 모두 들어갔습니다

하지만 그렇게 된다면 유저가아닌 봇으로 의심을 받아 제재를 받을 수 있습니다 

그래서 이 부분을 함수형으로 만들어 간결하게 만들어봅시다

 

아래 handleWriteInput 함수를 만들어서 process.env.EMAIL을 id로 받아서 id를 확장 연산자를 사용해서

배열로 만들어서 로그인창에서 한글자씩 시간을 두고 글자를 입력하게됩니다

좀 더 자세한것은 아래에서 확인해보겠습니다

 

 

const puppeteer = require('puppeteer');
const writekeyboardList = require('./writekeyboardList');
const dotenv = require('dotenv'); // npm i dotenv
dotenv.config();

const setCrawler = async () => {
  const browser = await puppeteer.launch({
    headless: false,
    args:['--window-size=1920,1080']
  });
  const page = await browser.newPage();
  await page.setViewport({
    width: 1080,
    height:1080,
  })

  const handleWriteInuput = async (selector, id) => {
    const writeList = writekeyboardList()
    const idOfList = [...id]
    await page.click(selector);
    console.log(`idOfList`, idOfList)
      for (const id of idOfList) {
        const inputValue = await writeList.filter(element => element.ShiftLeft === id || element.key === id )
        if (inputValue.length > 0) {
          const code = inputValue[0].code
          const ShiftLeft = inputValue[0].ShiftLeft === id ? 'ShiftLeft' : ''
          if (ShiftLeft) {
            await page.keyboard.down(ShiftLeft);
            await setTime(selector, 500)
            await page.keyboard.press(code);
            await setTime(selector,500)
            await page.keyboard.up(ShiftLeft);
          } else {
            await page.keyboard.press(code);
          }
          await setTime(selector,500)
        }
      }
  }

  const inputValue = async (selector, value) => {
    await page.evaluate((selector, value) => {
      document.querySelector(selector).value = value
    }, selector, value)
    await setTime(selector)
  }

  const buttonClick = async (selector, nextSelector, type) => {
    await page.evaluate((selector) => {
      document.querySelector(selector).click();
    }, selector)
    if(type === 'login'){
      // waitForRequest(요청 대기), waitForResponse(응답 대기)
      const res = await page.waitForResponse(res => {
        return res.url().includes('graphql')
      })
      const result = await res.json()
      console.log('login', result);
      if(await result.extensions.is_final){
        await setTime(type, 2000)
        await page.keyboard.press('Escape')
      }
    } else if(type === 'logOut'){
      await page.waitForNavigation()
    } else {
      if(nextSelector){
        await page.waitForSelector(nextSelector)
      }
      await setTime(selector)
    }
  }

  const setTime = async (selector = 'normal', waitTime = 0) => {
    const setTime = await page.evaluate(async(waitTime) => {
      const timeList = await [3975, 4547, 5231, 6397, 7894, 3394, 4206, 5147, 6932, 7430, 3561, 4896, 5877, 6407, 7133];
      let randomTime = await timeList[Math.floor(Math.random() * timeList.length)]
      if (waitTime > 0) randomTime = waitTime
      await new Promise(r => setTimeout(r, randomTime))
      return randomTime
    }, waitTime)
    console.log(`waitTime[${selector}]: `, setTime)
    return setTime
  } 
  return { buttonClick, inputValue, handleWriteInuput, setTime, page: page, browser: browser }
}


const crawler = async () => {
  try {
    const { buttonClick, inputValue, handleWriteInuput, setTime, page, browser } = await setCrawler();
    await page.goto('https://facebook.com');

    const id = await process.env.EMAIL;
    const password = await process.env.PASSWORD;
    
    // 로그인
    //await inputValue('#email', id);
    handleWriteInuput('#email', id)
    await inputValue('#pass', password);
    await buttonClick('[name="login"]', '', 'login');
   

    // 로그아웃
    await buttonClick('.i8zpp7h3', '.i1n1lj7b.mmwt03ec');
    await buttonClick('.i1n1lj7b.mmwt03ec > div:last-child > div', '', 'logOut')
    await page.close();
    await browser.close();
  } catch (e) {
    console.log(e);
  }
}

crawler()

 

 

아래의 함수를 확인하면 각자의 역할들이 존재합니다

아래에서 차근차근 설명해 보겠습니다

 

웹크롤링-키보드코드
키보드 코드 사용하여 입력하기 함수형!

 

 

먼저 빨강 테두리의 함수는 키보드 코드 리스트가 담겨져 있으며,

require()를 사용해서 파일에 접근해서 함수를 사용할 수 있게 합니다

 

 

웹크롤링-nodejs
웹크롤링 키보드 코드 파일

 

 

파일에는 아래와 같은 함수가 export되고 있습니다

code가 위의 keyboard.press()의 매개변수 인자값이 되며

Shift를 누른 상태 값은 ShiftLeft의 값이 대체되어 값이 입력된다

 

 

 

 

module.exports = function writekeyboardList() {
  const keyboard = [
    {'keyCode': 48, 'code': 'Digit0', 'ShiftLeft': ')', 'key': '0'},
    {'keyCode': 49, 'code': 'Digit1', 'ShiftLeft': '!', 'key': '1'},
    {'keyCode': 50, 'code': 'Digit2', 'ShiftLeft': '@', 'key': '2'},
    {'keyCode': 51, 'code': 'Digit3', 'ShiftLeft': '#', 'key': '3'},
    {'keyCode': 52, 'code': 'Digit4', 'ShiftLeft': '$', 'key': '4'},
    {'keyCode': 53, 'code': 'Digit5', 'ShiftLeft': '%', 'key': '5'},
    {'keyCode': 54, 'code': 'Digit6', 'ShiftLeft': '^', 'key': '6'},
    {'keyCode': 55, 'code': 'Digit7', 'ShiftLeft': '&', 'key': '7'},
    {'keyCode': 56, 'code': 'Digit8', 'ShiftLeft': '*', 'key': '8'},
    {'keyCode': 57, 'code': 'Digit9', 'ShiftLeft': '\(', 'key': '9'},
    {'keyCode': 65, 'code': 'KeyA', 'ShiftLeft': 'A', 'key': 'a'},
    {'keyCode': 66, 'code': 'KeyB', 'ShiftLeft': 'B', 'key': 'b'},
    {'keyCode': 67, 'code': 'KeyC', 'ShiftLeft': 'C', 'key': 'c'},
    {'keyCode': 68, 'code': 'KeyD', 'ShiftLeft': 'D', 'key': 'd'},
    {'keyCode': 69, 'code': 'KeyE', 'ShiftLeft': 'E', 'key': 'e'},
    {'keyCode': 70, 'code': 'KeyF', 'ShiftLeft': 'F', 'key': 'f'},
    {'keyCode': 71, 'code': 'KeyG', 'ShiftLeft': 'G', 'key': 'g'},
    {'keyCode': 72, 'code': 'KeyH', 'ShiftLeft': 'H', 'key': 'h'},
    {'keyCode': 73, 'code': 'KeyI', 'ShiftLeft': 'I', 'key': 'i'},
    {'keyCode': 74, 'code': 'KeyJ', 'ShiftLeft': 'J', 'key': 'j'},
    {'keyCode': 75, 'code': 'KeyK', 'ShiftLeft': 'K', 'key': 'k'},
    {'keyCode': 76, 'code': 'KeyL', 'ShiftLeft': 'L', 'key': 'l'},
    {'keyCode': 77, 'code': 'KeyM', 'ShiftLeft': 'M', 'key': 'm'},
    {'keyCode': 78, 'code': 'KeyN', 'ShiftLeft': 'N', 'key': 'n'},
    {'keyCode': 79, 'code': 'KeyO', 'ShiftLeft': 'O', 'key': 'o'},
    {'keyCode': 80, 'code': 'KeyP', 'ShiftLeft': 'P', 'key': 'p'},
    {'keyCode': 81, 'code': 'KeyQ', 'ShiftLeft': 'Q', 'key': 'q'},
    {'keyCode': 82, 'code': 'KeyR', 'ShiftLeft': 'R', 'key': 'r'},
    {'keyCode': 83, 'code': 'KeyS', 'ShiftLeft': 'S', 'key': 's'},
    {'keyCode': 84, 'code': 'KeyT', 'ShiftLeft': 'T', 'key': 't'},
    {'keyCode': 85, 'code': 'KeyU', 'ShiftLeft': 'U', 'key': 'u'},
    {'keyCode': 86, 'code': 'KeyV', 'ShiftLeft': 'V', 'key': 'v'},
    {'keyCode': 87, 'code': 'KeyW', 'ShiftLeft': 'W', 'key': 'w'},
    {'keyCode': 88, 'code': 'KeyX', 'ShiftLeft': 'X', 'key': 'x'},
    {'keyCode': 89, 'code': 'KeyY', 'ShiftLeft': 'Y', 'key': 'y'},
    {'keyCode': 90, 'code': 'KeyZ', 'ShiftLeft': 'Z', 'key': 'z'},
    {'keyCode': 190, 'code': 'Period', 'ShiftLeft': '>', 'key': '.'},
  ]
  return keyboard
};

 

 

 

 

더 많은 키보드 코드들을 알고 싶다면,

사이드 바에 공식문서 & 유용한 도구 목록을 확인해보세요!

 

 

웹크롤링-공식문서
웹크롤링 공식문서

 

그렇게 지정된 코드들을 아래의 특정 문자열을 받아와서 filter()를 적용해서 code, ShiftLeft를 가져옵니다

ShiftLeft가 값이 할당되면 대문자라는 뜻이므로 

대문자라면 keyboard.down(), keyboard.up()을 적용합니다

 

아래의 함수를 선언해서 handleWrite를 호출하는 것만으로 간단하게 적용할 수 있습니다

또 한, for(const id of idOfList)에서 비동기식 순차적으로 실행하게되어

글자를 한글자씩 입력하게됩니다

 

for (const id of idOfList) {
    const inputValue = await writeList.filter(element => element.ShiftLeft === id || element.key === id )
    if (inputValue.length > 0) {
      const code = inputValue[0].code
      const ShiftLeft = inputValue[0].ShiftLeft === id ? 'ShiftLeft' : ''
      if (ShiftLeft) {
        await page.keyboard.down(ShiftLeft);
        await setTime(selector, 500)
        await page.keyboard.press(code);
        await setTime(selector,500)
        await page.keyboard.up(ShiftLeft);
      } else {
        await page.keyboard.press(code);
      }
      await setTime(selector,500)
    }
}

 

 

 

이렇게 웹크롤링에서 키보드 코드를 입력해서 사용자 친화적으로 만들어서

봇으로 의심을 조금이라도 피하게하기위함입니다

웹크롤링의 포스팅 글이 점점 길어지고 있습니다

그래도 알게되는 것도 많으며,  앞으로 확실히 나아가는 것같습니다

그럼 다음 포스팅도 기대해주시고 

 

다음 웹 크롤링 관련 글로 찾아뵙겠습니다

 

대모험이 끝나기를 기대하며 화이팅!

 

 

 

728x90
반응형
728x90
반응형

이전 포스팅에서 웹 크롤링 인피니트 스크롤링무엇인가 알아보았으며,

인피니트 스크롤링이 적용된 페이지에 대해서

이미지를 다운하고 파일로 만드는 방법을 알아보았습니다

 

인스타그램, 페이스북 여러 웹사이트에서 인피니트 스크롤링을 적용하고 있으니

꼭 알아두어야 할 것 같습니다

 

그럼 더 자세히 알고 싶으시면 아래의 링크를 확인해주세요

 

 

2022.08.27 - [IT_Web/Nodejs] - 웹 크롤링 인피니트 스크롤링 조작하여 이미지 다운로드하고 파일 만들기

 

웹 크롤링 인피니트 스크롤링 조작하여 이미지 다운받고 파일만들기

이전 포스팅에서 웹 크롤링을 하여 스크린샷을 찍는 방법에 대해 풀 스크린과 어느 특정 영역, 또는 화면 전체 이렇게 3가지 방법으로 스크린샷 찍는 방법을 알아보았습니다 웹크롤링할 때 화면

tantangerine.tistory.com

 

 

본론에 앞서 웹 크롤링을 하실 때는

본인이 책임져야 한다는 것을 잊지 마세요

합법적이지 못한 방법은 언제나 

꼭 책임이 따른다는 것을 명심하시길 바랍니다

 

그러니 회사 업무의 효율성을 위해서만 사용하시고

올바르게 사용하시길 바랍니다

 

그리고

웹 크롤링이 벌써 7번째(?) 포스팅인 것 같습니다

그래서 환경설정과 여러 라이브러리 설치 방법은 생략하고 진행하겠습니다

보고 싶으신 분이 계시다면 이전 포스팅에서 확인하시길 바랍니다

메뉴에 Node 쪽을 확인해보시면 관련 포스팅을 찾을 수 있습니다 

 

그럼 이제 본론으로 넘어가 보겠습니다

 

웹 크롤링 로그인, 로그아웃 및 waitForResponse(응답 대기) 알아보기 

 

아래의 코드를 보시면

약간 코드를 수정해서 값을 할당하는 함수와 웹 크롤링하는 함수를 불리하여

함수의 재사용을 높일 수 있게 변경하였습니다

 

그럼 아래에서 더 상세 한 설명을 이어나가겠습니다

기초적인 브라우저 환경설정 등은 주석으로 설명을 대신하겠습니다

로그인 로그아웃에 초점을 맞추겠습니다

 

const puppeteer = require('puppeteer');
const dotenv = require('dotenv'); // npm i dotenv
//const userAgent = require('new-user-agent')
dotenv.config();

const setCrawler = async () => {
  const browser = await puppeteer.launch({ // 웹 사이트의 브라우저를 띄우는 함수이다
    headless: false, // 웹크롤링할 때 사이트 노출 여부를 결정하는 값(운영을 하게될때 노출은 필요없다)
    args:['--window-size=1920,1080'] // 브라우저의 size를 설정한다
  });
  const page = await browser.newPage(); // 브라우저를 노출하고 새 탭보기로 페이지를 오픈한다
  await page.setViewport({ // 오픈할 때 가로 세로 길이를 설정한다
    width: 1080,
    height:1080,
  })

  //userAgent의 값을 랜덤으로 셋팅해주는 함수입니다 하지만 
  // 하지만 버전이 너무 낮아 현제 웹페이지에는 맞는게 없네요
  //await page.setUserAgent(userAgent.test());
  //console.log('userAgent', userAgent.test());

  const inputValue = async (selector, value) => {
    await page.evaluate((selector, value) => {
      document.querySelector(selector).value = value
    }, selector, value)
    await setTime(selector)
  }
  const buttonClick = async (selector, nextSelector, type) => {
    await page.evaluate((selector) => {
      document.querySelector(selector).click();
    }, selector)
    if(type === 'login'){
      const res = await page.waitForResponse(res => {
        return res.url().includes('graphql')
      })
      const result = await res.json()
      console.log('login', result);
      if(await result.extensions.is_final){
        await setTime(type)
        await page.keyboard.press('Escape')
      }
    } else if(type === 'logOut'){
      await page.waitForNavigation()
    } else {
      if(nextSelector){
        // waitForRequest(요청 대기), waitForResponse(응답 대기)
        await page.waitForSelector(nextSelector)
      }
      await setTime(selector)
    }
  }
  const setTime = async (selector = 'normal') => {
    const setTime = await page.evaluate(async() => {
      const timeList = await [3975, 4547, 5231, 6397, 7894, 3394, 4206, 5147, 6932, 7430, 3561, 4896, 5877, 6407, 7133];
      const randomTime = await timeList[Math.floor(Math.random() * timeList.length)]
      await new Promise(r => setTimeout(r, randomTime))
      return randomTime
    })
    console.log(`waitTime[${selector}]: `, setTime)
    return setTime
  } 
  return { buttonClick, inputValue, setTime, page: page, browser: browser }
}

// keyboard.press(); 인자값 목록 https://github.com/puppeteer/puppeteer/blob/v1.12.2/lib/USKeyboardLayout.js
// $('.i8zpp7h3')
// $('.i1n1lj7b.mmwt03ec')
// 

const crawler = async () => {
  try {
    const { buttonClick, inputValue, setTime, page, browser } = await setCrawler();
    const url = 'https://facebook.com';
    await page.goto(url);

    const id = await process.env.EMAILL;
    const password = await process.env.PASSWORD;
    
    // 로그인
    await inputValue('#email', id);
    await inputValue('#pass', password);
    await buttonClick('[name="login"]', '', 'login');
   

    // 로그아웃
    await buttonClick('.i8zpp7h3', '.i1n1lj7b.mmwt03ec');
    await buttonClick('.i1n1lj7b.mmwt03ec > div:last-child > div', '', 'logOut')
    // await page.close();
    // await browser.close();
  } catch (e) {
    console.log(e);
  }
}

crawler()

 


 

로그인 input 태그 찾기 및 로그인 하기

 

아래의 이미지를 보시면

<input> 태그 id, name속성 값이 email로 지정되어있습니다

 

 

웹크롤링-태그찾기
웹크롤링 태그 찾기

 

 

위에서 찾은 태그 이름인 #email, #pass를 함수 호출의 매개변수 첫 번째 인자 값으로 설정합니다

그리고 두 번째에는 로그인할 id와 password를 지정합니다

 

다음으로 선언된 inputValue 함수에 page.evaluate()를 호출하여 document.querySelector()로 태그에 접근합니다

이때 첫 번째 매개변수인 selector 인자 값을  document.querySelector(selector) 할당하여 호출합니다

그럼 태그를 가져오게 되고 그때 .value값을 새로 지정하거나 click()를 호출하여 버튼을 클릭하여

로그인을 시도하게 됩니다

 

 

여기서 잠깐!!

page.evalueate() 함수 안에서 document객체에 접근할 수 있다는 것 기억하시죠??

그리고 evaluate()에서 두 번째 매개변수부터 값을 할당해서 호출하면 

evaluate() 내부에서 사용이 가능하다는 것을 잊지 마세요

 

 

 

 

 

 

웹크롤링-로그인
웹크롤링 로그인 함수

 

 

evaluate() 함수에서 document객체를 활용해서 id, password를 할당하는 것과

puppeteer를 활용해서 하는 방법 두 가지가 있습니다

 

아래의 코드에서 page.type(), page.hover(), page.click() 3개의 함수를 확인할 수 있습니다

간단해 보이는 이 함수들은 그 사용법과 활용법이 다르며

언제 이 함수들이 버전업으로 활용방법이 변경될지 모르는 일입니다

그러니 대도록이면 함수를 선언하여 위와 같이 호출하는 방식으로 사용하시길 바랍니다

 

 

const crawler = async () => {
  try {
    const { buttonClick, inputValue, setTime, page, browser } = await setCrawler();
    await page.goto('https://facebook.com');

    const id = await process.env.EMAILL;
    const password = await process.env.PASSWORD;

    await page.type('#email', id);
    //await inputValue('#email', id);

    await page.type('#pass', password);
    //await inputValue('#pass', password);

    await page.hover('[name="login"]'); // 마우스를 선택자에 올리기
    await Promise.all([
      page.click('[name="login"]'),
      page.waitForNavigation(),
    ]);
    //await buttonClick('[name="login"]');
    await setTime()
    await page.keyboard.press('Escape');
        console.log('Escape')
    // await page.close();
    // await browser.close();
  } catch (e) {
    console.log(e);
  }
}

crawler()

 


 

waitForResponse 응답 대기 활용방법

 

 

로그인을 성공하면 아래와 같이 권한 페이지 설정을 하기 위해 dim처리가 되어 있습니다

dim처리를 없애보도록 하겠습니다

 

 

 

 

로그인을 클릭하게 되면 서버 호출을 시도하게 됩니다

그래서 page.waitForResponse()를 호출해서

웹 페이지에서 서버를 호출한 res.url()과 우리가 알고 있는 url주소를 비교하여

같으면 true값을 return 합니다

그리고 res값을 json()를 사용하여 Object형태로 받아올 수 있으며,

아래와 같이 접근이 가능합니다

 

 

 

웹크롤링 waitForResponse 함수 결과값

 

 

 

 

result.extensions.is_final에 접근을 하여 true일 경우

로그인 호출 후 페이지 랜더링 시간을 예방하기 위해 setTime()를 호출합니다

그리고 keyboard.press() 함수를 사용해서 Escape 즉 우리가 알고 있는 esc를 누르는 효과로 

dim처리를 해제할 수 있습니다 

 

사이드 바 공식문서 카테고리에 키보드를 누를 수 있는 인자 값으로 뭐가 있는지

링크를 걸어두도록 하겠습니다

 

 


 

 

로그인 아웃 태그 찾기 및 로그아웃하기

 

 

아래와 같이 빨간색 테두리를 클릭하게 되면 

class명을 확인할 수 있습니다

그럼 앞서 로그인 하기와 같이 선언된 함수를 사용해서 프로필 서브 창을 활성화합니다

 

 

 

웹크롤링-태그찾기2
웹크롤링 로그아웃 태그 찾기

 

 

 

태그를 찾기 위해서는 창에 태그를 확인해야 합니다

아래와 같이 불확실할 경우에는 특정 지역을 찾을 수밖에 없습니다

어차피 웹크롤링은 웹사이트 하나에 대해서 대응할 수밖에 없으니

아래와 같이 지정해 봅시다

서브 창에서 div를 찾아 전체 div태그를 찾습니다

그리고 > div:last-child > div를 해서 최하위 div태그를 찾아서 로그인 버튼을 클릭하게 합니다

 

 

 

 

웹크롤링-태그찾기3
웹크롤링 서브창 태그 찾기

 

 

 

서브 창이 활성화될 때까지 기다려서 그다음 로그아웃 메뉴를 클릭해야 합니다

 

그것을 구현하기 위해 서브 창을 활성화할 태그 첫 번째 인자 값과

활성화될 때까지 기다릴 두 번째 인자 값을 할당하여 호출합니다

 

 

그래서 클릭한 다음 아래와 같이 함수를 호출하여 

다음 태그를 기다리고 그다음 함수를 실행하게 됩니다

 

 

웹크롤링 서브창 활성화 대기

 

 

 

이렇게 웹 크롤링해서 로그인과 로그아웃 그리고 서버 응답 대기,

그리고 현재 페이지 내에서 클릭 이벤트 등을 이용하여 페이지가 변화될 때

기다리는 함수를 알아보았습니다

 

웹크롤링은 개발할 때 한눈에 확보여서 재밌기도 합니다

이런 것을 배워서 제가 하고자 하는 것에 더 좋은 시스템을 만들어서 활용해보도록 하겠습니다

 

이렇게 이번 포스팅은 마치도록 하겠습니다

오늘도 너무너무 고생했지만 그래도 보람이 있는 하루인 것 같습니다

 

이렇게 보람찬 생활을 한다면 언젠가는 이 대모험도 끝나지 않을까? 합니다

 

그럼 끝날 수 있다고 믿으면 파이팅합시다

그럼 다음 포스팅에 뵙겠습니다

 

 

 

 

 

 

 

 

 

728x90
반응형
728x90
반응형

이전 포스팅에서 웹 크롤링을 하여 스크린샷을 찍는 방법에 대해

풀 스크린과 어느 특정 영역, 또는 화면 전체 이렇게 3가지 방법으로

스크린샷 찍는 방법을 알아보았습니다

 

웹크롤링할 때 화면 비율을 적용하는 등 여러가지를 알아보았습니다

더 자세히 알고 싶으시다면 아래 링크를 클릭해 주시길 바랍니다

 

그리고 이전 포스팅에서 설명한 세팅엑셀파일 쓰기, 읽기

자세한 설명은 생략할 수 있으니 참고 하시길 바랍니다

 

 

2022.08.26 - [IT_Web/Nodejs] - 웹 크롤링 퍼펫티어 활용하여 스크린샷 이미지 폴더에 저장하기

 

웹 크롤링 퍼펫티어 활용하여 스크린샷 이미지 폴더에 저장하기

이전 포스팅 웹크롤링 이미지 다운로드 적용하기 퍼펫티어를 활용해 웹 크롤링을 하여 이미지 다운로드를 적용해보았다 navigator.userAgent에 대한 속성 값도 변경해보고, 이미지를 웹에서 다운받

tantangerine.tistory.com

 

 


 

인피니트 스크롤링이란?

 

먼저 인피니트 스크롤링이란 무엇일까요?

이 효과는 페이스북, 인스타그램 등 많은 사이트에서 사용을 하고 있는 방법입니다

사용자가 클릭을 하지않고 컨텐츠에 계속 집중할 수 있도록

스크롤링만으로 사용자에게 컨텐츠가 무한 노출하여

사이트 이탈율을 방지하는 효과가 있습니다

 

그래서 조회하는 버튼형식이 아닌 스크롤을 조작해서

어느 위치에 오면 컨텐츠를 조회하는 것입니다

 

그래서 이제는 웹크롤링할 때 스크롤을 조작해서 

이미지를 다운받는 방법 구현하려합니다

 

하지만 인피니트 스크롤링을 구현할 경우에는

싱글 페이지 어플리케이션을 활용하는 웹 사이트가 대부분입니다

 

그리고 리액트를 사용했다면 클라이언트 사이드 랜더링을 사용하기 때문에

웹크롤링이 쉽지않습니다

 

그래서 이런 경우에는 puppeteer를 사용하면 웹크롤링이 수월해집니다

 

그리고 웹 크롤링하기 적당한 웹 사이트인지 판단하기위해 

postman을 사용하여 개발하기 이전에 어느정도 판단이 가능합니다

 

 

아래의 빨간영역을 보시면 웹크롤링할 사이트로 요청을 보내면

html코드, 그리고 웹 크롤링하려는 사이트의 미리보기가 가능합니다

그래서 이미지를 보거나 코드를 보면서 선택자 접근이 얼마나 가능한지도 판단해서

웹크롤링을 시작할 수 있습니다

 

웹크롤링-스크롤링이미지
웹 크롤링에 적합한 사이트 알아보기

 


 

 

 


 

스크롤조작 해서 이미지 다운받기

 

웹 스크롤 대상 웹 사이트는 unsplash.com 입니다

 

 

이미지 다운받기 이전에

태그에 접근 하는 방법을 먼저 알아보고 코드 분석에 들어가겠습니다

 

 

웹크롤링-스크롤링2
웹 스크롤링 태그 접근 미리하기

 

지금 현재 위에서 보시면 두개의 태그가 있습니다

<div class="GIFah"> <img class="YV19w"> 두 개의 태그를 볼 수 있습니다

그래서 접근이 가능한지 아니면 필요없는 태그가 붙어서 오는지 선택자를 사용해서 미리 태그에 접근을 해봅니다

아래의 이미지와 같이 태그 접근이 가능한 것을 확인할 수 있습니다

 

 

웹크롤링-스크롤3
선택자로 태그 접근하기

 

 

 

 

그럼 이제 아래의 코드를 천천히 분석해보시길 바랍니다

 

아래에 코드를 보시면

브라우저와 페이지 영역을 설정하여 해상도를 높입니다

그리고 querySelectorALL을 활용해서 이미지의 부모 엘리먼트를 태그를 가져옵니다

그리고 태그로 forEach를 사용하여 자식 img태그에 접근하여

src에 접근하여 이미지 주소를 가져와 이미지 다운받는 것까지 구현하였습니다

 

상세한 설명은 아래에서 하겠습니다

 

const axios = require('axios')
const fs = require('fs');
const puppeteer = require('puppeteer');

fs.readdir('imges', (err) => {
  if(err) {
    console.log('imges 폴더가 없어 imges 폴더를 생성합니다.')
    fs.mkdirSync('imges')
  }
})

const crawler = async () => {
  try {
  const browser = await puppeteer.launch({
      headless: false,
      args:['--window-size=1920,1080']
    });
  const page = await browser.newPage();
  await page.setViewport({
    width: 1920,
    height:1080,
  });
  await page.goto('https://unsplash.com')
  let result = []
  while(result.length < 10) {
    const srcs = await page.evaluate(() => {
      window.scrollBy(0, 0)
      const imgs = []
      const imgEls = document.querySelectorAll('.GIFah')
      if (imgEls.length) {
        imgEls.forEach((y, i) => {
          let src = y.querySelector('img.YVj9w').src
          if(i < 10){
            if(src){
                imgs.push(src)
            }
            y.parentElement.removeChild(y);
          }
        });
      }
      window.scrollBy(0, 30)
      return imgs
    });
    result = result.concat(srcs)
    await page.waitForSelector('.GIFah')
    console.log('result', result.length, result)
  }
  result.forEach(async(imgUrl, i) => {
    if (imgUrl) {
      const imgInfo = await axios.get(imgUrl.replace(/\?.*$/,''), { 
        responseType: 'arraybuffer',
      })
      fs.writeFileSync(`imges/${new Date().valueOf()}.jpeg`, imgInfo.data)
    }
  })
  await page.close();
  await browser.close();
  } catch (e) {
    console.error(e)
  }
}

crawler();

 

아래의 같이 해상도를 크게 설정합니다

스크롤을 조작하는데 너무 해상도가 작으면

스크롤 위치가 너무 많이 이동을 하게되면

스크롤링으로 다음 컨텐츠 조회가 잘 되지않는 현상이 있으니 참고하시길 바랍니다

 

웹크롤링-스크롤링4
웹크롤링 화면 비율 조정하기

 

 

아래에서 선택자를 사용해서 태그에 접근합니다

그리고 그 태그로 이미지태그를 접근하여 이미지를 다운받습니다

그리고 중요한것은 현재 가지고 있는 태그의 부모 태그에 접근해서 모든 하위 태그를 삭제합니다

그렇게 되면 처음 가져온 image 주소를 저장하고나서 필요없게 된 태그를 삭제하여

다음 스크롤링 해서 가져온 이미지 관련 태그만 접근할 수 있다는 장점이 있습니다

 

 

웹크롤링-스크롤5
태그 접속해서 삭제및 이미지 주소가져오기

 

while문으로 특정 조건이 만족할 때까지 무한 루프가 실행됩니다

그리고 concat을 해서 가져온 이미지 주소를 

최종 결과 변수에 저장을 합니다

 

 

 

웹크롤링-스크롤링6
특정 조건을 허용하기 전까지 무한루프 돌기

 

 

 

그리고 forEach로 파일을 만들어서 확인할 수있습니다

window.scrollBy()로 스크롤릉 내리는 효과를 주어 다음 컨텐츠를 조회하게 합니다

그리고 최종 결과 변수 값에 배열이 계속저장되어

결국에는 특정조건이 맞지 않게되면

무한 루프에서 나와 파일을 만들고

브라우저 및 페이지를 종료합니다

 

 

오늘은 여기까지입니다

웹 크롤링을 사이드 프로젝트를 하게되면서 여러가지 응용할 수 있을것같습니다

그래서 조금 기대도 되고 다음에 사이드 프로젝트를 하는것을

포스팅해서 올리겠습니다

 

이렇게 공부를 하면서 사이드 프로젝트를 만들어가는 과정에

흥미를 붙여서 계속 공부하려는 의지를 붙여주는게 정말 중요한 것같습니다

 

우리는 아직 너무많은 이야기들이 남았으니까요

솔직히 개발자가들의 공부가 어디가 끝인지도 잘모르겠네요

그러니 흥미 잃지마시고 끝까지 파이팅 하시길바랍니다

 

우리 IT 대모험은 끝나지 않았으니까요!!

 

 

 

 

728x90
반응형
728x90
반응형

이전 포스팅 웹크롤링 이미지 다운로드 적용하기

퍼펫티어를 활용해 웹 크롤링을 하여 이미지 다운로드를 적용해보았다

navigator.userAgent에 대한 속성 값도 변경해보고,

이미지를 웹에서 다운받아 드라이버에 저장하는 것과

받아온 이미지 링크 주소를 엑셀에도 넣어보았습니다

 

이번 포스팅도 이어지는 부분이 있으니 확인하시길 바랍니다

그리고 조금 더 상세한 부분은 아래의 링크를 확인해주세요

 

 

2022.08.24 - [IT_Web/Nodejs] - 웹크롤링 이미지 다운로드 및 puppeteer 활용해 이미지 주소 엑셀에 입력하기

 

웹크롤링 이미지 다운로드 및 puppeteer 활용해 이미지 주소 엑셀에 입력하기

이전 포스팅에서 웹크롤링 Puppeteer 및 Promise 활용 방법 Node.js로 웹 크롤링할 경우 Puppeteer를 사용하면 정말 편하게 개발을 할 수 있습니다 evaluate() 함수를 활용하여 잠시 대기시간을 갖거나, 선택

tantangerine.tistory.com

 


 

Puppeteer 활용해서 웹 사이트 스크린샷 이미지 저장하기

 

웹 크롤링을 하기위한 세팅은 이전 포스팅에서부터 이어지고 있으니

관심 있으신 분은 #웹 크롤링 태그를 클릭해서 확인해주시길 바랍니다

 

우선 아래의 코드를 작성합니다

 

launch(), setViewport(), page.screenshot()

함수에 대해서 알아보겠습니다

 

const fs = require('fs');
const puppeteer = require('puppeteer');

const xlsx = require('xlsx');
const workbook = xlsx.readFile('xlsx/data.xlsx');
const ws = workbook.Sheets.Sheet1;
const xlsxRecords = xlsx.utils.sheet_to_json(ws)

fs.readdir('screenShot', (err) => {
  if(err) {
    console.log('screenShot 폴더가 없어 screenShot 폴더를 생성합니다.')
    fs.mkdirSync('screenShot') 
  }
})

const screenshotCrawler = async () => {
  try {
    const browser = await puppeteer.launch({
      headless: false,
      args:['--window-size=1920,1080']
    });
    console.log('첫번째: records', xlsxRecords)
    const page = await browser.newPage();
    await page.setViewport({
      width: 1920,
      height:1080,
    });
    await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36')
    for(const [i, r] of xlsxRecords.entries()) {
      await page.goto(r.링크);
      await page.screenshot({ 
        path: `screenShot/${r.제목}.png`, // 저장할 위치를 지정한다
        fullPage: false, // 웹 페이지의 스크롤의 밑 전체 페이지를 스크린샷할 수 있다
        clip: {
          x: 100,
          y: 100,
          width: 300,
          height: 300,
        }// 왼쪽 상단 모서리 (x, y), 너비(width), 높이(height)
      });// 그리고 fullPage와 clip은 같이 사용할 수 없다
      
      const setTime = await page.evaluate(async() => {
        const timeList = await [3975, 4547, 5231, 6397, 7894, 3394, 4206, 5147, 6932, 7430, 3561, 4896, 5877, 6407, 7133];
        const randomTime = await timeList[Math.floor(Math.random() * timeList.length)]
        await new Promise(r => setTimeout(r, randomTime))
        return randomTime
      })
      console.log('setTime', setTime)
    }
    await page.close();
    await browser.close();
  } catch (e) {
    console.error(e)
  }
}

screenshotCrawler();

 

 

우선 초기에 해상도를 적용하지 않을 경우

아래와 같이 브라우저는 큰 화면이 아닌 반 정도인 것을 확인할 수 있습니다

 

 

웹크롤링-스크린샷2
초기 브라우저 설정

 

 

아래의 코드에 보면 기존 코드에 args:['--window-size=1920,1080']를 추가하면 됩니다

그리고 1920, 1080은 자신의 모니터 해상도를 생각하시고 지정합시다

 

 

웹크롤링-스크린샷3
브라우저 해상도 지정하기

 

 

위의 코드를 적용하면 브라우저의 크기가 커진것을 확인할 수 있습니다

이제는 화면의 해상도를 키우면 브라우저만큼 크게 넓어질 것 같습니다

 

 

웹크롤링-스크린샷4
args 값 적용 화면

 

 

아래의 코드 setViewport() 함수를 적용합니다 

브라우저 해상도와 맞게 값을 지정합니다

 

 

웹크롤링-스크린샷5
화면 해상도 지정하기

 

 

아래와 같이 화면이 해상도에 맞게 커지게 되었습니다

이제는 스크린샷을 찍는 코드를 적용하도록 하겠습니다

 

 

웹크롤링-스크린샷6
setViewport() 적용 화면

 

 

 

 

 

 

아래의 코드를 확인해봅시다

clip에 값을 지정하면 어느 한 영역을 스크린샷하여 찍을 수 있습니다

그 영역은 지정된 웹 페이지의 x, y축에서

width, height를 지정하여 사각형 영역을 만들어 스크린샷을 찍습니다

 

 

 

웹크롤링-스크린샷7
스크린샷 적용 코드

 

위와 같이 코드를 적용하면 아래와 같이 

스크린샷을 찍게 되어 path위치에 저장하게 됩니다

 

 

웹크롤링-스크린샷8
스크린샷 clip 값 적용 화면

 

 

아래의 전체 화면에 clip영역이 어떻게 지정되는지 확인이 가능합니다

이제는 fullPagetrue값을 주면 어떻게 스크린샷을 찍는지 확인해보겠습니다

 

 

웹크롤링-스크린샷9
clip 값 영역 지정

 

 

fullPage값을 true로 지정하게 되면 아래와 같이 전체 화면을 찍게 됩니다

웹 페이지 전체를 찍는 것이니 유의해 주시길 바랍니다

그리고 한 가지 더 fullPage 값이 true일 경우 clip 값을 지정하면

에러가 발생하니 주의하시길 바랍니다

 

 

웹크롤링-스크린샷10
fullPage값이 true로 지정된 스크린샷 이미지

 

 

이렇게 웹크롤링에서 스크린샷을 찍어 보았습니다

간단하게 함수 3개로 구현하였습니다

 

웹크롤링 기능은 아직 많이 남았으니

조금만 참고 포스팅을 기대해주시고 

힘내시길 바랍니다

 

아직 IT 대모험은 끝나지 않았으니까요

그럼 파이팅 하시고

 

다음 포스팅에서 뵙겠습니다

 

728x90
반응형
728x90
반응형

통계 쿼리는 조금 어려울 수도 쉬울 수도 있을 것 같습니다

하지만 대부분의 사람들은 어렵다고 할 것입니다

저 또한 그렇게 느끼고 있고요

 

한번 같이 공부하면서 알아보도록 하겠습니다

 

 

결과셋을 하나의 행으로 데이터 조회하기

 

통계 쿼리의 가장 기본인 결과셋을 하나의 행으로 만드는 것을 우선 해보겠습니다

행 그룹에서 값을 가져와서 그룹당 단일 행의 열로 변환하려고 합니다.

예를 들어 각 부서의 사원수를 표시하는 결과 셋이 있다고 해봅시다

 

 

아래와 같은 결과 셋을 하나행으로 보이도록 출력을 다시 재구성하려고 합니다.

통계쿼리1
결과셋을 하나의행으로 만들어보자

 

이 방법은 통계 쿼리에 자주 사용하는 방법이며 

가장 기초적인 방법의 예문으로 만들려고 합니다

 

이것을 바탕으로 응용해서 적용해보시길 바랍니다

 

 

우선 테이블에 어떤 데이터가 들어있는지 한번 확인해봅시다.

 

SELECT deptno FROM emp;

 

아래와 같이 데이터가 들어있으며, 총 14개의 행이 있습니다

 

통계쿼리2
원본 테이블 데이터 정보

 

이 데이터를 각 부서의 사원수를 구하기 위해서는

아래와 같이 일반적으로 생각하게 될 것입니다

하지만 이 데이터는 한 행에 출력이 않아 통계 쿼리로는 부적합합니다

그래서 하나 행으로 조회하는 방법을 알아보는 것이 최우선입니다 

 

SELECT deptno, coount(*) AS CNT FROM EMP GROUP BY deptno;

 

 

하나의 행으로 출력하는 과정을 알아보도록 하겠습니다

아래의 코드를 분석해 봅시다

 

 

select deptno,
       case when deptno=10 then 1 else 0 end as deptno_10,
       case when deptno=20 then 1 else 0 end as deptno_20,
       case when deptno=30 then 1 else 0 end as deptno_30
  from emp
 order by 1

 

 

위의 코드로 아래와 같이 조회되는 것을 확인할 수 있습니다

위의 DEPTNO 컬럼은 가독성을 위한 값이니 참고하시기 바랍니다.

CASE WHEN THEN ELSE END를 활용해서 한 컬럼에 대해서 값을 출력합니다 

그 값은 간단합니다 DEPTNO가 10이면 1을 주고 나머지는 0을 부여하게 됩니다.

그리고 두 번째 컬럼은 DEPTNO가 20, 세번째 컬럼은 DEPTNO가 30으로

아래의 데이터가 조회됩니다  그러나 아직은 조금 미흡합니다

아직 행이 하나가 아니기 때문이지요

 

그럼 조금 수정해보겠습니다

 

통계쿼리3
case문으로 쿼리 조회

 

 

아래의 코드로 합계결과 값은 하나의 행으로 반환하였습니다

위의 결과 값에서 집계 함수 SUM을 사용하여 각 DEPTNO의 발생 횟수를 계산합니다

 

 

 select sum(case when deptno=10 then 1 else 0 end) as deptno_10,
       sum(case when deptno=20 then 1 else 0 end) as deptno_20,
       sum(case when deptno=30 then 1 else 0 end) as deptno_30
  from EMP;

 

아래와 같이 최종 결과가 출력됩니다 

 

 

통계쿼리4
최종 결과

 

 

하지만 아직 조금 부족합니다 그 이유는 한 테이블에서 CASE문을 전체를 적용해서 

시스템상 성능 부하가 올 수 있습니다 그리고 단순히 CASE 표현식을 합산하는 방법을 이기 때문입니다

지금은 데이터가 많이 없기 때문에 이 같은 방법을 해도 문제가 없지만 데이터가 몇만 건씩 넘어간다면

문제가 발생할 것입니다

다른 방법을 알아보도록 하겠습니다

 

 

select case when deptno=10 then empcount else null end as deptno_10,
  case when deptno=20 then empcount else null enD as deptno_20,
  case when deptno=30 then empcount else null end as deptno_30
from (
        select deptno, count(*) as empcount
          from emp
         group by deptno
     ) x;

 

 

아래의 코드를 자세히 보시면

처음에 COUNT 함수를 사용했던 것을 기억하실 것입니다

그 쿼리를 인라인 뷰로 사용하여 부서당 사원수를 생성합니다

그럼 그 결과 값은 아래와 같이 출력되는 것을 확인할 수 있습니다

 

이 방법은 인라인 뷰로 먼저 생성된 데이터가 3개의 행이므로

CASE문 실행할 경우 전체 행을 읽지 않고 3개의 행만으로

결과를 출력할 수 있습니다 

 

 

통계쿼리5
인라인 뷰 활용 예

 

그런 다음 MAX 함수로 열을 하나의 행으로 축소를 합니다

아래의 코드를 확인해 봅시다

 

select max(case when deptno=10 then empcount else null end) as new_deptno_10,
       max(case when deptno=20 then empcount else null end) as new_deptno_20,
       max(case when deptno=30 then empcount else null end) as new_deptno_30
from (
        select deptno, count(*) as empcount
          from emp
         group by deptno
     ) x;

 

결과 값은 아래와 같이 출력되는 것을 확인할 수 있습니다

 

 

통계쿼리6
성능강화 쿼리 적용 결과값

 

아래는 사례별로 통계 쿼리를 작성하는 것을 포스팅 연결하였습니다

그리고 마지막에는 통계쿼리 응용문제가 있으니

확인하셔서 공부하는데 도움이 되셨으면 합니다 감사합니다

 

 

2022.08.08 - [IT_Web/Oracle] - Oracle mybatis 활용 DECODE CASE GROUP BY ROLLUP 회원 통계 쿼리 월별 분기별 연도별

 

Oracle mybatis 활용 DECODE CASE GROUP BY ROLLUP 회원 통계 쿼리 월별 분기별 년도별

회원 월별 분기별 년도별 통계 쿼리 오늘은 회원별 통계 쿼리를 알아보도록 하겠습니다 mybatis를 활용해서 여러 조건문을 한번에 처리하는 쿼리를 작성하도록 하겠습니다 selectType을 프론트 단에

tantangerine.tistory.com

 

 

2022.08.09 - [IT_Web/Oracle] - oracle 회원 가입 증가 현황 DECODE 통계 쿼리 CASE, GROUP BY 활용

 

oracle 회원 가입 증가현황 DECODE 통계 쿼리 CASE, GROUP BY 활용

회원 가입 증가 현황 통계 쿼리 회원 가입 통계 쿼리를 만들어 보려고 합니다 저도 아직 익숙하지 않아서 조금 더 좋은 방법 있다면 댓글 남겨주세요~ 등록일(REG_DT)을 기준으로 신규회원 가입현

tantangerine.tistory.com

 

2022.08.10 - [IT_Web/Oracle] - oracle 상품 판매 통계 쿼리 union all case group by 활용

 

oracle 상품 판매 통계 쿼리 union all case group by 활용

상품 판매 통계 쿼리 오늘은 상품 판매 통계 쿼리를 만들어 보겠습니다 상품은 여러 종류가 있습니다 그것을 어떻게 구분할지는 스스로가 정해야겠지요 아래에서는 상품구분을 단순하게 알파

tantangerine.tistory.com

 


 

 


 

 

사례로 보는 통계 쿼리 문

* 아래의 문제는 쿼리문을 집중해서 보시고 이렇게도 쿼리를 작성할 수 있구나 하고

생각해보는 시간이 되었으면 합니다

 

 

 

 

HP라는 제품은 너무 많은 등급을 가지고 있어 제품별로 분석하기를 원하고, ‘LD’라는 제품은 등급의 수가 적고 중요하므로 등급별로 분석하기를 원하며, ‘PP’라는 제품은 다양한 등급을 가지고 있으나 ‘P530C’라는 등급은 전략적으로 관리하고자 하여 등급별로 분석하고, 나머지는 기타로 모아주기를 원한다고 생각하자

 

이런 경우에는 CASE와 GROUP BY를 활용하여 풀어보자

 

SELECT

  CASE

    WHEN 제품 = ‘HP’ THEN 제품

    WHEN 제품 = ‘LD’ THEN 등급 ,

    WHEN 등급 = ‘P530C’ THEN 등급

    ELSE ’기타‘

  END

  , SUM(총매출), SUM(총수량).....

FROM 매출테이블

WHERE 매출일자 LIKE ’9808%’

GROUP BY

  CASE

    WHEN 제품 = ‘HP’ THEN 제품

    WHEN 제품 = ‘LD’ THEN 등급 ,

    WHEN 등급 = ‘P530C’ THEN 등급

    ELSE ’기타‘

  END

 


 

SALE_HIST 테이블을 참조하여 ‘PENCIL’ 총판매금액과 01번 사업장의 총판매금액과

ERASER를 판매하고 있는 사업장의 총판매 금액을 출력하고 그 나머지를 기타로 묶어 총판매금액을 출력하시오

통계쿼리7
SALE_HIST 테이블 데이터 정보

 

SELECT

  CASE

    WHEN SALE_ITEM = 'PENCIL' THEN SALE_ITEM

    WHEN SALE_ITEM = 'ERASER' THEN SALE_SITE

    WHEN SALE_SITE = 01 THEN SALE_SITE

    ELSE '기타'

  END AS 특별관리항목

  , SUM(SALE_AMT) AS 총판매금액

FROM SALE_HIST

  GROUP BY

    CASE

      WHEN SALE_ITEM = 'PENCIL' THEN SALE_ITEM

      WHEN SALE_ITEM = 'ERASER' THEN SALE_SITE

      WHEN SALE_SITE = 01 THEN SALE_SITE

      ELSE '기타'

    END

 

통계쿼리8
상품별 총판매금액 통계쿼리

 

 


 

 

 

TEST100 테이블을 참조하여 C2에 해당하는 값의 비율을 구하여라

 

통계쿼리9
TEST100 테이블 데이터 정보

 

SELECT C1, C2

, ROUND(RATIO_TO_REPORT(C2) OVER(),2) * 100 AS PER

FROM TEST100

 

통계쿼리10
TEST100 상품별 비율

 


 

 

TEST35 테이블을 참조하여 KEY1은 부서라고 생각하고 KEY2의 A는 판매액 B는 매입액이라고 할 때

부서별 판매액과 매입액의 차에 대한 값(수익)을 구하고 차액의 비율을 구하시오

 

통계쿼리11
TEST35 테이블 데이터 정보

 

 

SELECT

  CASE WHEN KEY1 IS NOT NULL THEN KEY1 ELSE '합계' END AS KEY1

  ,  SUM(CASE WHEN KEY2 = 'A' THEN AMT ELSE 0 END) AS 판매액

  ,  SUM(CASE WHEN KEY2 = 'B' THEN AMT ELSE 0 END) AS 매입액

  ,  SUM(CASE WHEN KEY2 = 'A' THEN AMT ELSE -AMT END) AS 수익

  ,  ROUND(SUM(CASE WHEN KEY2='A' THEN AMT ELSE -AMT END)

                        / SUM(CASE WHEN KEY2='A' THEN AMT END)*100 ) AS PER

FROM (SELECT KEY1, KEY2, SUM(AMT) AMT

               FROM TEST35

               GROUP BY ROLLUP(KEY1),  KEY2

               )

GROUP BY KEY1;

 

 

통계쿼리12
부서 판매액 차액 비율

 


 

 

Temp를 이용한 문제를 풀어보자

Temp에 있는 직원들을 부서별 직급별로 SALARY 합을 보려 한다 이때 부서별로 직급들이 동일행에 나오도록

보여주고 부서별 전체 급여 그리고 PER컬럼의 전체 부서 급여 합계 비율을 구하여라

 

 

통계쿼리13
TEMP 테이블 데이터 정보

 

 

SELECT CASE WHEN DEPTNO IS NOT NULL THEN DEPTNO ELSE '합계' END DEPTNO

                 , SUM(A.부장) 부장, SUM(A.차장) 차장, SUM(A.과장) 과장

                 , SUM(A.대리) 대리, SUM(A.사원) 사원, SUM(A.수습) 수습, SUM(A.PER) 전체급여비율

                 , SUM(A.SAL) 부서별급여합계

FROM (SELECT DEPT_CODE DEPTNO, SALARY SAL

                               , SUM(CASE WHEN LEV = '부장' THEN SALARY ELSE 0 END) 부장

                               , SUM(CASE WHEN LEV = '차장' THEN SALARY ELSE 0 END) 차장

                               , SUM(CASE WHEN LEV = '과장' THEN SALARY ELSE 0 END) 과장

                               , SUM(CASE WHEN LEV = '대리' THEN SALARY ELSE 0 END) 대리

                               , SUM(CASE WHEN LEV = '사원' THEN SALARY ELSE 0 END) 사원

                               , SUM(CASE WHEN LEV = '수습' THEN SALARY ELSE 0 END) 수습

                               , ROUND(RATIO_TO_REPORT(SALARY) OVER (),2) * 100 AS PER

                               , SUM(SALARY)

              FROM TEMP

              GROUP BY DEPT_CODE, SALARY, LEV

              ) A

GROUP BY ROLLUP(DEPTNO)

ORDER BY DEPTNO

 

 

통계쿼리14
부서별 전체 급여 비율

 


 

 

TEST02 테이블의 자료를 이용해 최대 CRATE를 가지는 일자의 AMT와 최소 CREATE를 가지는 일자의 AMT를 읽어오는 문장을 출력하시오

 

통계쿼리15
TEST02 테이블 정보

 

 

SELECT distinct

  LAST_VALUE(AMT) OVER( ORDER BY CRATE

                                    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) MAX_VAL

  , FIRST_VALUE(AMT) OVER( ORDER BY CRATE

                                       ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) MIN_VAL

FROM TEST02

 

 

통계쿼리16
최대 최소 금액출력

 

                    


 

 

이렇게 통계 쿼리를 작성법을 알아보았습니다

조금이나마 도움이 되었으며 합니다

저도 통계 쿼리를 접할 때 많이 힘들었던 적이 있어서 이렇게 준비하게 되었습니다

그럼 앞으로 공부할 날이 너무나도 많기에 힘내시고 

같이 파이팅합시다

 

그럼 다음 포스팅도 기대해주세요!

 

728x90
반응형
728x90
반응형

이전 포스팅에서 웹크롤링 Puppeteer 및 Promise 활용 방법

 

Node.js로 웹 크롤링할 경우 Puppeteer를 사용하면 정말 편하게 개발을 할 수 있습니다

evaluate() 함수를 활용하여 잠시 대기시간을 갖거나,

선택자를 지정하여 태그에 대해 접근하여 값을 가져오거나 등 

여러 가지 작업을 간편하게 하였습니다

 

더 자세한 사항을 알고 싶으시다면 아래의 링크를 확인해주세요

 

 

2022.08.20 - [IT_Web/Nodejs] - nodejs 웹 크롤링을 Puppeteer 및 Promise 활용 완벽 파헤치고 CSV 파일 쓰기

 

nodejs 웹 크롤링을 Puppeteer 및 Promise 활용 완벽 파헤치고 CSV파일 쓰기

이전 포스팅에 대하여 이전 포스팅에서는 간단하게 웹 크롤링을 하는 방법을 알아보았습니다 axios와 cheerio를 사용해서 웹 크롤링을 해보았습니다 하지만 axios는 한계가 있습니다 싱글 페이지로

tantangerine.tistory.com

 

 

웹크롤링 이미지 다운로드 및 이미지 주소 엑셀에 입력하기

 

 

우선 이전 포스팅에서 계속 이어져서 작업을 진행하고있으니

이전 포스팅을 꼭 확인해주세요

 

아래의 코드를 한번 천천히 살펴보시길 바랍니다

그리고 세부 설명은 아래에서 이미지와 같이 진행하겠습니다

 

const axios = require('axios')
const fs = require('fs');
const puppeteer = require('puppeteer');

const xlsx = require('xlsx');
const add_to_sheet = require('./add_to_sheet');
const workbook = xlsx.readFile('xlsx/data.xlsx');
const ws = workbook.Sheets.Sheet1;
const xlsxRecords = xlsx.utils.sheet_to_json(ws)


fs.readdir('screenShot', (err) => { // readdir는 비동기로 이벤트 루프 속에서 작동하기 때문에 밑에 함수와는 관계가 없다
  if(err) {
    console.log('screenShot 폴더가 없어 screenShot 폴더를 생성합니다.')
    fs.mkdirSync('screenShot') // 프로그램이 처음 시작될때 한번만 시작할 때는 Sync는 사용해도 된다
  }
})


fs.readdir('poster', (err) => {
  if(err) {
    console.log('poster 폴더가 없어 poster 폴더를 생성합니다.')
    fs.mkdirSync('poster')
  }
})


const crawler = async () => {
  try {
    const browser = await puppeteer.launch({headless: false});
    console.log('첫번째: records', xlsxRecords)
    const page = await browser.newPage();
    await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36')
    add_to_sheet(ws, 'C1', 's', '평점')
    add_to_sheet(ws, 'D1', 's', '이미지 주소')
    for(const [i, r] of xlsxRecords.entries()) {
      await page.goto(r.링크);
      console.log('userAgent', await page.evaluate('navigator.userAgent'))
      const selectedTag = await page.evaluate(async () => {
        let score = ''
        let imgUrl = ''
        const scoreEl = await document.querySelector('.score.score_left .star_score')
        const imgEl = await document.querySelector('.poster img')
        if (scoreEl) {
          score = scoreEl.textContent;
        }
        if (imgEl) {
          imgUrl = imgEl.src;
        }
        return { score, imgUrl } 
      })

      if (!selectedTag?.score) {
        console.log('두번째 selectedTag 1: ',r.제목, `${i+1} 번째 영화 평점`, parseFloat(selectedTag.score.trim()));
        const newCell = 'C' + (i + 2)
        add_to_sheet(ws, newCell, 'n', parseFloat(selectedTag.score.trim()))
      } else {
        const scoreEl = await page.$('.score.score_left .star_score');
        if (scoreEl) {
          const selectedTag = await page.evaluate(tag => tag.textContent, scoreEl);
          if (selectedTag) {
            console.log('두번째 score 2: ', parseFloat(selectedTag.trim(), `${i+1} 번째 영화 평점`, parseFloat(selectedTag.trim())))
            const newCell = 'C' + (i + 2)
            add_to_sheet(ws, newCell, 'n', parseFloat(selectedTag.trim()))
          }
        }
      }
      // 이미지 주소로 요청을 받아서 이미지 데이터를 받는다
      // 이미지 데이터는 버퍼로 받는다 이buffer가 연속적으로 들어있는 자료구조가 arraybuffer라고 한다
      // 이미지 주소를 가져올때는 확인을 필히 해야하며 어떠한 규칙을 가지고 있는지 확인해야 한다 쿼리스트링을 없애는것이 답이 아니다
      if (!selectedTag?.imgUrl) {
        const imgInfo = await axios.get(selectedTag.imgUrl.replace(/\?.*$/,''), { // 정규표현식으로 쿼리스트링을 삭제한다
          responseType: 'arraybuffer',
        })
        fs.writeFileSync(`poster/${r.제목}.jpg`, imgInfo.data)
        console.log('두번째 imgUrl 2: ', selectedTag, `${i+1} 번째 영화 이미지 주소`, selectedTag);
        const newCell = 'D' + (i + 2)
        add_to_sheet(ws, newCell, 's', selectedTag)
      } else {
        const imgEl = await page.$('.poster img');
        if (imgEl) {
          const selectedTag = await page.evaluate(tag => tag.src, imgEl);
         
          if (selectedTag) {
            console.log('두번째 imgUrl 2: ', selectedTag, `${i+1} 번째 영화이미지 주소`, selectedTag);
            const newCell = 'D' + (i + 2)
            add_to_sheet(ws, newCell, 's', selectedTag)
            const imgInfo = await axios.get(selectedTag.replace(/\?.*$/,''), {
              responseType: 'arraybuffer',
            })
            fs.writeFileSync(`poster/${r.제목}.jpg`, imgInfo.data)
          }
        }
      }
      
      const setTime = await page.evaluate(async() => {
        const timeList = await [3975, 4547, 5231, 6397, 7894, 3394, 4206, 5147, 6932, 7430, 3561, 4896, 5877, 6407, 7133];
        const randomTime = await timeList[Math.floor(Math.random() * timeList.length)]
        await new Promise(r => setTimeout(r, randomTime))
        return randomTime
      })
      console.log('setTime', setTime)
    }
    await page.close();
    await browser.close();
    xlsx.writeFile(workbook, 'xlsx/xlsxRsult.xlsx')
  } catch (e) {
    console.error(e)
  }
}

crawler();

 

 

라이브러리를 우선 불러옵니다

세팅방법은 이전 포스팅을 참고해주시길 바랍니다

 

여러 개가 있으니 한번 확인해보세요!

 

라이브러리 불러오는 것은 그렇게 어려운 것이 없으니

설명은 생략하겠습니다

 

workbook.Sheets.Sheet1의 의미는 

제 엑셀 파일의 Sheet의 명이 Sheet1입니다

그래서 Sheet1의 데이터를 받아오게 됩니다

 

라이브러리-불러오기
필요 라이브러리 불러오기

 

 

 

 

 

이미지를 저장할 폴더가 있는지 없는지 체크해서 폴더를 생성합니다

fs.readdir은 비동기로 이벤트 루프 속에서 작동하기 때문에 밑에 crawler함수와는 관계가 없습니다

 

또 한, fs함수 중 Sync를 사용하면 안 된다고 알고 계시겠지만

프로그램이 처음 시작될 때 한 번만 실행할 함수라면 Sncy함수를 사용해도 무관합니다

 

 

 

폴더생성
폴더 생성

 

아래의 코드처럼 puppeteerlaunch()로 실행 준비를 하고

newPage()로 브라우저를 띄우게 됩니다

이때 setUserAgent()를 사용해서 나의 Agent정보를 지정할 수 있습니다

이 정보 값은 navigator.userAgent를 개발자 모드에서 콘솔 창에 적게 되면 노출되니 참고하세요

 

wssheet1의 데이터를 가져오며 형식이 들어있기 때문에

add_to_sheet()를 사용해서

엑셀 파일의 평점과, 이미지 주소를 먼저 적습니다

 

 

 

add_to_sheet()의 코드는 아래와 같습니다

설명은 생략하며 한번 분석해보시길 바랍니다!!

 

 

const xlsx = require('xlsx');

function range_add_cell(range, cell) {
  var rng = xlsx.utils.decode_range(range);
  var c = typeof cell === 'string' ? xlsx.utils.decode_cell(cell) : cell;
  if (rng.s.r > c.r) rng.s.r = c.r;
  if (rng.s.c > c.c) rng.s.c = c.c;

  if (rng.e.r < c.r) rng.e.r = c.r;
  if (rng.e.c < c.c) rng.e.c = c.c;
  return xlsx.utils.encode_range(rng);
}

module.exports = function add_to_sheet(sheet, cell, type, raw) {
  sheet['!ref'] = range_add_cell(sheet['!ref'], cell);
  sheet[cell] = { t: type, v: raw };
};

 

 

page.goto로 페이지 이동이 가능합니다

그리고 태그 접근을 하려면 두 가지 방법이 있습니다

우선 첫 번째 방법은 evaluate()에서 document접근이 가능하기에

querySelector()를 사용해서 태그에 접근해서 값을 가져옵니다

그 값들은 비구조화 할당으로 한 번에 객체로 return 합니다

 

 

evaluate함수-활용법
evaluate 함수의 활용법

 

위에서 받아온 값에서 score값이 있으면

바로 엑셀에 값을 적용합니다

두 번째 매개변수는 엑셀의 어디 부분에 값을 넣을지 지정하는 것입니다

세 번째 매개변수는 데이터의 자료형을 지정하는 것입니다

그래서 저는 C2부터 시작하며, 'n' number를 넣겠다고 지정한 것입니다

 

그리고 score값이 없으면

다시 두 번째 방법으로 받는 것을 적용해보았습니다

이 방법은 page.$() 활용해서 태그에 접근하는 element 값을 가져옵니다

 

그다음 evaluate() 함수를 사용해서 값을 가져와 할당합니다

 

 

 

태그-접근방법
두번째 태그 접근방법

 

 

이미지를 받기 위해서는 우선 axios.get으로 이미지 주소를 값으로 요청을 합니다

responseTypearraybuffer로 설정합니다

이미지 주소로 요청받은 데이터는 버퍼이며,

이 버퍼가 연속적으로 들어가 있는 자료구조가 arraybuffer입니다

 

그리고 엑셀에 이미지 링크 주소를 입력하고

이미지를 다운로드하여. jpg로 내 컴퓨터의 poster폴더에 저장합니다

 

 

 

이미지받기
이미지 받기

 

크롤링 로봇으로 보이지 않기 위해

여러 가지 타임을 배열로 넣어 setTime을 실행합니다

그래서 다음 페이지 작업의 시간 간격을 두어 진행하게 됩니다

 

 

잠시대기시긴설정
로봇으로 보이지않게 하기위한 함수

 

 

마지막 브라우저를 닫고 

엑셀 파일에 데이터를 넣은 정보 값을 할당하여

파일로 만들어서 종료합니다

 

엑셀파일생성
엑셀 파일 생성

 

 

콘솔 창에 노출된 여러 정보 값을 확인할 수 있으며

진행상황에서 확인된 setTimescore값 등을 확인할 수 있습니다

 

 

콘솔창-노출
콘솔창 정보값 노출화면

 

 

아래와 같이 파일 구조와

poster폴더에 jpg 파일이 생성된 것이 확인할 수 있습니다

 

 

이미지다운완료결과
이미지 생성된 파일들

 

 

엑셀 파일에 입력된 이미지 주소를 확인할 수 있습니다

 

 

 

이렇게 엑셀 쓰기와 웹 크롤링 작업을 알아보았습니다

여러 작업이 동반된 개발이라 개인적인 프로젝트를 해도 유용하다고 생각합니다

이것으로 무엇을 해볼까 생각 중입니다

아무튼 이렇게 공부를 끝내겠습니다

 

앞으로도 계속 공부를 해야 하는 입장에서 힘들겠지만

파이팅 하시길 바랍니다

 

그럼 다음 포스팅도 많은 관심 부탁드립니다

728x90
반응형

+ Recent posts

Powered by Tistory, Designed by wallel