기존에 사용하던 스크래핑 코드
async getHTML() {
// const options = {
// method: 'GET',
// url: 'https://dddd.kr/footwear',
// headers: {
// accept:
// 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
// 'accept-language':
// 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6,zh;q=0.5',
// 'cache-control': 'max-age=0',
// cookie:
// '_ga=GA1.1.707882008.1711439246; _fbp=fb.1.1711439246481.1719838514; _tt_enable_cookie=1; _ttp=8yXK6BytRt5rcoTm8Zy-i2xInfW; __qca=P0-308357420-1711439246657; rsci_vid=26936b67-4973-cbbe-e183-3675af5ae70c; permutive-id=f52685e4-3213-43bc-a35a-aef89c7deca2; dddd=shown; user-value=68; __gads=ID=e5ff9b6997257ca1:T=1711439247:RT=1712109653:S=ALNI_MY9SUVI65jdHrgm-Rafq8Fqqfxw5g; __eoi=ID=f8867c4cd76e4140:T=1711439247:RT=1712109653:S=AA-AfjadtbYv5_oXFmWa3q281RWN; _clck=1ewtjod%7C2%7Cfkm%7C0%7C1546; OptanonConsent=isGpcEnabled=0&datestamp=Wed+Apr+03+2024+11%3A00%3A55+GMT%2B0900+(%ED%95%9C%EA%B5%AD+%ED%91%9C%EC%A4%80%EC%8B%9C)&version=202310.2.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=0cab7763-b597-40b9-9257-d875d99277a9&interactionCount=1&landingPath=NotLandingPage&groups=C0001%3A1%2CC0002%3A1%2CC0003%3A1%2CC0004%3A1&AwaitingReconsent=false&geolocation=KR%3B44; OptanonAlertBoxClosed=2024-04-03T02:00:55.163Z; _clsk=6ogf4k%7C1712109656280%7C1%7C0%7Cd.clarity.ms%2Fcollect; _ga_FPSZ48KG02=GS1.1.1712036567.5.1.1712109673.39.0.0',
// 'if-none-match': '"cb05a834190ee96881dc8866e8223ed8"',
// referer: 'https://dddd.kr/',
// 'sec-ch-ua':
// '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
// 'sec-ch-ua-mobile': '?0',
// 'sec-ch-ua-platform': '"Windows"',
// 'sec-fetch-dest': 'document',
// 'sec-fetch-mode': 'navigate',
// 'sec-fetch-site': 'same-origin',
// 'sec-fetch-user': '?1',
// 'upgrade-insecure-requests': '1',
// 'user-agent':
// 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
// },
// };
// try {
// const response = await axios.request(options);
// const html = response.data;
// const $ = cheerioModule.load(html);
// const urls = [];
// $('.post-box').each(function () {
// const url = $(this).attr('data-permalink');
// urls.push(url);
// });
// console.log('URLs:', urls);
// return urls;
// } catch (error) {
// console.error('Error:', error);
// return null;
// }
// }
// async fetchHiddenData(urls: string[]) {
// const hiddenDataPromises = urls.map(async (url) => {
// try {
// const response = await axios.post(
// 'https://api.permutive.com/v2.0/batch/events?enrich=false&sdkp=true&k=919e734f-ccf8-4682-8bdd-bf3e30f900d4',
// {
// url: url,
// },
// );
// return response.data;
// } catch (error) {
// console.error('hidden data:', error);
// return null;
// }
// });
// // 모든 숨겨진 데이터를 가져온 후에 URL을 반환
// const hiddenData = await Promise.all(hiddenDataPromises);
// return hiddenData;
// }
// async getAllData() {
// const urls = await this.getHTML();
// if (!urls) {
// // HTML 가져오기에 실패한 경우 처리
// return null;
// }
// const hiddenData = await this.fetchHiddenData(urls);
// return {
// urls: urls,
// hiddenData: hiddenData,
// };
// }
// 기존에 햇던 내용
///////////////////////////
const options = {
method: 'POST',
url: 'https://api.permutive.com/v2.0/batch/events',
params: {
enrich: 'false',
sdkp: 'true',
k: '919e734f-ccf8-4682-8bdd-bf3e30f900d4',
},
headers: {
accept: '*/*',
'accept-language':
'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6,zh;q=0.5',
'content-type': 'text/plain',
origin: 'https://hypebeast.kr',
referer: 'https://hypebeast.kr/',
'sec-ch-ua':
'"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
},
data: '[{"user_id":"f52685e4-3213-43bc-a35a-aef89c7deca2","name":"LinkClick","cohorts":["adv","106032","110684","114647","123913","127668","144305","144308","146170","146171"],"segments":[106032,110684,114647,123913,127668,144305,144308,146170,146171],"properties":{"dest_url":"https://hypebeast.kr/footwear/page/5?after1709582459","client":{"type":"web","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36","url":"https://hypebeast.kr/footwear","domain":"hypebeast.kr","title":"신발 | Hypebeast","referrer":"https://hypebeast.kr"}},"session_id":"96558164-9c49-45d8-b53b-1d15f360009f","view_id":"1fb32c7f-cd21-4512-8bc5-5a948790f123"}]',
};
axios
.request(options)
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.error(error);
});
}
////////////////////////
// async getDetaileHtml() {
// try {
// const browser = await puppeteer.launch({ headless: false });
// const page = await browser.newPage();
// await page.goto('https://hypebeast.kr/footwear');
// // 페이지 내용 수집
// const infos = await page.$$eval('.post', (elements) =>
// elements.map((element) => {
// const title = element.querySelector('post-body-title').textContent.trim();
// const image = element.querySelector('img').getAttribute('data-src');
// const subTitle = element
// .querySelector('.post-box-content-excerpt')
// .textContent.trim();
// const content = element.
// return { title, image, subTitle, content };
// }),
// );
// await Promise.all(
// infos.map(async (info) => {
// const news = new News();
// news.newsUrl = info.url;
// news.newsImg = info.image;
// news.content = info.subTitle;
// news.title = info.title;
// news.subTitle = info.subTitle;
// news.newsDate = new Date();
// await this.newsRepository
// .createQueryBuilder()
// .insert()
// .values(news)
// .execute();
// }),
// );
// } catch(err) {
// }
// }
// async getHTML() {
// try {
// const browser = await puppeteer.launch({ headless: false });
// const page = await browser.newPage();
// await page.goto('https://hypebeast.kr/footwear');
// // 스크롤링 함수 정의
// async function autoScroll(page) {
// await page.evaluate(async () => {
// await new Promise((resolve, reject) => {
// let totalHeight = 0;
// // 페이지 이동 픽셀 300으로 지정
// const distance = 300;
// // 일정한 간격으로 스크롤 하기 위한 setInterval
// const scrollInterval = setInterval(() => {
// // 현재 body에 있는 총 스크롤 가능한 높이 로드
// const scrollHeight = document.body.scrollHeight;
// window.scrollBy(0, distance);
// // 스크롤 된 높이 업데이트
// totalHeight += distance;
// // 스크롤 된 높이 전체 스크롤한 높이 비교 후
// // 크거나 같으면 모든 페이지 스크롤 한 것으로 판단
// // 스크롤 멈추기
// if (totalHeight >= scrollHeight) {
// clearInterval(scrollInterval);
// resolve(void 0);
// }
// }, 100);
// });
// });
// }
// // 페이지를 스크롤하여 추가 콘텐츠를 로드
// await autoScroll(page);
// // "더 알아보기" 버튼 클릭
// const loadMoreButton = await page.$('.button-load-more');
// if (loadMoreButton) {
// await loadMoreButton.click();
// // 추가 콘텐츠가 로드될 때까지 기다림
// await page.waitForSelector('.post-box', { timeout: 300000 });
// }
// // 페이지 내용 수집
// const infos = await page.$$eval('.post-box ', (elements) =>
// elements.map((element) => {
// const title = element.getAttribute('data-title');
// const image = element.querySelector('img').getAttribute('data-src');
// const excerpt = element
// .querySelector('.post-box-content-excerpt')
// .textContent.trim();
// const url = element.querySelector('a').getAttribute('href');
// return { title, image, excerpt, url };
// }),
// );
// await Promise.all(
// infos.map(async (info) => {
// const news = new News();
// news.newsUrl = info.url;
// news.newsImg = info.image;
// news.content = info.excerpt;
// news.title = info.title;
// news.newsDate = new Date();
// await this.newsRepository
// .createQueryBuilder()
// .insert()
// .values(news)
// .execute();
// }),
// );
// await page.close();
// await browser.close();
// return infos;
// } catch (error) {
// console.log(error);
// return error;
// }
// }
네트워크 패킷을 분석 후 페이지 넘버를 보내
내가 원하는 페이지까지 url을 스크래핑하여 가져오는로직이다.
async getURLsFromPage(pageNumber: number): Promise<string[]> {
try {
const response = await axios.post(
`https://ddddddddddddddd/${pageNumber}`,
);
console.log(response.data);
const html = response.data;
const $ = cheerioModule.load(html);
const urls: string[] = [];
$('.post-box').each(function () {
const url = $(this).attr('data-permalink');
if (url) {
urls.push(url);
}
});
console.log(urls);
return urls;
} catch (error) {
console.error(error);
return null;
}
}
async getAllURLs(): Promise<string[]> {
const allURLs: string[] = [];
let pageNumber = 1;
// let hasMorePages = true;
for (let i = 1; i <= 5; i++) {
const urls = await this.getURLsFromPage(i);
allURLs.push(...urls);
}
console.log(allURLs);
return allURLs;
}
위 로직으로 콘솔을 찍어보았을 때 찍히는 로그다.
총 페이지는 626페이지가 있는데 나는 이 페이지 전체를
크롤링해오는 것이 아니라 5개의 페이지만 나오게끔 불러드렸다.
근데 문제는 파싱한 데이터는 빈 배열만 출력해주고 값을 가져오지 못한다는 것이다.
이 부분을 해결하려고 여러 많은 시도를 해보았지만 결과는 똑같았다ㅜㅜ
내가 가져오려는 페이지의 특성이 API를 숨겨둬서 로드 될 때 페이로드를 보내주는데
그 과정에서 오류가 나는 듯 하다. 시간을 더 들이면 MVP 구현이 안될 수 있기 때문에
최신 페이지만 파싱해서 가져오고 데이터를 저장해 CRUD를 만드는 것이다.
그 후 계속 추가되는 데이터를 cron을 이용해서 스크래핑을 해오는 것.
크론을 기반으로 네스트에서 지원해주는게
스케줄이라는 라이브러리다.
https://docs.nestjs.com/techniques/task-scheduling
위 사이트를 참고하여 사용할 수 있다.
이 방법을 적용시켜보도록 해야겠다.
[TIL] 2024.04.05 인기순 정렬과 스크래핑 데이터 중복처리 (0) | 2024.04.06 |
---|---|
[TIL] 2024.04.05 axios, puppeteer를 활용한 웹스크래핑 (0) | 2024.04.05 |
[TIL] 2024.04.02 네트워크 패킷 분석 (0) | 2024.04.03 |
[TIL] 2024.04.01 스크래핑 데이터 저장 (0) | 2024.04.01 |
[TIL]2024.03.28 HTTP, GET,POST 정리 (0) | 2024.03.29 |