작은 프로젝트, 시작부터 배포까지

처음 시작할 때 '프로젝트'를 다루는 방법을 전혀 이해하지 못했습니다. 한 눈에 프로젝트의 범위, 단계 및 고려 사항을 이해하기 어려웠습니다. 비하인드를 보고 싶었다. 우리는 모두 다르게 작업하지만 이 기사에서는 작은 것에 대한 아이디어가 떠오를 때 어떻게 작업하는지 보여줍니다. 내가 가려운 곳을 긁는 걸 보러 오세요.

나는 이전에 MDN 웹 문서에서 이번 주 기사에 대한 연구 여행을 촉발할 무언가를 찾고 있었는데 임의의 페이지 링크가 없다는 것을 발견했습니다. 랜덤 페이지 기능은 웹 기술의 일반적인 수정에 유용할 것입니다. 흥미로워 보이는 항목을 클릭할 수 있습니다.



저는 JavaScript용 index page을 해킹하는 것으로 여행을 시작했습니다. 이것은 종종 DevTools를 사이트의 초안 버전으로 사용하여 직장에서 프런트 엔드 웹 문제를 해결하는 방법이기도 합니다.



HTML에는 document.querySelector("#wikiArticle > table > tbody")에 테이블 본문이 있습니다. Chrome에서는 요소를 마우스 오른쪽 버튼으로 클릭하고 이동Copy -> Copy JS Path하여 이를 찾을 수 있습니다. 각 항목은 세 개의 테이블 행을 사용합니다. 나는 페이지 링크와 요약을 쫓고 있습니다. 행을 각각 하나의 항목을 구성하는 그룹으로 분리해 보겠습니다.

콘솔에서 이 줄을 실행할 수 있습니다.

// Index table
const indexTable = document.querySelector("#wikiArticle > table > tbody");

// There are three <tr>s to a row
const allRows = indexTable.querySelectorAll('tr');
const chunkRows = [];
for (var i = 0; i < allRows.length; i += 3) {
    chunkRows.push([allRows[i], allRows[i + 1], allRows[i + 2]]);
}

인덱스 페이지 데이터의 복사본을 어딘가에 저장해야 합니다. 각 요청마다 페이지를 스크랩하는 것은 낭비적이고 느립니다. 웹을 통해 전달할 것이므로 JSON으로 저장하는 것이 가장 적합합니다. 페이지 스니펫에는 링크 속성과 텍스트 속성이 있어야 합니다. 여기서도 일부 템플릿을 처리하겠습니다.

// Map these chunked rows into a row object
const pageEntries = [];
chunkRows.forEach(row => {
    const a = row[0].querySelector('a');
    const page = {
        link: `<a class="page-link "href="${a.href}">${a.innerText}</a>`,
        text: `<div class="page-text">${row[1].querySelector('td').innerText}</div>`
    }
    pageEntries.push(page);
})

이를 사용자에게 어떻게 전달할 것인지 결정해야 했습니다. Chrome 확장 프로그램을 고려했지만 프로토타입을 더 빨리 만들고 싶었기 때문에 Glitch로 전환하여 Express 템플릿을 사용했습니다.

스크래핑 논리를 비동기 함수로 패키징하고 puppeteer을 사용하여 인덱스 페이지의 컨텍스트에서 이러한 명령을 실행했습니다. (Glitch에서는 보안 위험이 될 수 있는 --no-sandbox를 사용해야 합니다.) 완료된 프로젝트에서 node getNewPages.js를 사용하여 이 스크립트를 수동으로 호출하여 디스크의 항목을 업데이트할 수 있습니다. (이것은 크론 작업에 깔끔하게 맞을 것입니다!).

Chrome DevTool 해커에서 좀 더 응집력 있는 것까지.

// getNewPages.js

const fs = require('fs');
const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({
      args: ['--no-sandbox'] // Required for Glitch
    });
    const page = await browser.newPage();
    await page.goto('https://developer.mozilla.org/en-US/docs/Web/JavaScript/Index');

    // Scan index table, gather links and summaries
    const pageEntries = await page.evaluate(() => {

        // Index table
        const indexTable = document.querySelector("#wikiArticle > table > tbody");

        // There are three <tr>s to a row
        const allRows = indexTable.querySelectorAll('tr');
        const chunkRows = [];
        for (var i = 0; i < allRows.length; i += 3) {
            chunkRows.push([allRows[i], allRows[i + 1], allRows[i + 2]]);
        }

        // Map these chunked rows into a row object
        const pageEntries = [];
        chunkRows.forEach(row => {
            const a = row[0].querySelector('a');
            const page = {
                link: `<a class="page-link "href="${a.href}">${a.innerText}</a>`,
                text: `<div class="page-text">${row[1].querySelector('td').innerText}</div>`
            }
            pageEntries.push(page);
        })
        return pageEntries;
    });

    // Save page objects to JSON on disk
    const pageJSON = JSON.stringify(pageEntries);
    fs.writeFile('./data/pages.json', pageJSON, function (err) {
        if (err) {
            return console.log(err);
        }
        console.log(`New pages saved! (JSON length: ${pageJSON.length})`);
    });

    await browser.close();
})();

더 빠른 액세스를 위해 모든 페이지 항목을 메모리에 유지하는 require('./data/pages.json')를 사용하여 JSON 파일을 노드에 로드할 수 있습니다(이는 ~300kb의 고정된 작은 크기로 인해 가능함). 웹 앱의 나머지 부분은 임의 함수를 둘러싼 래퍼입니다.

// server.js

// init project
const express = require('express');
const app = express();
app.use(express.static('public'));

const pages = require('./data/pages.json');
const rndPage = () => pages[Math.floor(Math.random() * pages.length)];

app.get('/', (req, res) => res.sendFile(__dirname + '/views/index.html'));

app.get('/rnd', (req, res) => res.send(rndPage()));

// listen for requests :)
const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

우리에게 필요한 유일한 클라이언트 측 JavaScript는 페이지 링크 및 요약을 업데이트하기 위한 fetch 호출입니다. 뿐만 아니라 귀여운 흔들림 애니메이션이 있는 버튼으로 항목을 넘길 수 있습니다. 같은 스니펫이 두 번 나올 확률은 897분의 1입니다. 어떻게 해결하시겠습니까? 댓글로 알려주세요!

여기에서 이동: 동일한 행 패턴을 따르는 MDN 문서(JavaScript 제외)의 다른 페이지가 있습니다. 즉, 스크립트에서 URL을 변경하여 간단하게 스크랩할 수 있습니다.

코드 사본을 사용하여 프로젝트를 재생하거나remix 리포지토리를 복제할 수 있습니다healeycodes/random-mdn-page.


프로그래밍 및 개인 성장에 대해 150명 이상의 사람들이 내newsletter에 가입했습니다!

기술에 대해 트윗합니다.

좋은 웹페이지 즐겨찾기