lynx는 리눅스 cli 용 텍스트 브라우저 인데
과거에(한 2000년대 초반)는 그래도 첫 페이지 접속하면 정직하게(!) html이 날아와서
인터넷이 느리거나 하면 lynx로 테스트만 보곤 했는데
요즘에는 javascript 지원 브라우저를 통해
javascript를 처리해서 다른 url로 넘어가야 페이지가 보이게 되어있다 보니 lynx로 접속하면 이런 화면만 보게 된다.
| Google 브라우저 업데이트 이 브라우저는 더 이상 지원되지 않습니다. 계속 검색하려면 최신 버전으로 업그레이드하세요. 자세히 알아보기 |
| # Daum #다음 이 사이트의 기능을 모두 활용하기 위해서는 자바스크립트를 활성화 시킬 필요가 있습니다. 브라우저에서 자바스크립트를 활성화하는 방법을 참고 하세요. |
[링크 : https://lynx.invisible-island.net/]
아무튼 이걸 우회하기 위해서는 자바스크립트를 지원하는 녀석이 필요한데
그때 만만(?)한게 셀레니움(selenium).
셀레니움을 통해서 접속하면 모니터 다리지 않은 가상의 브라우저에서 접속해서
자바스크립트 까지 처리된 페이지를 얻을수 있다.
요건 gpt 에서 생성해준 초기 기능 테스트용 코드
접속하고 body 태그 하위의 내용들을 getText()를 이용해 얻어와서 보여준다.
| const { Builder, By } = require('selenium-webdriver'); const chrome = require('selenium-webdriver/chrome'); const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); async function startBrowser(startUrl) { const options = new chrome.Options(); options.addArguments('--headless=new'); options.addArguments('--no-sandbox'); options.addArguments('--disable-gpu'); const driver = await new Builder() .forBrowser('chrome') .setChromeOptions(options) .build(); let currentUrl = startUrl; while (true) { try { await driver.get(currentUrl); console.clear(); /* ===== 본문 텍스트 출력 ===== */ const bodyText = await driver .findElement(By.tagName('body')) .getText(); console.log(bodyText.substring(0, 3000)); console.log('\n---------------- LINKS ----------------'); /* ===== 링크 수집 ===== */ const elements = await driver.findElements(By.css('a')); const links = []; for (let i = 0; i < elements.length; i++) { const text = await elements[i].getText(); const href = await elements[i].getAttribute('href'); if (href && text.trim()) { links.push({ index: links.length, text, href }); } } links.forEach(l => { console.log(`[${l.index}] ${l.text}`); }); console.log('\n[q] 종료'); /* ===== 사용자 입력 ===== */ const answer = await new Promise(resolve => rl.question('\n이동할 링크 번호: ', resolve) ); if (answer === 'q') break; const idx = parseInt(answer); if (!isNaN(idx) && links[idx]) { currentUrl = links[idx].href; } } catch (err) { console.error('오류 발생:', err.message); break; } } await driver.quit(); rl.close(); } /* 시작 URL */ startBrowser('https://example.com'); |
그리고 페이지 업/다운으로 페이지 이동하고
화살표로는 링크들 이동, 엔터는 해당 링크 따라가기 로 해서 구현한게 아래 코드
| const blessed = require('blessed'); const { Builder } = require('selenium-webdriver'); const chrome = require('selenium-webdriver/chrome'); const startUrl = process.argv[2] || 'https://example.com'; let driver; let history = []; let lines = []; let linkIndexes = []; let selectedLink = 0; let scroll = 0; let title = ''; let currentUrl = ''; /* ================= Selenium ================= */ async function loadPage(url, pushHistory = true) { if (pushHistory && currentUrl) history.push(currentUrl); await driver.get(url); currentUrl = url; title = await driver.getTitle(); const pageData = await driver.executeScript(() => { const result = []; function walk(node) { if (node.nodeType === Node.TEXT_NODE) { const t = node.textContent.replace(/\s+/g, ' ').trim(); if (t) result.push({ text: t }); return; } if (node.nodeType !== Node.ELEMENT_NODE) return; const tag = node.tagName.toLowerCase(); if (tag === 'a' && node.href && node.innerText.trim()) { result.push({ text: node.innerText.trim(), href: node.href }); return; } if (['script','style','noscript'].includes(tag)) return; if (['p','div','section','article','li','pre','blockquote', 'h1','h2','h3','h4','h5','h6'].includes(tag)) { node.childNodes.forEach(walk); result.push({ text: '' }); return; } node.childNodes.forEach(walk); } walk(document.body); return result; }); lines = []; linkIndexes = []; pageData.forEach(item => { const idx = lines.length; if (item.href) { lines.push({ text: item.text, href: item.href, selectable: true }); linkIndexes.push(idx); } else { lines.push({ text: item.text, selectable: false }); } }); scroll = 0; selectedLink = 0; } /* ================= Selection / Scroll ================= */ function recalcSelectionInView() { const top = scroll; const bottom = scroll + body.height - 1; const visible = linkIndexes.filter(i => i >= top && i <= bottom); if (visible.length) selectedLink = linkIndexes.indexOf(visible[0]); } function drawScrollbar() { const total = lines.length; const view = body.height; if (total <= view) return; const maxScroll = total - view; const barHeight = Math.max(1, Math.floor(view * view / total)); const barTop = Math.floor(scroll * (view - barHeight) / maxScroll); for (let i = 0; i < barHeight; i++) { const line = body.getLine(barTop + i); if (line !== undefined) { body.setLine(barTop + i, line + '▐'); } } } /* ================= TUI ================= */ const screen = blessed.screen({ smartCSR: true, fullUnicode: true, title: 'Text Browser' }); const header = blessed.box({ top: 0, height: 2, tags: true }); const body = blessed.box({ top: 2, bottom: 2, tags: true }); /* footer를 두 줄로 분리 */ const footerHelp = blessed.box({ bottom: 1, height: 1, tags: true }); const footerSelected = blessed.box({ bottom: 0, height: 1, tags: true }); screen.append(header); screen.append(body); screen.append(footerHelp); screen.append(footerSelected); function render() { const height = body.height; const visible = lines.slice(scroll, scroll + height); const selectedLine = linkIndexes[selectedLink]; header.setContent(`{bold}${title}{/bold}\n${currentUrl}`); body.setContent( visible.map((l, i) => { const idx = scroll + i; if (l.selectable) { if (idx === selectedLine) return `{white-fg}{blue-bg}${l.text}{/}`; return `{blue-fg}{underline}${l.text}{/}`; } return l.text; }).join('\n') ); drawScrollbar(); footerHelp.setContent( `{dim}↑↓ 이동 PgUp/PgDn 페이지 Enter 열기 b 뒤로 q 종료{/dim}` ); footerSelected.setContent( `{bold}Selected:{/bold} ${lines[selectedLine]?.href || ''}` ); screen.render(); } /* ================= Keys ================= */ function moveSelection(delta) { const next = selectedLink + delta; if (next < 0 || next >= linkIndexes.length) return; selectedLink = next; const idx = linkIndexes[selectedLink]; if (idx < scroll) scroll = idx; if (idx >= scroll + body.height) scroll = idx - body.height + 1; render(); } screen.key('up', () => moveSelection(-1)); screen.key('down', () => moveSelection(1)); screen.key('pageup', () => { scroll = Math.max(0, scroll - body.height); recalcSelectionInView(); render(); }); screen.key('pagedown', () => { scroll = Math.min( Math.max(0, lines.length - body.height), scroll + body.height ); recalcSelectionInView(); render(); }); screen.key('enter', async () => { const line = lines[linkIndexes[selectedLink]]; if (line?.href) { await loadPage(line.href); render(); } }); screen.key('b', async () => { if (history.length) { await loadPage(history.pop(), false); render(); } }); screen.key('resize', () => { recalcSelectionInView(); render(); }); screen.key(['q','C-c'], async () => { if (driver) await driver.quit(); process.exit(0); }); /* ================= Init ================= */ (async () => { const options = new chrome.Options(); options.addArguments('--headless=new'); driver = await new Builder() .forBrowser('chrome') .setChromeOptions(options) .build(); await loadPage(startUrl); render(); })(); |
[링크 : https://chatgpt.com/share/697b1d46-a670-800c-a520-0d6289a4391c]
'Programming > node.js' 카테고리의 다른 글
| electron asar 파일 (0) | 2025.08.26 |
|---|---|
| node excel export (0) | 2024.07.18 |
| web qr decoder (0) | 2024.04.04 |
| node.js 웹소켓 채팅 서버 예제 (0) | 2022.07.14 |
| ubuntu 18.04 / nodej.s 18.x 실패 (0) | 2022.05.19 |





