브라우저 자동화 도구 Puppeteer로 데이터 스크레이핑을 하고 있었다.
배포 환경은 AWS Lambda이고, 개발 환경은 내 로컬 PC(Ubuntu 24.04.1 LTS, node v22.12.0)다.
Lambda로 puppeteer를 실행시키려면 경량화된 chromium binary가 필요했다. 이에 서버리스 플랫폼용 chromium을 사용하고 있었다.
로컬 환경에서는 굳이 서버리스 플랫폼용 chromium을 사용할 필요가 없으므로, puppeteer가 설치해주는 기본 chrome을 사용하기로 했다.
위 조건을 만족시키기 위해 브라우저 실행 시 puppeteer-core를 사용하고, 브라우저는 따로 설치하도록 했다.
const browser = await puppeteer.launch({
args: process.env.NODE_ENV === 'development' ? ['--no-sandbox'] : chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath:
process.env.NODE_ENV === 'development'
? await getExecutablePathFromCache()
: await chromium.executablePath(),
headless: process.env.NODE_ENV === 'development' ? false : chromium.headless,
})
async function getExecutablePathFromCache() {
const chromiumVersion = process.env.CHROMIUM_VERSION_REAL
if (!chromiumVersion) {
throw new Error('CHROMIUM_VERSION_REAL is not set')
}
// 설치된 브라우저 목록 가져오기
const installedBrowsers = await browsers.getInstalledBrowsers({
cacheDir: '.',
})
// 설치된 브라우저 목록에서 크롬 브라우저를 찾아서 실행 파일 경로를 가져오기
let executablePath = installedBrowsers.find(
(browser) => browser.browser === 'chrome' && browser.buildId === chromiumVersion
)?.executablePath
// 실행 파일 경로가 없으면 크롬 브라우저를 설치하기
if (executablePath === undefined) {
try {
const chromium = await browsers.install({
cacheDir: `${process.cwd()}`,
browser: browsers.Browser.CHROME,
buildId: chromiumVersion,
})
executablePath = chromium.executablePath
} catch (error) {
console.error('Browser Installation Failed')
throw error
}
}
return executablePath
}
NODE_ENV가 development인 경우, 현재 프로젝트의 root dir에 puppeteer로 chrome을 설치하도록 만들었다.
chrome binary를 설치하고 puppeteer를 실행시켰더니, 다음과 같은 에러가 나왔다.
Error: Failed to launch the browser process!
chrome/linux-131.0.6778.204/chrome-linux64/chrome: error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory
TROUBLESHOOTING: https://pptr.dev/troubleshooting
at Interface.onClose (/home/hwan/restock-notifier/dist/index.js:77482:16)
at Interface.emit (node:events:536:35)
at Interface.close (node:internal/readline/interface:526:10)
at Socket.onend (node:internal/readline/interface:252:10)
at Socket.emit (node:events:536:35)
at endReadableNT (node:internal/streams/readable:1698:12)
at process.processTicksAndRejections (node:internal/process/task_queues:90:21)
Node.js v22.12.0
libnss3.so 라이브러리가 없다고 나온다.
libnss3.so는 Ubuntu에 설치되는 공유 라이브러리다. 정적 라이브러리와는 달리, 라이브러리가 프로그램에 포함되지 않고, 런타임에 참조되어 사용된다고 한다.
Chrome을 실행하기 위해 libnss3 공유 라이브러리가 필요한 것이 아닌지 의심이 되었다. 공식 문서를 찾아보니, 다음과 같은 옵션을 발견할 수 있었다.

installOptions에 installDeps=true를 추가해주었다.
const chromium = await browsers.install({
cacheDir: `${process.cwd()}`,
browser: browsers.Browser.CHROME,
buildId: chromiumVersion,
installDeps: true,
})
installOptions에 installDeps=true를 추가하여 실행시켰더니 다음과 같은 에러가 나왔다.
Error: Installing system dependencies requires root privileges
at installDeps (/home/hwan/restock-notifier/dist/index.js:77735:11)
at installUrl (/home/hwan/restock-notifier/dist/index.js:77785:15)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async install (/home/hwan/restock-notifier/dist/index.js:77715:18)
at async getExecutablePathFromCache (/home/hwan/restock-notifier/dist/index.js:83916:25)
at async Crawler.openTargetPage (/home/hwan/restock-notifier/dist/index.js:83950:64)
at async Crawler.scrape (/home/hwan/restock-notifier/dist/index.js:83961:31)
at async handler (/home/hwan/restock-notifier/dist/index.js:83989:3)
Node.js v22.12.0
공식 문서를 보면, apt-get을 수행하기 위한 sudo 권한이 필요하다고 나와 있다.
그래서 node script를 sudo로 실행해봤는데, node를 찾지 못해서 which node로 node가 어디에 있는지 확인했고, /home/hwan/.nvm/versions/node/v22.12.0/bin/node에 있다는 것을 파악했다.
해당 node를 /usr/local/bin에 위치하도록 심볼릭 링크를 걸어주었다.
sudo ln -s "$NVM_DIR/versions/node/$(nvm version)/bin/node" "/usr/local/bin/node"
이제 sudo node run.js를 입력하면 sudo 권한으로 node가 실행되고, installDeps=true 옵션에 따라 시스템 의존성을 설치할 수 있게 되었다.
puppeteer-core와 별도 설치된 브라우저 바이너리를 같이 사용할 때, 브라우저 실행에 필요한 시스템 라이브러리를 자동으로 설치할 수 있게 되었다.
시스템 라이브러리를 자동으로 설치하도록 하기 위해 브라우저 설치 API를 사용할 때 installDeps 옵션을 추가했고, 이를 위해 nvm으로 node를 관리하는 상황에서 sudo 권한으로 node script를 실행할 수 있게 만들었음을 위 해결 과정 섹션에서 보였다.
시스템 라이브러리를 sudo apt-get install ~~와 같이 직접 설치해주는 방법도 가능했지만, 수동으로 설치해주어야 하는 의존성을 최대한 줄이고 싶어서 의존성 설치 과정을 자동화했다.