https://blog.cau-likelion.org/
CAU-Likelion Blog
멋쟁이 사자들을 위한 블로그 피드
blog.cau-likelion.org
멋사 중앙대만을 위한 블로그 피드를 제작해보았다.
nextjs와 typescipt를 사용해서 간단하게 rss를 파싱하는 방식으로 진행했다.
RSS란
Rich Site Summary또는 Really Simple Syndication의 약자로, 어떤 사이트에 새로운 컨텐츠가 올라왔을때, 해당 사이트를 방문하지 않고 컨텐츠를 이용하기 위한 방법이다.
블로그별 RSS주소
tistory : [blogName].tistory.com/rss
velog : v2.velog.io/rss@[velogID]
naver : rss.blog.naver.com/[naverID]
medium : medium.com/feed/@[MediumID]
이걸 활용하면 해당 유저의 id값, 또는 블로그 이름만 알아도 해당 블로그 글의 피드를 얻어올 수 있다.
rss-parser
그리고 놀랍게도 rss-parser라이브러리 또한 존재했다.
https://www.npmjs.com/package/rss-parser
rss-parser
A lightweight RSS parser, for Node and the browser. Latest version: 3.13.0, last published: 4 months ago. Start using rss-parser in your project by running `npm i rss-parser`. There are 298 other projects in the npm registry using rss-parser.
www.npmjs.com
parser.parseURL(url)을 통해 feed를 얻어올 수 있다.
해당 feed는 items로 구성되고, item하나는 title(글 제목), link(링크), content(글의 내용), isoDate(발행일)등으로 구성된다.
이걸 활용해서 다음과 같은 컴포넌트를 구성해 줄 것이다.
다른 것은 그냥 갖다쓰면되는데 content와 썸네일이 문제였다.
content 문제
1. html 태그
html태그가 그대로 노출되고, 줄바꿈 문자가 표기되는 문제가 있었다. replace메서드를 활용해서 없애주었다.
...
content: item.content.replace(/<[^>]*>?/g, '')
.replace(/\n/g, '')
.replace(/ /g, '')
.trim()
.replace(/\s+/g, ' ')
.slice(0, 300),
2. 광고문제
content에는 블로그 이용자가 광고를 삽입한 경우, 본문에 광고 스크립트가 표기되는 문제가 있었다.
광고스크립트 div의 클래스는 모두 .revenue_unit_wrap으로 시작했는데, 처음에는 이걸 정규식 필터링으로 해결해보고자 했었다.
하지만, <div></div>의 구성이 중첩되어 어디까지가 정확히 .revenue_unit_wrap의 div가 닫히는 구간인지 판단하기 힘든 부분이 있었고, 그냥 DOM라이브러리를 찾아보자는 결론에 이르렀다.
다행이 JSDOM이라는 npm 라이브러리가 있었고, 이를 활용해서 광고 문제를 해결했다.
https://www.npmjs.com/package/jsdom
jsdom
A JavaScript implementation of many web standards. Latest version: 22.1.0, last published: 3 months ago. Start using jsdom in your project by running `npm i jsdom`. There are 6380 other projects in the npm registry using jsdom.
www.npmjs.com
jsdom은 해당 content를 dom형태로 만들 수 있고, jsdom인스턴스.window.document를 통해 얻은 document로 DOM API문법을 사용할 수 있다.
이를 활용해 아래와 같이 removeAdd를 만들어주었다.
const removeAdd = (content: string | undefined) => {
if (!content) return '';
const dom = new JSDOM(content);
const document = dom.window.document;
const revenueUnitWraps = document.querySelectorAll('.revenue_unit_wrap');
revenueUnitWraps.forEach((revenueUnitWrap) => {
revenueUnitWrap.remove();
});
const outputHTML = document.documentElement.outerHTML;
return outputHTML;
};
3. medium rss의 문제
웃기게도 medium은 자기 혼자 content태그가 아니라 content:encoded를 사용한다.
이를 해결하기 위해 decideConten함수로 content내용을 결정해주었다.
const decideContent = (item: feed) => {
if (item.content) return item.content;
return item['content:encoded'];
};
썸네일 문제
문제는 썸네일이었다. 본문에서 긁어왔어야 했는데, 이부분은 처음 만나는 img태그를 긁어오도록 정규식을 짜주었다.
const getThumbnail = (content: string | undefined) => {
const regex = /<img\s+src=(?:(['"])(.*?)\1|([^'"\s]+))/g;
if (content) {
const result = content.match(regex);
if (result) return result[0].split(/src=/)[1].split(/\'|\"/)[1];
return null;
}
return null;
};
전체코드
참고로 blogList는 [{name:"이름", blog:"rss주소"}, ...]의 형태다.
export const getRss = async () => {
const parser = new Parser();
return await Promise.all(
blogList.map(async ({ name, blog }) => {
const feed = await parser.parseURL(blog);
const result = feed.items.map((item) => {
const content = decideContent(item as feed);
return {
title: item.title,
writer: name,
link: item.link,
content: removeAdd(content)
.replace(/<[^>]*>?/g, '')
.replace(/\n/g, '')
.replace(/ /g, '')
.trim()
.replace(/\s+/g, ' ')
.slice(0, 300),
thumbnail: getThumbnail(content),
date: item.isoDate,
};
});
return result as unknown as feed[];
})
);
};
속도의 문제
이렇게 해서 SSR로 배포하자 생긴 문제가 바로 렌더링 문제였다.
SSR vs SSG light house로 비교하여 서비스 성능 개선하기
https://blog.cau-likelion.org/ LikeLionCAU Blog this This 키워드동작은 나타내는 메서드는 자신이 속한 객체의 상태, 프로퍼티를 참조하고 변경이 가능해야 한다.그러기 위해서는 자신이 속한 객체를 가리키
0422.tistory.com
위 글을 참고하면 된다.