0%

들어가기

개발프로젝트에 따라서 Node의 버전을 여러개 설치하고, 번갈아 가면서 사용해야 할 경우가 있다.

이럴때는 NVM(Node Version Manager)를 이용해서 한 개발환경에 어려개의 Node 버전을 설치해서, 원하는 Node 버전을 골라서 사용 할 수 있다.

일단 기존에 Node가 설치되어 있다면 Node를 제거하자.

NVM 설치

아래 경로로 이동해서 Windows용 nvm설치 파일을 다운로드 한다. nvm-setup.zip 파일을 다운로드 한다.

https://github.com/coreybutler/nvm-windows/releases

Windows10-nvm-install%20424344dc17ab48128597652eee74f8b6/Untitled.png

압축을 풀면 폴더 내부에 nvm-setup.exe 파일이 나온다. 해당 파일을 실행시키면 nvm이 설치가 된다.

Windows10-nvm-install%20424344dc17ab48128597652eee74f8b6/Untitled%201.png

윈도우 터미널에서 아래 명령어를 입력하면 설치된 NVM의 버전을 확인 할 수 있다.

1
nvm version

Windows10-nvm-install%20424344dc17ab48128597652eee74f8b6/Untitled%202.png

NVM을 통해 Node를 설치하고, 설치한 Node를 활성화 하기

아래 명령을 입력하면 nvm에 설치된 node의 리스트를 확인 할 수 있다.

1
nvm ls

Windows10-nvm-install%20424344dc17ab48128597652eee74f8b6/Untitled%203.png

지금은 NVM을 막 설치한 상태라서 Node 리스트가 보이지 않는다.

일단 현재 설치 할 수 있는 Node버전 리스트를 확인해보자. 아래 링크로 이동해보자.

https://nodejs.org/ko/download/releases/

Windows10-nvm-install%20424344dc17ab48128597652eee74f8b6/Untitled%204.png

위처럼 Node 버전들을 확인 할 수 있다.

아래처럼 nvm install v노드버전 형식으로 입력하면 해당 Node 버전을 다운로드 한다.

1
nvm install v15.11.0

Windows10-nvm-install%20424344dc17ab48128597652eee74f8b6/Untitled%205.png

nvm에서 특정 Node 버전 활성화 하기

1
nvm use 15.11.0

Windows10-nvm-install%20424344dc17ab48128597652eee74f8b6/Untitled%206.png

이렇게 nvm으로 node 버전을 설치하고 활성화 작업을 완료했다.

끝!!!

참고자료

[https://seunghyun90.tistory.com/52](

들어가기

React의 CRA를 사용해서 프로젝트를 생성하고, ant 디자인을 적용하고, dark 테마를 적용하는 예제를 진행하겠다.

주요한 내용은 아래 블로그 글을 참고했으며, 자세한 내용은 아래 링크를 참고하길 바란다.

https://jfelix.info/blog/dynamic-themes-in-ant-design-how-to-change-between-light-and-dark-theme

본인은 less 및 gulp에 대한 이해가 없다.

이 글의 목적은 대략적 맥락(ant 디자인의 동적 테마 변경에 관련)과 빠른 프로젝트 구축을 위한 글이다.

주의사항

node 버전 14이상에서 예제를 진행해야 한다.

본인은 webstorm 개발툴을 사용했다.(vscode를 사용해도 전혀 문제 없을 것이다.)

리액트 프로젝트 생성 및 Ant 디자인 적용

아래 명령으로 리액트 프로젝트를 생성한다.

1
npx create-react-app antd-swith-theme

react-cra-antd-darktheme%20efa153b7e9b7443c9b838c30ff14e9e1/Untitled.png

아래 명령으로 ant디자인과 ant디자인에 사용하는 icon 을 설치한다.

1
2
npm i atnd
npm install --save @ant-design/icons

react-cra-antd-darktheme%20efa153b7e9b7443c9b838c30ff14e9e1/Untitled%201.png

위 처럼 설치된 내용을 확인 할 수 있다.

ant 디자인을 사용하기 위한 준비는 완료 되었다.

Ant 디자인의 light, dark테마 switch 기능 추가하기

프로젝트의 src 폴더 아래 themes 폴더를 생성하고 그 아래 dark-theme.less, light-thmes.less 파일을 생성한다.

react-cra-antd-darktheme%20efa153b7e9b7443c9b838c30ff14e9e1/Untitled%202.png

dark-theme.less 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@import "~antd/lib/style/color/colorPalette.less";
@import "~antd/dist/antd.less";
@import "~antd/lib/style/themes/dark.less";

// These are shared variables that can be extracted to their own file
@primary-color: #00adb5;
@border-radius-base: 4px;

@component-background: #303030;
@body-background: #303030;
@popover-background: #303030;
@border-color-base: #6f6c6c;
@border-color-split: #424242;
@table-header-sort-active-bg: #424242;
@card-skeleton-bg: #424242;
@skeleton-color: #424242;
@table-header-sort-active-bg: #424242;

light-theme.less파일

1
2
3
4
5
6
7
@import "~antd/lib/style/color/colorPalette.less";
@import "~antd/dist/antd.less";
@import "~antd/lib/style/themes/default.less";

// These are shared variables that can be extracted to their own file
@primary-color: #00adb5;
@border-radius-base: 4px;

여기서 잠깐 위 두 파일을 기준으로 ant 디자인의 테마 변경을 위한 맥락을 설명하겠다.

당연히 dark-theme.less 파일은 dark 모드를 사용할 때 적용할 스타일 정보를 저장한다.

light-theme.less 파일은 light 모드를 사용할 때 적용할 스타일을 저장한다.

프로젝트 root 경로에 gulpfile.js 파일을 생성하자.

gulpfile.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const gulp = require('gulp')
const gulpless = require('gulp-less')
const postcss = require('gulp-postcss')
const debug = require('gulp-debug')
var csso = require('gulp-csso')
const autoprefixer = require('autoprefixer')
const NpmImportPlugin = require('less-plugin-npm-import')

gulp.task('less', function () {
const plugins = [autoprefixer()]

return gulp
.src('src/themes/*-theme.less')
.pipe(debug({title: 'Less files:'}))
.pipe(
gulpless({
javascriptEnabled: true,
plugins: [new NpmImportPlugin({prefix: '~'})],
}),
)
.pipe(postcss(plugins))
.pipe(
csso({
debug: true,
}),
)
.pipe(gulp.dest('./public'))
})

gulp라는 프론트엔드 task runner를 이용해서 gulpfile.js 를 실행시킨다.

gulpfile.js는 dark-theme.less, light-theme.less파일을 로드해서 css파일로 변환해서, public 폴더에 저장한다.

그리고 react-css-theme-switcher 라는 것을 사용해서 react에서 dark-theme.css와 light-theme.css 파일을 선택적으로 로드해서 사용한다.

glup 관련 의존성을 추가한다.

1
yarn add -D gulp gulp-less gulp-postcss gulp-debug gulp-csso autoprefixer less-plugin-npm-import

그리고 아래 postcss 도 추가한다.(내가 참고한 블로그에는 없지만, 본인의 경우 아래 의존성이 추가적으로 필요했다.)

1
npm i -D postcss

아래 명령으로 gulp를 이용해서 gulpfile.js 파일을 실행시킨다.

1
npx gulp less

public 폴더에 dark-theme.css, light-theme.css 파일이 gulp를 통해서 생성이 되었다.

만약 dark-theme.less, light-theme.less 파일을 본인의 입맛에 맞게 수정했다면, 위 npx gulp less 파일을 실행시켜, 새롭게 dark-theme.css, light-theme.css 파일을 재 생성 해 주어야 한다.

react-cra-antd-darktheme%20efa153b7e9b7443c9b838c30ff14e9e1/Untitled%203.png

다음은 react 에서 동적으로 css 파일을 변경해서 테마를 변경시켜주는 react-css-theme-switcher 를 설치하자.

1
yarn add react-css-theme-switcher

react에서 react-css-theme-switcher를 사옹해서 테마 변경하는 코드 작성

아래 코드에서는 react-css-theme-switcher 관련 코드와 toggleTheme 메소드만 주의 깊게 보면 된다.

index.css, app.css는 사실 신경쓰지 않아도 된다. 그냥 좀 화면을 예쁘게 만들어 주는 것 뿐이다.

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// index.js
import React from "react";

import ReactDOM from "react-dom";

import "./index.css";
import App from "./App";
import { ThemeSwitcherProvider } from "react-css-theme-switcher";

const themes = {
dark: `${process.env.PUBLIC_URL}/dark-theme.css`,
light: `${process.env.PUBLIC_URL}/light-theme.css`,
};

ReactDOM.render(
<React.StrictMode>
<ThemeSwitcherProvider themeMap={themes} defaultTheme="light">
<App />
</ThemeSwitcherProvider>
</React.StrictMode>,
document.getElementById("root")
);

index.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

min-height: 100vh;
}

.fade-in {
animation: fadeIn;
animation-duration: 0.6s;
}

@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from "react";
import "./App.css";
import { useThemeSwitcher } from "react-css-theme-switcher";
import { Switch, Input } from "antd";

export default function App() {
const [isDarkMode, setIsDarkMode] = React.useState();
const { switcher, currentTheme, status, themes } = useThemeSwitcher();

const toggleTheme = (isChecked) => {
setIsDarkMode(isChecked);
switcher({ theme: isChecked ? themes.dark : themes.light });
};

// Avoid theme change flicker
if (status === "loading") {
return null;
}

return (
<div className="main fade-in">
<h1>The current theme is: {currentTheme}</h1>
<Switch checked={isDarkMode} onChange={toggleTheme} />

<Input
style={{ width: 300, marginTop: 30 }}
placeholder="I will change with the theme!"
/>
</div>
);
}

app.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

min-height: 100vh;
}

.fade-in {
animation: fadeIn;
animation-duration: 0.6s;
}

@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

마지막 결과 확인

1
npm run start

react-cra-antd-darktheme%20efa153b7e9b7443c9b838c30ff14e9e1/antd-darktheme.gif

예제 github 경로

https://github.com/blog-examples/react-antd-darktheme.git

참고자료

antd 디자인 dark테마 적용

https://jfelix.info/blog/dynamic-themes-in-ant-design-how-to-change-between-light-and-dark-theme

antd 디자인 적용

[https://jeonghwan-kim.github.io/2018/10/13/ant-design-101.html](

테스트 환경

  • 인텔리J 얼티메이트
  • java8
  • springboot
  • gradle
  • junit4

오류시나리오

스프링부트 웹 프로젝트를 생성후 간단한 컨트롤러 테스트시 아래와 같은 오류 발생

문제점1. 한글로 만든 테스트 메소드명이 콘솔에서 깨져서 나온다.

문제점2. 테스트 코드 실행 시, 테스트 메소드를 인식하지 못한다.

Intellij%20springboot%20gradle%20test%E1%84%89%E1%85%B5%20No%20tests%20found%20f%20bb190dfb754c4433a1a58dcf176f8d7d/Untitled.png

해결방법

인텔리J에서 settings - Build, Execution, Deployment - Build Tools - Gradle 로 들어간다.

Intellij%20springboot%20gradle%20test%E1%84%89%E1%85%B5%20No%20tests%20found%20f%20bb190dfb754c4433a1a58dcf176f8d7d/Untitled%201.png

인텔리J에서 gradle로 프로젝트 생성시 디폴트로 아래처럼 설정 되어있다.

Build and run using: Gradle

Run tests using: Gradle

아래처럼 Gradle을 IntelliJ IDEA 로 변경해준다.

Intellij%20springboot%20gradle%20test%E1%84%89%E1%85%B5%20No%20tests%20found%20f%20bb190dfb754c4433a1a58dcf176f8d7d/Untitled%202.png

다시 테스트를 해보면 아래처럼 정상동작을 한다.

Intellij%20springboot%20gradle%20test%E1%84%89%E1%85%B5%20No%20tests%20found%20f%20bb190dfb754c4433a1a58dcf176f8d7d/Untitled%203.png

실습환경

  • java8
  • gradle
  • intellij
  • springboot
  • lombok

들어가기

인텔리J에서 gradle기반 springboot프로젝트에 lombok 셋팅을 해보자.

프로젝트 생성과정은 생략한다.

lombok 라이브러리 추가하기

build.gradle 파일에 아래 lombok 라이브러리를 추가한다.

1
implementation 'org.projectlombok:lombok'

gradle을 새로고침해서 lombok라이브러리를 받아준다.

아래처럼 lombok이 추가된 것을 확인 할 수 있다.

그리고 settigs - Plugins에 들어가서 Lombok 라이브러리를 설치하자.

난 이미 설치가 되어 있는 상태이다.

아마 최초 설치 시 설치가 왼료되면, intelliJ를 재시작 하게 될 것이다.

Lombok 플러그인을 설치하고, intelliJ를 재시작 한 뒤 Settings - Build, Execution, Deployment - Compiler - Annotation Processors 에 들어가서 Enable annotation processing 항목을 체크하고 저장하자.

이렇게 intelliJ에서 롬복 설정을 완료했다.

주의점

lombok 설정은 프로젝트 마다 설정해 줘야 한다.

플러그인 설치는 인텔리J에 설치되는 것이니 한번만 해도 되지만, 만약 새로운 프로젝트를 생성했다면 build.gradle에 추가한 lombok라이브러리 추가와 Enable annotation processing을 체크를 다시 해줘야 한다.

들어가기

windows10에서 cmd에서 mvn -v를 입력하면 maven의 버전이 나와야 하지만, 설치가 안된 상태에서는 아래처럼 나오게 된다.

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled.png

Maven 압축파일 다운받기

아래 사이트로 이동하자.

https://maven.apache.org/download.cgi#

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled%201.png

접속한 페이지에서 Download에 들어가서 아래로 좀 내려보면 Files를 확인 할 수 있다.

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled%202.png

현재 2020-10-17 기준으로 3.6.3 버전을 제공하고 있다.

해당 압축 파일을 다운로드 하자.

아래와 같이 zip 파일이 다운로드가 될 것이다. 원하는 경로에 압축을 풀어주면 된다.

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled%203.png

내 경우 C드라이브 아래 maven이라는 폴더를 생성해서 아래처럼 압축을 풀어 놓았다.

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled%204.png

Windows10에서 Maven 환경변수 설정

windows10에서 maven의 환경변수를 설정해주자.

제어판 → 시스템 → 환경변수로 이동하자.

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled%205.png

아래처럼 입력해주자.

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled%206.png

그 다음으로 시스템 변수에서 Path를 찾아 Maven의 bin 경로를 추가해주자.

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled%207.png

새로만들기를 눌러서 %MAVEN_HOME%\bin 을 입력해주자.

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled%208.png

Maven의 설정이 완료 되었다.

새로운 cmd 창을 열어서 mvn -v 명령어를 입력해 보면, maven이 정상동작 하는 것을 확인 할 수 있다.

windows10%E1%84%8B%E1%85%A6%20maven%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%20%E1%84%86%E1%85%B5%E1%86%BE%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%BC%20d866af6b66984e5b9de1e491282be900/Untitled%209.png

들어가기

ubuntu 18.04가 설치된 virtual box에 elasticsearch7을 설치해보자.

아직 공부가 부족해서, 자세한 내용을 모른체 연습용 elasticsearch를 설치하는 내용이다.

클러스터, 샤드등 설정에 대한 내용은 포함하고 있지 않다.

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled.png

실습

jdk 설치

먼저 elasticsearch를 설치하기 위해서는 jdk 설치를 해야 한다.

1
javac -version

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%201.png

먼저 jdk가 설치 되어있는지 확인해보자. 난 위처럼 설치가 안되어 있다.

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%202.png

1
sudo apt install openjdk-11-jdk

난 위처럼 open jdk 11로 설치를 진행했다.

아래처럼 jdk 가 설치 되었다.

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%203.png

Elasticsearch를 설치하자

https://iruwl.github.io/catetan/install-elasticsearch-7.0.0-on-ubuntu-18.04/

위 자료를 참고하여 실습을 진행했다.

Elasticsearch리파지토리 GPG 공개키를 import 한다.

1
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -

아래처럼 OK 가 나온다면 정상적으로 키를 import 한 것이다.

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%204.png

Elasticsearch 라파지토리를 등록한다. 아래처럼 현재 최신 버전인 7 버전 리파지토리를 등록했다.

1
sudo sh -c 'echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" > /etc/apt/sources.list.d/elastic-7.x.list'

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%205.png

다음 명령어로 elasticsearch를 설치하자.

1
2
sudo apt update
sudo apt install elasticsearch

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%206.png

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%207.png

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%208.png

위 처럼 설치가 정상적으로 진행 되었다.

엘라스틱서치를 동작시키기전에 간단한 설정파일을 집고 넘어가자.

  • /etc/elasticsearch/elasticsearch.yml
  • /etc/elasticsearch/jvm.options

elasticsearch.yml 파일에는 엘라스틱 서치의 전반적인 환경설정이 들어간다.

일단 아래처럼 클러스트명을 지정한다.

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%209.png

그리고 network 영역에서 network.host에서 해당 host의 ip를 등록한다.

network.host를 지정하지 않을경우, elasticsearch가 설치된 host pc에서 localhost로 접속이 가능하지만, 외부에서 elasticsearch에 접속 할 수 없다.

아래처럼 network.host에 명시적으로 host pc의 ip를 지정할경우 해당 ip를 사용해서 외부에서 elasticsearch에 접속 할 수 있다.

http.port 에서 포트를 지정한다.

discovery.seed_hosts를 아래 이미지 처럼 지정한다.(클러스트 노드 관련 설정인거 같다.)

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%2010.png

jvm.options 파일은 elasticsearch의 jvm heap 메모리 설정을 할 수 있다.

아래 이미지를 보면 기본으로 jvm heap이 1g 로 설정되어 있는 것을 확인 할 수 있다.

자신의 환경에 맞게 설정해주자.(초기 heap과 max heap의 사이즈를 동일하게 설정하는 것이 좋다고 한다.)

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%2011.png

기초적인 elasticsearch설정이 끝났다면, 서비스를 등록하고 실행해보자.

1
2
sudo systemctl enable elasticsearch.service
sudo systemctl start elasticsearch.service
1
sudo systemctl status elasticsearch.service

아래처럼 서비스가 정상 동작하는 것을 확인 할 수 있다.

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%2012.png

ubuntu의 포트 여는 것은 생략한다.

아래 명령어로 elasticsearch에 GET 요청을 날려보자.

1
curl -X GET '10.0.2.15:9200'

Ubuntu%2018%2004%20elasticsearch7%20install%20a053c074d29c440f9377bdbc67dbd811/Untitled%2013.png

정상적으로 elasticsearch가 응답하는 것을 확인 할 수 있다.

참고자료

https://iruwl.github.io/catetan/install-elasticsearch-7.0.0-on-ubuntu-18.04/

[https://kifarunix.com/install-elasticsearch-7-x-on-ubuntu-18-04-debian-9-8/](

들어가기

scrapy만으로는 크롤링 결과물에 동적인 이벤트를 발생해서 그 결과물을 크롤링 할 수 없다.

따라서 scrapy에서 이런 동적 크롤링을 지원하는 selenium을 연동해서 사용할 필요가 있다.

이 글은 scrapy로 간단한 프로젝트를 생성하고 seleinum을 연동하여 크롤링하는 예제를 만드는 과정을 기록한다.

scrapy의 모든 기능을 담고 있지 않다.(item, pipeline)

실습

실습환경

  • windows 10(64)
  • anaconda
  • python 3.7
  • scrapy
  • selenium
  • chrome dirver
  • pycham

scrapy 프로젝트 생성

anaconda 프롬프트에서 다음 명령으로 예제 scrapy 프로젝트를 생성하자.

1
scrapy startproject scrapy_with_selenium

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled.png

별 오류 없이 명령어가 실행되면, 명령어를 실행한 경로에서 해당 프로젝트라 폴더로 생성된다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%201.png

해당 폴더를 pycham으로 열어보자.

아래구조와 같이 프로젝트가 생성된다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%202.png

scrapy로 프로젝트를 생성했으니, 실습에 사용할 간단한 spider를 생성하자.

크롤링할 대상 페이지는 아래와 같다.

https://auto.naver.com/bike/mainList.nhn

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%203.png

네이버의 오토바이 검색 페이지를 크롤링 실습으로 사용해 보겠다.

spider 생성

1
scrapy genspider naverbike auto.naver.com/bike/mainList.nhn

위 명령어로 네이버 오토바이 페이지를 크롤링할 spider를 생성하자.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%204.png

명령어를 실행하면 아래처럼 spider 파일이 자동으로 생성된다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%205.png

여기서 기초적일 설정이 필요하다.

settings.py 파일에서 ROBOTSTXT_OBEY = False로 변경하자.

해당설정은 웹 서버가 크롤링을 거부해도 강제로 진행하는 옵션이다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%206.png

그리고 앞서 생성한 naverbike.py 라는 스파이더의코드를 아래처럼 수정해주자.

scrapy 명령으로 생성한 spider 코드의 start_urls 리스트를 보면 url 맨 뒤에 슬래시가 있는데 아래처럼 제거해주자. 아래 이미지를 처럼, xxxxxx/mainList.nhn 으로 맨 뒤에 슬래시가 없어야 정상적으로 크롤링이 될 것이다.

그리고 parse 메소드에서 reposen.text를 출력해서 크롤링이 재대로 되는지 확인해보자.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%207.png

터미널에서 아래 명령어로 해당 spider를 동작시켜 크롤링 결과가 출력되는지 확인해보자

1
scrapy crawl naverbike

아래처럼 크롤링 결과가 정상적으로 출력이 되는 것을 확인 할 수 있다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%208.png

지금 까지 한 작업은 순수하게 scrapy를 사용해서 크롤링을 해본 것이다.

이제 여기에 selenium을 연동시켜 보자.

scrapy에 seleinum연동하기

scrapy에 selenum을 연동하는 것은 생각 보다 간단하다.

scrapy 프로젝트를 생성할 때 같이 생성된 middleware 라는것에 적용하면 된다

요약

  • download middleware에 selenium설정과 selenium크롤링 코드를 작성한다.
  • spider에서 selenium을 설정한 download middleware를 사용한다고 옵션으로 지정해준다.
  • 해당 spider를 크롤링하게 되면, selenium으로 크롤링 하게 된다.

scrapy의 middleware란?

미들웨어란 보통 흐름, 처리 과정중 중간에 삽입되어 무언가를 처리하는 것을 말하는데, scrapy 의 middleware도 그런 역할이다.

앞서 생성한 scrapy 프로젝트의 디렉토리를 보면 아래처럼 middleware.py라는 파일이 자동으로 생성되어 있다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%209.png

파일 안을 보면 기본적으로 프로젝트명SpiderMiddleware라는 클래스가 있다.

그리고 좀 아래로 내려가다 보면 프로젝트명DownloadMiddleware라는 클래스가 있다.

이렇게 SpiderMiddleware, DownloadMiddleware라는 두개의 클래스가 디폴트로 생성된다.

일단 실습에 앞서 이 미들웨어들에 대해서 간단하게 알아보자.

scrapy의 미들웨어를 설명하기 전에 scrapy의 동작에 대해서 살짝 맛을 보자.

scrapy의 동작 과정설명

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2010.png

아래는 document의 원문이다. 내 짧은 영어로 발번역을 첨부한다.

  1. The Engine gets the initial Requests to crawl from the Spider.
    [우리가 spider에게 크롤링을 명령하면(ex:scpay crawl naverbike) spider는 Engine에게 request를 보낸다. ]
  2. The Engine schedules the Requests in the Scheduler and asks for the next Requests to crawl.
    [Engine은 spider로 부터 받은 최초의 request를 받아서 scheduler에 전달한다. scheduler는 일종의 크롤링 request를 저장하고 여유가 되면 동작시키는 일종의 이벤트큐와 같은 역할을 하는 것 같다. ]
  3. The Scheduler returns the next Requests to the Engine.
    [Scheduler가 다시 request를 Engine에게 전달한다.]
  4. The Engine sends the Requests to the Downloader, passing through the Downloader Middlewares (see [process_request()](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#scrapy.downloadermiddlewares.DownloaderMiddleware.process_request)).
    [Engine이 Request를 Downloader Middlewares를 거쳐, Downloader에게 전달한다. ]
  5. Once the page finishes downloading the Downloader generates a Response (with that page) and sends it to the Engine, passing through the Downloader Middlewares (see [process_response()](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#scrapy.downloadermiddlewares.DownloaderMiddleware.process_response)).
    [Downloader는 크롤링할 웹 페이지를 가져와서 그것을 Response을 생성한다. 그리고 이걸 다시 Downloader Middleware를 거쳐 Engine에게 전달한다.]
  6. The Engine receives the Response from the Downloader and sends it to the Spider for processing, passing through the Spider Middleware (see [process_spider_input()](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#scrapy.spidermiddlewares.SpiderMiddleware.process_spider_input)).
    [Engine이 Downloader로 부터 크롤링의 결과물을 담은 Response를 받는다. 그리고 이걸 다시 Spider Middleware를 거쳐서, Spider에게 보낸다.]
  7. The Spider processes the Response and returns scraped items and new Requests (to follow) to the Engine, passing through the Spider Middleware (see [process_spider_output()](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#scrapy.spidermiddlewares.SpiderMiddleware.process_spider_output)).
    [Spider는 engine으로 부터 받은 크롤링 결과물 Response를 처리하고, Engine에게 보낼 새로운 Request(새로운 크롤링을 위한)와 추출된 item(크롤링 결과물을 의미있는 데이터 단위로 추출)]
  8. The Engine sends processed items to Item Pipelines, then send processed Requests to the Scheduler and asks for possible next Requests to crawl.
    [Engine은 추출된 아이템(크롤링에서 추출한 데이터)를 item PipeLine에게 보낸다. Pipeline은 크롤링된 데이터를 후반 처리하는 녀석이다. 여기서 일단 크롤링 한번이 완료된 것이다.
    Engine은 spider에게 받은 Request를 다시 scheduler에제 전달하고 또 다른 크롤링의 절차가 시작된다.
    ]
  9. The process repeats (from step 1) until there are no more requests from the Scheduler.

위 scrapy의 크롤링 동작 과정 중 SpiderMiddleware와 DownloadMiddleware가 언급 되었다.

scrapy에 selenium을 연동하기 위해서는 크롤링할 웹 페이지에서 크롤링 결과를 받아올 때, 크롤링 방법에 개입을 해야 한다. 그 부분이 Download Middleware 이다.

아래 DownloadMiddleware에 selenium 을 적용해보자.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2011.png

middlewares.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
from scrapy import signals
from scrapy.http import HtmlResponse
from scrapy.utils.python import to_bytes

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from time import sleep

class ScrapyWithSeleniumSpiderMiddleware(object):

@classmethod
def from_crawler(cls, crawler):
print("this is ScrapyWithSeleniumSpiderMiddleware from_crawler ===================")
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s

def process_spider_input(self, response, spider):
return None

def process_spider_output(self, response, result, spider):
for i in result:
yield i

def process_spider_exception(self, response, exception, spider):
pass

def process_start_requests(self, start_requests, spider):
for r in start_requests:
yield r

def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)

class ScrapyWithSeleniumDownloaderMiddleware(object):

@classmethod
def from_crawler(cls, crawler):
print("this is ScrapyWithSeleniumSpiderMiddleware from_crawler +++++++++++++++++++++++")
middleware = cls()
crawler.signals.connect(middleware.spider_opened, signals.spider_opened)
crawler.signals.connect(middleware.spider_closed, signals.spider_closed)
return middleware

def spider_opened(self, spider):
CHROMEDRIVER_PATH = 'C:\dev_python\Webdriver\chromedriver.exe'
WINDOW_SIZE = "1920,1080"

chrome_options = Options()
# chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument(f"--window-size={WINDOW_SIZE}")

driver = webdriver.Chrome(executable_path=CHROMEDRIVER_PATH, chrome_options=chrome_options)
self.driver = driver

def spider_closed(self, spider):
self.driver.close()

def process_request(self, request, spider):
self.driver.get(request.url)

# scrapy에서 셀레니움을 연동해서 사용할경우. 셀레니움의 동적인 크롤링 코드는 여기 미들웨어에서 작성해야 할것 같다.
# headless 옵션을 끄고 아래 결과가 동작하는지 보자. 동작을 확인했다.
bikeCompanyAllBtn = self.driver.find_element_by_css_selector(
"#container > div.spot_main > div.spot_aside > div.tit > a")
bikeCompanyAllBtn.click()

body = to_bytes(text=self.driver.page_source)
sleep(5)
return HtmlResponse(url=request.url, body=body, encoding='utf-8', request=request)


def process_response(self, request, response, spider):
return response

def process_exception(self, request, exception, spider):
pass

위 코드에서 ScrapyWithSeleniumDownloaderMiddleware 클래스에 Selenium을 적용하는 코드들이 추가 되었다. ScrapyWithSeleniumSpiderMiddleware는 최초 생성된 상태로 수정하지 않았으니 신경쓰지 않아도 된다.

ScrapyWithSeleniumDownloaderMiddleware 에서 중점적으로 볼 부분은, spider_openedprocess_request 메소드이다.

  • spider_opened : Selenium의 기초 설정을 한다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2012.png

  • process_request: Downloader로 받은 크롤링의 결과물을 Selenuim으로 구체적인 크롤링 처리를 한다. 위 예제에서 특정 앵커 태그를 찾아서 click 이벤트를 발생 시켰다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2013.png

그리고 아래 부분은 selenium에서 크롤링한 결과물을 바이트로 변환해서 return 하게 된다. 이 결과물은 결과적으로 spider로 보내지게 된다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2014.png

spider 코드에서 해당 위의 download middleware를 사용하겠다고 설정을 해주자.

naverbike.py (spider)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import scrapy

class NaverbikeSpider(scrapy.Spider):
name = 'naverbike'
allowed_domains = ['auto.naver.com/bike/mainList.nhn']
start_urls = ['http://auto.naver.com/bike/mainList.nhn']
custom_settings = {
'DOWNLOADER_MIDDLEWARES': {
'scrapy_with_selenium.middlewares.ScrapyWithSeleniumDownloaderMiddleware': 100
}
}

def parse(self, response):
print(response.text)

custom_settings 부분이 DOWNLOADER_MIDDLEWARES를 어떤 것을 사용할지 지정하는 부분이다.

parse 부분에서는 간단하게 전달받은 respose의 내용물을 출력한다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2015.png

scrapy에 selenium을 연동하는 셋팅은 모두 끝이 났다.

이제 naverbike.py spider로 크롤링을 해보자.

위 download middleware에 selenium의 동작은 아래와 같다.

아래 페이지를 일단 크롤링 하고 전체 제조사 엘리멘트를 찾아서 클릭한다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2016.png

클릭한 다음에 나오는 페이지가 결론적으로 크롤링 되는 것이다.(아래화면)

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2017.png

scrapy프로젝트에서 터미널에서 아래 명령어를 실행하자.

1
scrapy crawl naverbike

아래처럼 selenium이 동작하면서, chrome web driver가 동작하고 크롤링이 진행되는 것을 확인 할 수 있다.(일부로 제대로 동작하는지 확인을 위해 selenium headless 옵션을 제거했다.)

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2018.png

재밌는 점은 크롤링이 종료 되면 자동으로 chrome web dirver창이 종료된다.(selenium에서는 별도의 quit 명령어를 호출하지만, scrapy에서는 그럴 필요가 없었다.)

끝!!!

마무리

scrapy를 정확히 모르는 입장에서, 단순히 scrapy에 selenium을 끼얹어 크롤링을 하는 예제를 만들어 보았다.

인터넷을 찾아보니 애시당초 scrapy와 selenium을 합쳐서 만든 프로젝트도 있는걸 보니, 그런것들을 활용해보는 것도 좋아 보인다.

위 예제 소스 github 주소

https://github.com/blog-examples/scrapy-with-selenium

참고자료

위 예제의 근간

https://heodolf.tistory.com/13

middelware 와 download middelware 의 차이점

https://stackoverflow.com/questions/17872753/what-is-the-difference-between-scrapys-spider-middleware-and-downloader-middlew

scrapy 와 selenium을 연동한 프로젝트가 존재한다.

https://github.com/clemfromspace/scrapy-selenium

scrapy 아키텍쳐 번역

http://sooyoung32.github.io/dev/2016/02/06/scrapy-architecture.html

scrapy structure

https://docs.scrapy.org/en/latest/topics/architecture.html

들어가기

저번에 anaconda에 scrapy를 설치 했었다. (windows와 호환성에 문제가 있는지 좀 고생을 했지만…

이번엔 scrapy로 간단한 프로젝트를 만들고 크롤링 실습을 해볼까 한다.

scrapy는 단순한 라이브러리가 아니라, 프레임 워크이다.

scrapy를 사용하기 위해서는 scrapy 기반 프로젝트를 생성해야 한다.

프레임워크 답게 cli 명령어로 프로젝트 생성, spider 생성, 크롤링 등을 지원한다.

실습

이 실습에서는 scrapy 프로젝트를 생성하고 간단한 spider를 생성해서 크롤링을 진행 하겠다.

실습환경

  • windows10(64)
  • anaconda(32)
  • conda 버전 4.8.3
  • scrapy 버전 1.6.0

scrapy로 프로젝트를 생성하자.

일단 conda 프롬프트를 열어서 아래 명령어로 scrapy 프로젝트를 생성한다.

1
2
scrapy startproject scrapy_example
문법: scrapy startproject 프로젝트명

주의 할점은 프로젝트 명으로 하이픈을 사용할 수 없다. (언더바는 사용이 가능)

scrapy%20952e11b67d354d7689155512461fde77/Untitled.png

위처럼 간단하게 scrapy 프로젝트를 생성 할 수 있다.

프로젝트가 생성되면 파이참으로 해당 프로젝트를 열어보자.

나는 개발도구로 파이참을 사용하겠다.

아래처럼 자동으로 프로젝트 폴더구조가 생성된 것을 확인 할 수 있다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%201.png

여기서 잠깐 파이참의 장점을 말하자면, 일반 cmd, gitbash에서는 conda 명령어가 path로 지정되지 않으면 conda 명령어를 사용 할 수 없지만, 파이참에서 인터프리터로 conda를 지정할 경우 파이참의 터미널에서 별도의 path 설정없이 conda 명령을 사용 할 수 있다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%202.png

다시 돌아와서, scrapy로 생성된 프로젝트의 폴더를 살펴보자.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%203.png

특의한 점은 프로젝트 루트 경로 아래, 프로젝트명과 동일한 폴더가 하나 더 추가로 생성되고, 그 아래 아래 디렉토리에 scrapy 프레임워크 디렉토리가 자동으로 생성된다.

  • spiders 폴더 : spider란 scrapy프레임워크에서 주체적으로 크롤링을 하는 일종의 봇과 같은 ㄴ녀석이다. 예를 들어서 내가 구글을 크롤링하고 싶으면 구글 스파이더를 생성해서 크롤링을 시키고, 또 네이버를 크롤링 하고 싶다면 네이버 스파이더를 생성해서 크롤링을 시키는 구조가 된다.
  • init.py : 엔트리 포인트
  • items.py : 크롤링한 데이터 어떻게 정형화 하고 저장할지에 대한 코드.
  • middelwares.py : node express 미들웨어처럼 크롤링 과정에서, 다양한 부가기능을 미들웨어라는 형태로 사용한다.
  • pipelines.py : 크롤링 이후 검색된 데이터를 어떻게 처리할지 코드.
  • settings.py : 프로젝트 설정 파일

Spider 를 만들자.

scrapy 에서 크롤링을 하는 실제 크롤러는 이 spider이다.

테스트로 https://auto.naver.com/bike/mainList.nhn 네이버 바이크 페이지를 크롤링 해도록 하겠다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%204.png

1
2
scrapy genspider naverbike auto.naver.com/bike/mainList.nhn
문법: scrapy genspider 스파이더이름 크롤링할url(http, https 프로토콜은 생략하자)

터미널에서 위 명령어롤 사용해서 naverbike라는 스파이더를 생성하자.

아래처럼 spiders 폴더에 naverbike.py 라는 파일이 자동으로 생겼다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%205.png

1
2
3
4
5
6
7
8
9
10
11
# -*- coding: utf-8 -*-
import scrapy

class NaverbikeSpider(scrapy.Spider):
name = 'naverbike'
allowed_domains = ['auto.naver.com/bike/mainList.nhn']
start_urls = ['http://auto.naver.com/bike/mainList.nhn/']

def parse(self, response):
print(response.text)
# pass

스파이더 소스를 보면 생성할때 입력된 url이 크롤링 대상으로 셋팅이 되어 있다.

parse 메소드가 실제 해당 스파이더가 동작하면서, 크롤링한 결과를 파싱해주는 메소드이다

위처럼 기존의 pass를 주석처리하고 print문으로 response.text를 출력하게 코드를 수정하자.

그리고 다음 명령어로 해당 스파이더를 동작 시키자.

1
2
scrapy crawl naverbike
문법: scrapy crawl 스파이이더명

내가 예상하기로는 해당 웹 페이지의 html 태그들이 주르륵 나와야 되는데 이상한, 로그 정보만 찍힌다.

내용을 보니 naver의 robot.txt에 의해서 응답을 못 받은것 같다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%206.png

scrapy crawl 명령어 리턴 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
2020-06-17 21:14:41 [scrapy.utils.log] INFO: Scrapy 1.6.0 started (bot: freerider_crawler)
2020-06-17 21:14:41 [scrapy.utils.log] INFO: Versions: lxml 4.5.0.0, libxml2 2.9.9, cssselect 1.1.0, parsel 1.5.2, w3lib 1.21.0, Twisted 20.3.0, Python 3.7.6 (default, Jan 8 2020, 16:
21:45) [MSC v.1916 32 bit (Intel)], pyOpenSSL 19.1.0 (OpenSSL 1.1.1d 10 Sep 2019), cryptography 2.8, Platform Windows-10-10.0.18362-SP0
2020-06-17 21:14:41 [scrapy.crawler] INFO: Overridden settings: {'BOT_NAME': 'freerider_crawler', 'NEWSPIDER_MODULE': 'freerider_crawler.spiders', 'ROBOTSTXT_OBEY': True, 'SPIDER_MODUL
ES': ['freerider_crawler.spiders']}
2020-06-17 21:14:41 [scrapy.extensions.telnet] INFO: Telnet Password: 683edf7aaadefaed
2020-06-17 21:14:41 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.logstats.LogStats']
2020-06-17 21:14:41 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2020-06-17 21:14:41 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
'scrapy.spidermiddlewares.referer.RefererMiddleware',
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2020-06-17 21:14:41 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2020-06-17 21:14:41 [scrapy.core.engine] INFO: Spider opened
2020-06-17 21:14:41 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2020-06-17 21:14:41 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2020-06-17 21:14:42 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (301) to <GET https://auto.naver.com/robots.txt> from <GET http://auto.naver.com/robots.txt>
2020-06-17 21:14:42 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://auto.naver.com/robots.txt> (referer: None)
2020-06-17 21:14:42 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET http://auto.naver.com/bike/mainList.nhn/>
2020-06-17 21:14:42 [scrapy.core.engine] INFO: Closing spider (finished)
2020-06-17 21:14:42 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/exception_count': 1,
'downloader/exception_type_count/scrapy.exceptions.IgnoreRequest': 1,
'downloader/request_bytes': 446,
'downloader/request_count': 2,
'downloader/request_method_count/GET': 2,
'downloader/response_bytes': 500,
'downloader/response_count': 2,
'downloader/response_status_count/200': 1,
'downloader/response_status_count/301': 1,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2020, 6, 17, 12, 14, 42, 285564),
'log_count/DEBUG': 3,
'log_count/INFO': 9,
'response_received_count': 1,
'robotstxt/forbidden': 1,
'robotstxt/request_count': 1,
'robotstxt/response_count': 1,
'robotstxt/response_status_count/200': 1,
'scheduler/dequeued': 1,
'scheduler/dequeued/memory': 1,
'scheduler/enqueued': 1,
'scheduler/enqueued/memory': 1,
'start_time': datetime.datetime(2020, 6, 17, 12, 14, 41, 909219)}
2020-06-17 21:14:42 [scrapy.core.engine] INFO: Spider closed (finished)

추축하건데, 아마도 네이버에서 scrapy라는 크롤러의 크롤요청을 파악하고 거부 한듯 하다.

크롤링 하려는 아래 페이지의 robot.txt 를 확인해보자.

auto.naver.com/bike/mainList.nhn

역시 모든 경로가 Disallow 이다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%207.png

어떻게 이것을 무시하고 크롤링 할 수 있을까?

scrapy로 robot.txt를 무시하고 크롤링 하는 방법

방법1, 방법2 가 있다 원하는 방법을 골라서 설정 하면된다.

방법1. scrapy crawl 명령시 옵션 추가

1
scrapy crawl --set=ROBOTSTXT_OBEY='False' naverbike

방법2. settings.py 에서 ROBOTSTXT_OBEY 설정을 False로 변경

scrapy%20952e11b67d354d7689155512461fde77/Untitled%208.png

방법1을 사용하며 크롤링 할때마다 옵션을 넣어 주어야 하지만, 방법2를 사용하면 프로젝트 전체 설정으로 적용 되기 때문에 scrapy crawl naverbike 명령을 사용하면 된다.

나는 방법 1을 사용해서 다시 크롤링 해 보았다.

하지만 이상하게 역시 크롤링이 재대로 되지 않았다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%209.png

이 문제 때문에 한참 해맸었는데, 문제는 아래와 같다

start_urls 맨 뒤에 슬래시가 있는데, 저 슬래시가 문제였다. 슬래시를 지우고 다시 크롤링 해보자.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2010.png

아래처럼 spider가 정상적으로 크롤링을 하는 것을 확인 할 수 있다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2011.png

위처럼 html이 크롤링 되는 것을 터미널에서 확인 할 수 있다.

음 스파이더가 자동으로 생성해준 코드라서, 설마 start_url의 마지막 슬래시가 크롤링이 안되는 원인이 될 것이라고는 생각조차 하지 못해 한참을 헤맸다.

scrapy shell을 이용해서 프롬프트에서 scrapy 크롤링을 할 수도 있다.

직접 코딩을 하기전에 테스트용도로 활용하면 좋을 방법이다.

1
scrapy shell 'https://auto.naver.com/bike/mainList.nhn'

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2012.png

위처럼 크롤링이 진행되다가 아래처럼 프롬프트가 멈춘다. 해당 부분에서 크롤링한 response를 가지고 shell에서 주피터노트북 처럼 테스트 코드를 작성해서 돌려 볼 수 있다.

그리고 빨간부분을 보면 해당 scrapy shell에서 호출 가능한 명령어 들을 보여준다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2013.png

response 객체를 이용해서 크롤링 된 데이터에 접근 할 수 있다.

response 객체 사용법

1
2
3
4
response.css(css 셀렉터 양식).get()
response.css('head > title').get() # 하나만 가져오기
response.css('head > title').getall() # 일치하는 거 다 가져오기

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2014.png

1
response.css('head > title::text').get()     # 하나만 가져오기, 태그는 제외

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2015.png

scrapy shell에서 위처럼 크롤링의 결과물인 response 객체를 이용해서 다양한 방법으로 크롤링 결과물을 간추리는 작업을 테스트 해 볼 수 있다.

네이버 자동차 바이크 페이지에서 전체 바이크 제조사 정보를 가져오고 싶은데, 마우스 클릭이 필요하다.

scrapy 만으로는 크롤링한 페이지에 마우스 클릭할 수 없다.

하지만 scrapy 크롤링 결과물에 셀레니움을 연동해서 처리가 가능해보인다.

https://stackoverflow.com/questions/36874494/simulating-a-javascript-button-click-with-scrapy

끝!!!

github 예제소스경로

https://github.com/blog-examples/python-scrapy-startproject/tree/master

들어가기

저번에 Selenium과 chrom web driver를 사용해서 naver bike에서 바이크 브랜드 정보를 크롤링 하는 예제를 진행 했다.

뭔가 아쉬운 점은, 크롤링을 할 때마다, web driver에 의해서 크롬 브라우저가 열린다는 것이다.

아래처럼 크롤링 스크립트를 돌리면 크롬 브라우저가 열린다.

selenium%20headless%203032c18b510e4d13bfb9c27587403f67/Untitled.png

물론 디버깅 코드를 작성하는 동안 재대로 동작하는 지 확인 하기에 좋고, 크롤링 마지막 코드에

driver.quit() 명령으로 해당 web driver 브라우저를 종료 시킬 수 있다.

하지만 내가 원하는 것은 아예 web driver 브라우저가 열리지 않고 크롤링이 되는 것을 원한다.

만약 실제 데몬서비스등으로 돌릴 때, 위 처럼 web driver 브라우저가 열릴 필요가 없기 때문이다.

아직 해보지 않아서 정확히는 모르겠지만, 만약 리눅스 서버에서 스크립트를 돌릴 경우 desktop환경이 아닌경우 오류가 나라 수도 있을 것 같다.(내 추축이다. 아닐수도 있다.)

Selenium에서는 headless 라는 기능을 통해 크롤링 할때 web driver의 브라우저가 화면에 직접 열리지 않고, 뒤에 숨어서 동작하게 할 수 있다고 한다.

한번 해보자.

Selenium headless 적용하기

일단 기존의 Selenium 예제 코드를 준비하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from selenium import webdriver
import time

chromedriver = 'C:\dev_python\Webdriver\chromedriver.exe'
driver = webdriver.Chrome(chromedriver)

driver.get('https://auto.naver.com/bike/mainList.nhn')

print("+" * 100)
print(driver.title)
print(driver.current_url)
print("바이크 브랜드 크롤링")
print("-" * 100)

# 바이크 제조사 전체 페이지 버튼 클릭
bikeCompanyAllBtn = driver.find_element_by_css_selector("#container > div.spot_main > div.spot_aside > div.tit > a")
bikeCompanyAllBtn.click()

time.sleep(3)

# 바이크 제조사 1번 페이지 진입해서 바이크 리스트 추출
allBikeCompanyElement = driver.find_elements_by_css_selector(
"#_vendor_select_layer > div > div.maker_group div.emblem_area > ul > li")

# 바이크 첫 페이지 크롤링
for item in allBikeCompanyElement:
bikeComName = item.find_element_by_tag_name("span").text
if (bikeComName != ''):
print("바이크 회사명:" + bikeComName)
ahref = item.find_element_by_tag_name("a").get_attribute("href")
print('네이버 자동차 바이크제조사 홈 sub url:', ahref)
imgUrl = item.find_element_by_tag_name("img").get_attribute("src")
print('바이크 회사 엠블럼:', imgUrl)

time.sleep(3)

# 바이크 제조사 리스트의 다음 페이지 버튼을 찾아서 클릭하자.

nextBtn = driver.find_element_by_css_selector(
"#_vendor_select_layer > div > div.maker_group > div.rolling_btn > button.next")
# 다음 바이크 제조사 페이지 버튼이 활성화 여부
isExistNextPage = nextBtn.is_enabled()

if (isExistNextPage == True):
print("다음 페이지 존재함=======================================>")
nextBtn.click()
allBikeCompanyElement = driver.find_elements_by_css_selector(
"#_vendor_select_layer > div > div.maker_group div.emblem_area > ul > li")
for item in allBikeCompanyElement:
bikeComName = item.find_element_by_tag_name("span").text
if (bikeComName != ''):
print("바이크 회사명:" + bikeComName)
ahref = item.find_element_by_tag_name("a").get_attribute("href")
print('네이버 자동차 바이크제조사 홈 sub url:', ahref)
imgUrl = item.find_element_by_tag_name("img").get_attribute("src")
print('바이크 회사 엠블럼:', imgUrl)

# 크롤링이 끝나면 webdriver 브라우저를 종료한다.
driver.quit()

위 코드에 headless 기능을 적용해보자. 방법은 아주 간단하다.

headless를 기능을 사용하겠다고, web driver에게 알려주기만 하면 된다.

headless 기능을 활성화 하는 코드는 아래와 같다.

1
2
3
4
5
6
# webdirver옵션에서 headless기능을 사용하겠다 라는 내용
webdriver_options = webdriver.ChromeOptions()
webdriver_options .add_argument('headless')

chromedriver = 'C:/dev_python/Webdriver/chromedriver.exe'
driver = webdriver.Chrome(chromedriver, options=webdriver_options )

위 크롤링 코드에 적용 하면 아래처럼 된다.

headless가 적용된 예제코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from selenium import webdriver
import time

# webdirver옵션에서 headless기능을 사용하겠다 라는 내용
webdriver_options = webdriver.ChromeOptions()
webdriver_options .add_argument('headless')

chromedriver = 'C:\dev_python\Webdriver\chromedriver.exe'
driver = webdriver.Chrome(chromedriver, options=webdriver_options )

driver.get('https://auto.naver.com/bike/mainList.nhn')

print("+" * 100)
print(driver.title)
print(driver.current_url)
print("바이크 브랜드 크롤링")
print("-" * 100)

# 바이크 제조사 전체 페이지 버튼 클릭
bikeCompanyAllBtn = driver.find_element_by_css_selector("#container > div.spot_main > div.spot_aside > div.tit > a")
bikeCompanyAllBtn.click()

time.sleep(3)

# 바이크 제조사 1번 페이지 진입해서 바이크 리스트 추출
allBikeCompanyElement = driver.find_elements_by_css_selector(
"#_vendor_select_layer > div > div.maker_group div.emblem_area > ul > li")

# 바이크 첫 페이지 크롤링
for item in allBikeCompanyElement:
bikeComName = item.find_element_by_tag_name("span").text
if (bikeComName != ''):
print("바이크 회사명:" + bikeComName)
ahref = item.find_element_by_tag_name("a").get_attribute("href")
print('네이버 자동차 바이크제조사 홈 sub url:', ahref)
imgUrl = item.find_element_by_tag_name("img").get_attribute("src")
print('바이크 회사 엠블럼:', imgUrl)

time.sleep(3)

# 바이크 제조사 리스트의 다음 페이지 버튼을 찾아서 클릭하자.

nextBtn = driver.find_element_by_css_selector(
"#_vendor_select_layer > div > div.maker_group > div.rolling_btn > button.next")
# 다음 바이크 제조사 페이지 버튼이 활성화 여부
isExistNextPage = nextBtn.is_enabled()

if (isExistNextPage == True):
print("다음 페이지 존재함=======================================>")
nextBtn.click()
allBikeCompanyElement = driver.find_elements_by_css_selector(
"#_vendor_select_layer > div > div.maker_group div.emblem_area > ul > li")
for item in allBikeCompanyElement:
bikeComName = item.find_element_by_tag_name("span").text
if (bikeComName != ''):
print("바이크 회사명:" + bikeComName)
ahref = item.find_element_by_tag_name("a").get_attribute("href")
print('네이버 자동차 바이크제조사 홈 sub url:', ahref)
imgUrl = item.find_element_by_tag_name("img").get_attribute("src")
print('바이크 회사 엠블럼:', imgUrl)

아래처럼 실행 하면, 이번에는 webdirver 브라우저 없이 크롤링이 진행되는 것을 확인 할 수 있다.

selenium%20headless%203032c18b510e4d13bfb9c27587403f67/Untitled%201.png

기타 webdirver 옵션

1
2
3
4
5
6
7
8
9
10
# 브라우저의 사이즈 지정(화면 사이즈에 따라서 동적으로 엘리멘트가 변하는 경우 필요할듯)
webdriver_options .add_argument('windows-size=1920x1080')
# 그래픽 카드 사용하지 않음
webdriver_options .add_argument('disable-gpu')
# http request header의 User-Agent 변조, 기본으로 크롤링 할 경우
# 이 정보는 크롬 헤드리스 웹드라이버로 넘어가므로 똑똑한 웹서버는
# 이 정보를 보고 응답을 안해줄수도 있는데 이걸 피하기 위해 변조할수있다.
webdriver_options .add_arguemnt('User-Agent: xxxxxxxxxxxxxxx')
# 사용자 언어
webdriver_options .add_arguemnt('lang=ko_KR')

마무리

seleinum과 webdriver의 조합 말고도, Phantomjs라는 것을 사용해서도 크롤링을 할 수 있고 위 처럼 headless또한 적용이 가능하다고 한다.

하지만 요즘 크롬이 워낙 대세라서, 유지보수가 될지 안될지 잘 모를 Phantomjs를 굳이 사용할 필요는 없어 보인다.

들어가기

python으로 크롤링을 할 때, 크롤링 대상인 웹 페이지에 동적인 동작을 곁들여서 크롤링을 할 수 있게 도와주는 라이브러리가 있는데, 그것이 selenium 이다.

selenium 장점

  • 웹 드라이버를 사용해서 웹 페이지를 동적크롤링 할 수 있다. 크롤링 결과의 특정 html 엘리먼트에 마우스 클릭를 발생시키거나 , input 엘리먼트에 텍스트를 채워넣기 등이 가능하다.

beautifulsoup, scrapy로는 특정 페이지에 동적인 이벤트를 가해서 크롤링을 할 수 없다.

예를 들어 10페이지 게시판을 크롤링 하려면, beautifulsoup에서는 페이지당 각각 url을 사용해서 크롤링 해야 하지만, selenium은 게시판 페이지를 크롤링 한 뒤, 다음 페이지 버튼을 찾아서 마우스 클릭하여 다음페이지로 이동해서 크롤링을 할 수 있다.

사실 selenium만으로 동적으로 크롤링을 하는 것은 아니다.

web driver라는 가상의 브라우저 프로그램(원래는 일종이 웹 테스트 도구라고 한다.)과 연동해서 위 기능을 구현 할 수 있다.

대충 아래와 비슷한 구조이다.

Selenium에 Chrom web driver를 연동해서 크롤링을 하면, Selenium는 가상 브라우저인 Chrom web driver를 통해 웹서버 응답을 받는다.

Selenium는 web driver를 제어 할 수 있으므로, 코드를 통해서 렌더링 결과물에 이벤트를 발생 시키고, 그에 대한 응답 결과를 받아 볼 수 있다.

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled.png

web driver종류가 여러개 있는것 같지만, 난 Chrome web driver를 사용할 예정이다.

Selenium을 windows10환경에 설치해보자.

실습환경

  • windows10(64bit)
  • anaconda(32bit)

Selenium 설치

셀레니움 설치 명령어

아래 두가지 방법 중 원하는 방법으로 설치하자.

1
2
pip install selenium     # pip 로 설치하는 방법
conda install selenium # conda로 설치하는 방법

난 conda를 사용해서 설치해보겠다.

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled%201.png

selenium을 설치는 간단하게 진행 될 것이다.

이제 앞서 언급한 webdirver를 설치해보자.

Chrome web driver 설치

브라우저에서 아래 페이지를 열자.

1
https://sites.google.com/a/chromium.org/chromedriver

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled%202.png

Current stable release링크를 클릭하자.

아래 페이지로 이동하는데, win32 버전 zip 파일을 클릭해서 다운 받자.

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled%203.png

압축파일을 다운로드 하고, 파일 압축을 풀어보면 아래처럼 chromedirver.exe라는 실행 파일이 보일 것이다.

크롤링을 할때 selenium에서 chromedirver.exe파일을 가져와서 사용하게 된다.

따라서 파이썬 스크립트에서 chromedirver.exe파일의 경로를 입력하게 된다.

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled%204.png

위 파일을 스크립트에서 반복적으로 사용하므로 관리하기 쉽게, C 드라이브에 옮겨놓다.

1
C:\dev_python\Webdriver\chromedirver.exe

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled%205.png

크롬 웹 드라이버 까지 셋팅을 마쳤다.

이제 간단한 예제를 이용해서 selenium으로 크롤링을 해보자.

Selenium 크롤링 실습(chromedriver사용)

일단 기본적인 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from selenium import webdriver

# selenium에서 사용할 웹 드라이버 절대 경로 정보
chromedriver = 'C:\dev_python\Webdriver\chromedriver.exe'
# selenum의 webdriver에 앞서 설치한 chromedirver를 연동한다.
driver = webdriver.Chrome(chromedriver)
# driver로 특정 페이지를 크롤링한다.
driver.get('https://auto.naver.com/bike/mainList.nhn')

print("+" * 100)
print(driver.title) # 크롤링한 페이지의 title 정보
print(driver.current_url) # 현재 크롤링된 페이지의 url
print("바이크 브랜드 크롤링")
print("-" * 100)

긱본적인 구조는 위와 같다. 파이썬 답게 아주 간결하고 이해하기 쉽다.

어떤 페이지를 크롤링 하더라도 위 코드는 반복적으로 사용하게 될 것이다.

실습으로 크롤링 할 페이지는 네이버의 바이크 페이지이다.

왼쪽 상단의 바이크 제조사 정보를 크롤링 한다고 가정해보자.

그런데 지금 화면에 보이는 것은 6개이다. 전체 제조사를 클릭해야 모든 바이크 정보를 추출 할 수 있다.

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled%206.png

일단 전체 제조사 버튼을 클릭해보자.

아까 보다는 많은 제조사 리스트가 나오지만, 다음 페이지가 존재한다.

다음 페이지도 눌러보자.

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled%207.png

두번째 패이지도 둘러보자. 아래를 보니 두번째 페이지가 끝이다.

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled%208.png

위의 페이지를 확인 했을 때, 모든 바이크 제조사 정보를 크롤링 하기 위해서는 다음과 같은 절차가 필요하다.

  1. 일단 https://auto.naver.com/bike/mainList.nhn 로 이동한다.
  2. 크롤링 결과에서 전체 제조사 엘리멘트를 찾아서 마우스 클릭하고 그 결과물을 받아온다.
  3. 바이크 제조사 정보를 크롤링 한다.
  4. 다음 페이지 버튼이 활성화 되어 있다면 클릭하고 그 결과물을 받아온다.(추가 제조사 정보 있음)
  5. 다음 페이지 버튼이 비 활성화 되어 있다면 크롤링을 중지한다.(더이상 제조사 정보 없음)

위와 같은 크롤링을 하기에는 beautifulsoup, scrapy 만으로는 무리가 있다.

selenium은 webdriver라는 일종의 테스트 가상 브라우저를 사용해서 위와 같은 일련의 프로그래밍 동작을 수행함으로, 동적으로 페이지를 크롤링 할 수 있다.

네이버 바이크에서 바이크 제조사 크롤링 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from selenium import webdriver
import time

chromedriver = 'C:\dev_python\Webdriver\chromedriver.exe'
driver = webdriver.Chrome(chromedriver)

driver.get('https://auto.naver.com/bike/mainList.nhn')

print("+" * 100)
print(driver.title)
print(driver.current_url)
print("바이크 브랜드 크롤링")
print("-" * 100)

# 바이크 제조사 전체 페이지 버튼 클릭
bikeCompanyAllBtn = driver.find_element_by_css_selector("#container > div.spot_main > div.spot_aside > div.tit > a")
bikeCompanyAllBtn.click()

time.sleep(3)

# 바이크 제조사 1번 페이지 진입해서 바이크 리스트 추출
allBikeCompanyElement = driver.find_elements_by_css_selector(
"#_vendor_select_layer > div > div.maker_group div.emblem_area > ul > li")

# 바이크 첫 페이지 크롤링
for item in allBikeCompanyElement:
bikeComName = item.find_element_by_tag_name("span").text
if (bikeComName != ''):
print("바이크 회사명:" + bikeComName)
ahref = item.find_element_by_tag_name("a").get_attribute("href")
print('네이버 자동차 바이크제조사 홈 sub url:', ahref)
imgUrl = item.find_element_by_tag_name("img").get_attribute("src")
print('바이크 회사 엠블럼:', imgUrl)

time.sleep(3)

# 바이크 제조사 리스트의 다음 페이지 버튼을 찾아서 클릭하자.
nextBtn = driver.find_element_by_css_selector(
"#_vendor_select_layer > div > div.maker_group > div.rolling_btn > button.next")
# 다음 바이크 제조사 페이지 버튼이 활성화 여부
isExistNextPage = nextBtn.is_enabled()

if (isExistNextPage == True):
print("다음 페이지 존재함=======================================>")
nextBtn.click()
allBikeCompanyElement = driver.find_elements_by_css_selector(
"#_vendor_select_layer > div > div.maker_group div.emblem_area > ul > li")
for item in allBikeCompanyElement:
bikeComName = item.find_element_by_tag_name("span").text
if (bikeComName != ''):
print("바이크 회사명:" + bikeComName)
ahref = item.find_element_by_tag_name("a").get_attribute("href")
print('네이버 자동차 바이크제조사 홈 sub url:', ahref)
imgUrl = item.find_element_by_tag_name("img").get_attribute("src")
print('바이크 회사 엠블럼:', imgUrl)

# 크롤링이 끝나면 webdriver 브라우저를 종료한다.
# driver.quit()

위 스크립트는 완벽하게 동작하는 전체 코드이다. 코드가 워낙 쉬어서 대충 보면 어떻게 동작하는지 이해가 될 것이다.

selenium api를 찾아보면 다양하게 엘리먼트를 추출하는 방법과, 추출한 엘리멘트에 어떠한 이벤트를 날릴수 있는 알 수 있다.(이글에서는 생략한다. 나도 잘 모르고, 구글링이 왕도이다)

위 코드의 실행 결과는 아래 와 같다.

아래처럼 크롤링 결과물이 출력된다.

특의 한 점은 webdriver라는 것이 일종의 웹 테스트 자동화 도구라고 말했듯이, webdriver가 제어하는 브라우저가 아래처럼 뜨고, 이벤트에 대한 동작이 해당 브라우저에 반영되어 실시간 관찰 할 수 있다. 위 코드 마지막 주석 driver.quit() 를 해제하면 크롤링이 끝난 뒤 해당 브라우저를 자동으로 종료 할 수 있다.

Selenium%20c103ada3648f4a389a9322d760ad1e9a/Untitled%209.png

마무리

간단하게 selenium, webdriver를 사용해서 동적으로 웹페이지를 크롤링 해보았다.

사실 위 실습에서 사용한 네이버 바이크 페이지를 개발자 도구로 보면, 위 처럼 버튼을 굳이 찾아서 클릭하고 크롤링 할 필요가 없다. (위 예제를 돌리고 나서 알게 되었다. 그냥 화면에 안보일 뿐 첫 페이지의 모든 제조사 정보가 있었음. 멍청한…)

위 예제를 돌려보면, webdriver에 의해 열리는 브라워창이 좀 애매하다.

만약 디버깅 용도라면, 괜찮지만 실제 크롤링 서비스를 돌릴 때 저렇게 브라우저가 열릴 필요가 없기 때문이다.

selenium에서는 headless 방식으로 브라우저가 실제 열리지 않고 크롤링이 가능하다고 한다.(phantomJS라는 비슷한 기술도 있다고 한다.)

이 것도 나중에 시간이 되면 한번 공부해 봐야 겠다.