실제 데이터 수집하기
"웹 페이지의
<title>
요소를 스크래핑하는 건 좋지만, 그다지 유용하지 않잖아요. 이제 실제 데이터를 스크래핑해서 기계가 읽을 수 있는 형식으로 저장할 수 있나요? 사실 이게 제가 이 튜토리얼을 시작한 이유거든요!"
알겠습니다, 젊은 파다완! 먼저 크롤링하는 법을 배워야 합니다. 그래야 데이터를 다룰 수 있죠!
실전용 크롤러 만들기
실전용 크롤러를 만드는 것은 어렵지 않지만, 스크래핑할 때 예상치 못한 함정들이 많이 있습니다. 이번 실전 프로젝트에서는 Crawlee 웹사이트 대신 예제 창고 스토어를 스크래핑하는 방법을 배워보겠습니다. 이 사이트에는 여러 카테고리의 제품 목록이 있고, 각 제품마다 상세 페이지가 있습니다.
이 웹사이트는 JavaScript 렌더링이 필요하기 때문에 Crawlee의 더 많은 기능을 보여드릴 수 있습니다. 또한 대규모 스크래핑을 할 때 반드시 마주치게 될 실전 문제들을 해결하는 데 도움이 되는 팁들도 추가했습니다.
크롤링 이론에 관심이 없다면 다음 장으로 바로 넘어가서 코딩을 시작하셔도 됩니다.
계획 세우기
때로는 스크래핑이 매우 간단할 수 있지만, 대부분의 경우 먼저 약간의 조사를 하고 다음과 같은 질문들에 답해보는 것이 도움이 됩니다:
- 웹사이트의 구조는 어떻게 되어 있나요?
- HTTP 요청만으로 스크래핑할 수 있나요(즉,
CheerioCrawler
로 가능한가요)? - 어떤 작업을 위해 헤드리스 브라우저가 필요한가요?
- 스크래핑 방지 기능이 있나요?
- HTML을 파싱해야 하나요, 아니면 웹사이트의 API에서 직접 데이터를 가져올 수 있나요?
이 튜토리얼의 목적상, 이 웹사이트는 CheerioCrawler
로 스크래핑할 수 없다고 가정하겠습니다. 실제로는 가능하지만, 이 입문 가이드에서 다루기에는 너무 깊이 들어가야 합니다. 따라서 지금은 일을 쉽게 만들어 PlaywrightCrawler
로 스크래핑하고, 그 과정에서 헤드리스 브라우저에 대해 배워보겠습니다.
필요한 데이터 선택하기
좋은 첫 단계는 어떤 데이터를 스크래핑하고 싶은지, 그리고 그것을 어디서 찾을 수 있는지 파악하는 것입니다. 일단은 스토어의 전체 컬렉션 페이지에서 모든 카테고리의 모든 제품을 스크래핑하고, 각 제품에 대해 다음 정보를 가져오기로 하겠습니다:
- URL
- 제조사
- SKU
- 제목
- 현재 가격
- 재고 수량
목록 페이지에서 바로 볼 수 있는 정보도 있지만, "SKU"와 같은 세부 정보를 얻으려면 제품의 상세 페이지도 열어봐야 한다는 것을 알 수 있습니다.
시작 URL
이것이 크롤링을 시작하는 지점입니다. 데이터와 최대한 가까운 곳에서 시작하는 것이 좋습니다. 예를 들어, 우리가 추출하고자 하는 모든 것이 https://warehouse-theme-metal.myshopify.com/collections
페이지에서 찾을 수 있다는 것을 이미 알고 있다면, https://warehouse-theme-metal.myshopify.com/
에서 시작해서 collections
링크를 찾는 것은 의미가 없습니다.
페이지 탐색하기
https://warehouse-theme-metal.myshopify.com/collections
페이지를 자세히 살펴보겠습니다. 페이지에는 몇 개의 카테고리가 있고, 각 카테고리에는 아이템 목록이 있습니다. 일부 카테고리 페이지에서는 하단에 다음 페이지로 이동하는 링크가 있는 것을 볼 수 있습니다. 이를 보통 페이지네이션이라고 합니다.
카테고리와 정렬
카테고리를 클릭하면 해당 카테고리로 필터링된 제품 페이지가 로드됩니다. 몇 개의 카테고리를 살펴보면서 동작을 관찰해보면, 다양한 조건(베스트셀러
, 가격 낮은순
등)으로 정렬할 수 있다는 것도 알 수 있지만, 이 예제에서는 이러한 정렬은 다루지 않겠습니다.
주의하세요. amazon.com과 같은 일부 웹사이트에서는 이것이 사실이 아니며, 실제로 카테고리의 제품 합계가 필터 없이 볼 수 있는 것보다 더 많습니다. 자세한 내용은 페이지네이션이 제한된 웹사이트 스크래핑 튜토리얼을 참조하세요.
페이지네이션
데모 창고 스토어의 페이지네이션은 꽤 단순합니다. 페이지를 이동할 때 URL이 다음과 같이 변경되는 것을 볼 수 있습니다:
https://warehouse-theme-metal.myshopify.com/collections/headphones?page=2
4페이지로 이동해보세요. 페이지네이션 링크가 업데이트되고 더 많은 페이지가 표시되는 것을 볼 수 있습니다. 하지만 이것이 모든 페이지를 포함하고 어느 시점에서 멈추지 않을 것이라고 확신할 수 있을까요?
위에서 설명한 필터 문제와 마찬가지로, 페이지네이션이 있다고 해서 모든 결과를 단순히 페이지를 넘기면서 볼 수 있다는 보장은 없습니다. 항상 페이지네이션에 대한 가정을 테스트하세요. 그렇지 않으면 결과의 일부를 놓치고도 모를 수 있습니다.
이 글을 쓰는 시점에서 Headphones
컬렉션의 결과 카운터는 75개의 결과(제품)를 보여줍니다. 한 페이지의 결과를 빠르게 세어보면 24개입니다. 6행에 각각 4개의 제품이 있습니다. 이는 총 4페이지의 결과가 있다는 의미입니다.
확신이 서지 않는다면 https://warehouse-theme-metal.myshopify.com/collections/headphones?page=2
와 같이 중간 페이지를 방문해서 페이지네이션이 어떻게 보이는지 확인해볼 수 있습니다.
크롤링 전략
이제 어디서 시작하고 모든 액터 세부 정보를 어떻게 찾을 수 있는지 알았으니, 크롤링 프로세스를 살펴보겠습니다.
- 카테고리 목록이 있는 스토어 페이지(시작 URL)를 방문합니다.
- 모든 카테고리의 링크를 큐에 추가합니다.
- 현재 페이지의 모든 제품 페이지를 큐에 추가합니다.
- 결과의 다음 페이지 링크를 큐에 추가합니다.
- 큐에서 다음 페이지를 엽니다.
- 결과 목록 페이지인 경우 2번으로 돌아갑니다.
- 제품 페이지인 경우 데이터를 스크래핑합니다.
- 모든 결과 페이지와 모든 제품이 처리될 때까지 반복합니다.
PlaywrightCrawler
는 올바른 요청을 제공하면 페이지 방문을 대신 처리해주고, 이미 페이지를 큐에 추가하는 방법도 알고 있으므로 이는 꽤 쉬울 것입니다. 그럼에도 불구하고 보여드리고 싶은 몇 가지 팁이 더 있습니다.
정상 작동 확인
스크래핑 로직을 작성하기 전에 모든 것이 제대로 설정되었는지 확인해보겠습니다. 이전 분석에서 뭔가가 맞지 않다는 것을 깨달을 수도 있고, 웹사이트가 예상한 대로 정확히 동작하지 않을 수도 있습니다.
아래 예제는 시작 URL을 방문하여 해당 페이지의 모든 카테고리의 텍스트 내용을 출력하는 새로운 크롤러를 만듭니다. 코드를 실행하면 개별 카테고리 카드의 매우 형식이 안 좋은 내용이 표시됩니다.
- Playwright
- Playwright with Cheerio
// CheerioCrawler 대신 Playwright를 사용하여
// JavaScript를 렌더링할 수 있도록 합니다.
import { PlaywrightCrawler } from 'crawlee';
const crawler = new PlaywrightCrawler({
requestHandler: async ({ page }) => {
// 액터 카드가 렌더링될 때까지 기다립니다.
await page.waitForSelector('.collection-block-item');
// 브라우저에서 액터 카드 요소를 대상으로 하는
// 함수를 실행하여 조작할 수 있게 합니다.
const categoryTexts = await page.$$eval('.collection-block-item', (els) => {
// 액터 카드에서 텍스트 내용을 추출합니다
return els.map((el) => el.textContent);
});
categoryTexts.forEach((text, i) => {
console.log(`카테고리_${i + 1}: ${text}\n`);
});
},
});
await crawler.run(['https://warehouse-theme-metal.myshopify.com/collections']);
// CheerioCrawler 대신 Playwright를 사용하여
// JavaScript를 렌더링할 수 있도록 합니다.
import { PlaywrightCrawler } from 'crawlee';
const crawler = new PlaywrightCrawler({
requestHandler: async ({ page, parseWithCheerio }) => {
// 액터 카드가 렌더링될 때까지 기다립니다.
await page.waitForSelector('.collection-block-item');
// 브라우저에서 페이지의 HTML을 추출하고
// Cheerio로 파싱합니다.
const $ = await parseWithCheerio();
// 익숙한 Cheerio 구문을 사용하여
// 모든 액터 카드를 선택합니다.
$('.collection-block-item').each((i, el) => {
const text = $(el).text();
console.log(`카테고리_${i + 1}: ${text}\n`);
});
},
});
await crawler.run(['https://warehouse-theme-metal.myshopify.com/collections']);
.collection-block-item
선택자를 어떻게 얻었는지 궁금하시다면, 다음 장에서 DevTools에 대해 설명할 때 알려드리겠습니다.
DevTools - 스크래퍼의 도구상자
여기서는 가장 일반적인 브라우저이기 때문에 Chrome DevTools를 사용하겠지만, 다른 브라우저의 도구를 사용하셔도 됩니다. 모두 매우 비슷합니다.
Chrome에서 https://warehouse-theme-metal.myshopify.com/collections로 이동한 다음 페이지의 아무 곳이나 마우스 오른쪽 버튼으로 클릭하고 검사를 선택하거나, F12를 누르거나 시스템이 선호하는 방식으로 DevTools를 열어보세요. DevTools를 사용하면 현재 열려 있는 웹 페이지의 모든 측면을 검사하거나 조작할 수 있습니다. DevTools에 대해 더 자세히 알아보려면 공식 문서를 참조하세요.
요소 선택하기
DevTools에서 요소 선택 도구를 선택하고 액터 카드 중 하나 위에 마우스를 올려보세요.
카드 내부의 다양한 요소를 선택할 수 있다는 것을 알 수 있습니다. 제목이나 설명과 같은 내용이 아닌, 카드 전체를 선택해보세요.
요소를 선택하면 DevTools HTML 검사기에서 해당 요소가 강조 표시됩니다. 요소들을 자세히 보면 다양한 HTML 요소에 클래스가 붙어 있는 것을 볼 수 있습니다. 이를 CSS 클래스라고 하며, 스크래핑에 활용할 수 있습니다.
반대로 HTML 검사기에서 요소 위에 마우스를 올리면 페이지에서 해당 요소가 강조 표시됩니다. 컬렉션 카드 주변의 페이지 구조를 살펴보세요. 모든 카드의 데이터가 collection-block-item 클래스를 포함하는 class
속성이 있는 <a>
요소 안에 표시되어 있는 것을 볼 수 있습니다. 이제 .collection-block-item
선택자를 어떻게 얻었는지 이해가 되실 것입니다. 이는 collection-block-item
클래스로 표시된 모든 요소를 찾는 방법입니다.
이 클래스를 가진 원하지 않는 요소가 없는지 항상 확인하는 것이 좋습니다. DevTools의 콘솔 탭에서 다음을 실행해보세요:
document.querySelectorAll('.collection-block-item');
31개의 컬렉션 카드만 반환되고 다른 것은 반환되지 않는 것을 볼 수 있습니다.
CSS 선택자와 DevTools는 꽤 큰 주제입니다. 더 자세히 알아보고 싶다면 Apify Academy의 초보자를 위한 웹 스크래핑 강좌를 방문해보세요. 무료이고 오픈소스입니다 ❤️.
다음 단계
다음으로는 모든 목록 페이지와 모든 제품 상세 페이지를 포함하여 스토어 전체를 크롤링해보겠습니다.