-60%까지 떨어졌던 주식이 부활하고 쏠쏠한 수익을 냈다. 그래서 다시 주식을 시작했고, 효율적으로 관리하기 위해서 개인적인 웹 페이지를 만들고 싶다는 욕심이 생겼다. 그 첫단계로 주식 데이터를 크롤링 해야겠다.
npm i puppeteer
...
@Post('/')
async currentStock(@Body('url') url: string[]): Promise<BoardsData> {
return await this.boardsService.name(url);
}
...
...
for (let urls of url) {
const page: puppeteer.Page = await browser.newPage();
await page.goto(urls, { waitUntil: 'networkidle2' });
const result: BoardData = await page.evaluate(() => {
const nameElement: HTMLElement | null = document.querySelector(
'div.instrument-price_instrument-price__xfgbB > div.text-xl > span.instrument-price_change-percent__bT4yt',
);
const newElement: HTMLElement | null = document.querySelector(
'div.instrument-header_instrument-name__VxZ1O > h1',
);
let change_percent: string = nameElement
? nameElement.textContent
: 'No data found';
let name: string = newElement
? newElement.textContent
: 'No data found';
return { change_percent, name };
});
...
({headless: false,})
headless 프로퍼티가 ture라면 실제 브라우저의 실행없이도 데이터를 크롤링 할 수 있다. 그런데 ture옵션을 주었을 때 정상적으로 작동하지 않는 것 같다. 일단 false 옵션을 사용하면 '헤드풀(headful)' 모드에서 브라우저를 시작한다. 즉, 실제 브라우저 창이 열리고 사용자가 볼 수 있게 된다는데 이 때는 정상작동 한다.
...
const browser: puppeteer.Browser = await puppeteer.launch({
headless: false,
});
...
await page.goto(urls, { waitUntil: 'networkidle2' })
Puppeteer의 page.goto 메소드의 waitUntil 옵션은 특정 이벤트가 발생할 때까지 페이지 네비게이션을 대기하는데 사용됩니다. 사용 가능한 값은 다음과 같습니다:
'load': window.load 이벤트가 발생할 때까지 대기. 이 이벤트는 모든 이미지, 스타일 시트 등이 로드된 후에 발생합니다.
'domcontentloaded': DOMContentLoaded 이벤트가 발생할 때까지 대기. 이 이벤트는 HTML이 완전히 로드되고 파싱되었지만 스타일 시트, 이미지, 서브프레임 등이 아직 로드되지 않았을 때 발생합니다.
'networkidle0': 최소한 500ms 동안 네트워크에 최대 0개의 네트워크 연결이 발생할 때까지 대기. 이는 페이지가 거의 완전히 로드되었음을 나타내는 좋은 지표가 될 수 있습니다.
'networkidle2': 최소한 500ms 동안 네트워크에 최대 2개의 네트워크 연결이 발생할 때까지 대기. 이는 페이지 로드가 거의 끝났지만 일부 요소가 아직 로드 중일 수 있음을 나타냅니다.
이러한 옵션은 웹 사이트의 로드 상태를 더 세밀하게 제어하고 싶을 때 유용합니다. 예를 들어, 네트워크 연결이 완전히 끝나기를 기다리는 대신 DOMContentLoaded 이벤트가 발생하자마자 크롤링을 시작하고 싶을 수 있습니다.
>
: 이 기호는 "직접 자식(direct child)" 선택자를 의미합니다. 즉, parent > child
는 parent
요소의 직접적인 하위 요소 중 child
를 선택합니다. parent
아래에 더 깊게 중첩된 child
는 선택하지 않습니다.
.
: 이 기호는 클래스 선택자를 의미합니다. 따라서 .class-name
은 HTML 요소에서 class-name
이라는 클래스를 가진 모든 요소를 선택합니다.
import { Controller, Get, Injectable } from '@nestjs/common';
import * as puppeteer from 'puppeteer';
import { createStockDTO } from './dto/boards.dto';
import { BoardsData, BoardData } from './interface/boards.interface';
@Injectable()
export class BoardsService {
async name(url: string[]): Promise<BoardsData> {
const browser: puppeteer.Browser = await puppeteer.launch({
headless: false,
});
let arr: BoardData[] = [];
for (let urls of url) {
const page: puppeteer.Page = await browser.newPage();
await page.goto(urls, { waitUntil: 'networkidle2' });
const result: BoardData = await page.evaluate(() => {
const nameElement: HTMLElement | null = document.querySelector(
'div.instrument-price_instrument-price__xfgbB > div.text-xl > span.instrument-price_change-percent__bT4yt',
);
const newElement: HTMLElement | null = document.querySelector(
'div.instrument-header_instrument-name__VxZ1O > h1',
);
let change_percent: string = nameElement
? nameElement.textContent
: 'No data found';
let name: string = newElement
? newElement.textContent
: 'No data found';
return { change_percent, name };
});
const arrName: BoardData = {
change_percent: result.change_percent,
name: result.name,
};
arr.push(arrName);
await page.close();
}
await browser.close();
return { currentPercent: arr };
}
async createStock(createStockDTO: createStockDTO) {
return;
}
}