Published on

[Django] RSS Feed를 만들어보자

Authors
  • avatar
    Name
    Almer Minified
    Twitter

[Django] RSS Feed를 Rss201rev2Feed와 Feed를 통해 만들기

RSS Feed에 대해

Django에서 Rss201rev2Feed와 Feed는 RSS를 위한 클래스다. RSS는 Really Simple Syndication의 줄임말로 웹사이트의 업데이트를 실시간으로 알려주는 양식이라고 볼 수 있다. 주로 뉴스 사이트, 블로그등에서 글이 발행되면 그걸 RSS를 구독하고 있는 곳에 뿌려주는 역할을 한다. 주로 핀터레스트, 트위터 등에서 RSS를 연결해서 사용할 수 있고 뉴스사이트에 들어가서 RSS를 구독해놓으면 그 뉴스에서 발행하는 기사들을 그 뉴스로 직접 방문하지 않고도 확인할 수 있다.

장고에서의 RSS

장고에서는 RSS를 만들기위한 두 가지 핵심클래스가 있다. 하나는 Rss201rev2Feed고 하나는 Feed이다.

Rss201rev2Feed

Rss201rev2Feed는 RSS 2.0.1 리비전 2 포맷으로 피드를 생성하도록 도와준다. 이 클래스는 표준 RSS 2.0 피드의 모든 요소를 지원한다. 주로 요즘은 RSS2.0을 쓰니 이걸 쓰면 될 것이다.

Feed (클래스)

Feed 클래스는 Django에서 사용자 정의 피드를 쉽게 생성할 수 있게 해주는 것이다. 위의 Rss201rev2Feed와 조합해서 사용해야 한다. 이 클래스를 이용하면 각 피드 아이템의 정보 즉 제목, 설명, 링크 등을 설정할 수 있다. 또한 그렇게 피드에 포함될 항목을 세세하게 결정하는 메소드를 오버라이딩해서 쓸 수도 있다.

실제 Feed 예제

    class PinterestLatestEntriesFeed(Feed):
        feed_type = PinterestRssFeed
        description = "Updates specifically formatted for Pinterest."

        def __init__(self, sub_domain, obj=None):
            super().__init__()
            self.obj = obj
            self.sub_domain = sub_domain
            self.title = f"RSS {sub_domain}"
            self.description = f"Visit your happiness! {sub_domain}.domain.com"
            self.link = f"https://{sub_domain}.{SITE_PLAIN_DOMAIN}/"

        def items(self):
            if self.obj:
                return self.obj
            else:
                # 이건 별 의미가 없음
                return Post.objects.none()

        def item_title(self, item):
            return f"{item.title}"

        def item_description(self, item):
            return f"[{item.title}]"

        def item_link(self, item):
            return f"https://{self.sub_domain}.{SITE_PLAIN_DOMAIN}/post/detail/{item.uid}/"

        def item_extra_kwargs(self, item):
            image_url = getattr(item, "thumbnail_relative_path", None)
            pub_date = getattr(item, "created", None)
            return {
                "image_url": f"https://{self.sub_domain}.{SITE_PLAIN_DOMAIN}/content/{image_url}"
                if image_url
                else "",
                "pub_date": item.created.strftime("%a, %d %b %Y %H:%M:%S +0000")
                if pub_date
                else "",
            }

위 코드를 살펴보자 우선

            feed_type = PinterestRssFeed
    description = "Updates specifically formatted for Pinterest."

이 부분은 이게 어떤 클래스이며 무슨 용도인지 보여준다. 그다음

    def __init__(self, sub_domain, obj=None):
    super().__init__()
    self.obj = obj
    self.sub_domain = sub_domain
    self.title = f"RSS {sub_domain}"
    self.description = f"Visit your happiness! {sub_domain}.domain.com"
    self.link = f"https://{sub_domain}.{SITE_PLAIN_DOMAIN}/"

이 부분에서는 처음에 이 클래스를 호출할 때 init을 설정할 수 있다. 사실 지금 여기서 다 해놓는 걸 추천한다. 그러면 따로 설정할 일이 없기 때문이다.

  def items(self):
    if self.obj:
        return self.obj
    else:
        # 이건 별 의미가 없음
        return Post.objects.none()

이 부분은 피드에 사용될 아이템들을 정의하는 부분이다. 쿼리셋을 넣어주는데 그 쿼리셋의 아이템들을 바탕으로 이제 개별 작업이 들어간다고 보면 된다. 아니면 쿼리셋을 밖에서 주지말고 items() 안에서 리턴해줘도된다.

    def item_title(self, item):
        return f"{item.title}"

    def item_description(self, item):
        return f"[{item.title}]"

    def item_link(self, item):
        return f"https://{self.sub_domain}.{SITE_PLAIN_DOMAIN}/post/detail/{item.uid}/"

    def item_extra_kwargs(self, item):
        image_url = getattr(item, "thumbnail_relative_path", None)
        pub_date = getattr(item, "created", None)
        return {
            "image_url": f"https://{self.sub_domain}.{SITE_PLAIN_DOMAIN}/content/{image_url}"
            if image_url
            else "",
            "pub_date": item.created.strftime("%a, %d %b %Y %H:%M:%S +0000")
            if pub_date
            else "",
        }

이 부분들은 그냥 함수 이름 그대로 결정된다. 각 아이템의 title, description, link 그리고 원한다면 그 외 키워드들을 넘겨주는 역할을 한다. 여기에서 입맛에 맞게 조절하면 된다.

실제 Rss201rev2Feed 예제


class PinterestRssFeed(Rss201rev2Feed):
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

def rss_attributes(self):
    attrs = super().rss_attributes()
    attrs["xmlns:media"] = "http://search.yahoo.com/mrss/"
    return attrs

def add_root_elements(self, handler):
    # super().add_root_elements(handler)
    handler.addQuickElement("title", self.feed["title"])
    handler.addQuickElement("link", self.feed["link"])
    handler.addQuickElement("description", self.feed["description"])
    sub_domain = get_subdomain_from_url(self.feed["link"])
    href = (
        f"https://{sub_domain}.{SITE_PLAIN_DOMAIN}/rss/rss.xml"
        if sub_domain
        else f"https://{SITE_PLAIN_DOMAIN}/rss.xml"
    )
    handler.addQuickElement(
        "atom:link",
        "",
        {
            "href": href,
            "rel": "self",
        },
    )
    # handler.addQuickElement("language", language)
    handler.addQuickElement(
        "lastBuildDate", timezone.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
    )

def add_item_elements(self, handler, item):
    super().add_item_elements(handler, item)

    mime_type, _ = guess_type(item["image_url"])
    if mime_type:
        handler.addQuickElement(
            "media:content", "", {"url": item["image_url"], "type": mime_type}
        )
    if item["pub_date"] != "":
        handler.addQuickElement("pubDate", item["pub_date"])

이제 코드들을 살펴보자

def rss_attributes(self):
  attrs = super().rss_attributes()
  attrs["xmlns:media"] = "http://search.yahoo.com/mrss/"
  return attrs

이 부분은 그 피드가 "http://search.yahoo.com/mrss/"이 형식을 따름을 알려주는 것이다. 이건 yahoo라고 해서 yahoo링크가 아니라 단지 그 형식을 나타내는 역할을 한다.


      def add_root_elements(self, handler):
        # super().add_root_elements(handler)
        handler.addQuickElement("title", self.feed["title"])
        handler.addQuickElement("link", self.feed["link"])
        handler.addQuickElement("description", self.feed["description"])
        sub_domain = get_subdomain_from_url(self.feed["link"])
        href = (
            f"https://{sub_domain}.{SITE_PLAIN_DOMAIN}/rss/rss.xml"
            if sub_domain
            else f"https://{SITE_PLAIN_DOMAIN}/rss.xml"
        )
        handler.addQuickElement(
            "atom:link",
            "",
            {
                "href": href,
                "rel": "self",
            },
        )
        # handler.addQuickElement("language", language)
        handler.addQuickElement(
            "lastBuildDate", timezone.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
        )

이 부분을 이용하면 이제 디테일하게 피드 자체의 설정을 할 수 있게 된다. 원하는 속성을 넣을수도 있고 자기가 바라는대로 커스터마이징이 가능하다. 나같은 경우는

    handler.addQuickElement(
      "lastBuildDate", timezone.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
    )

이 부분을 통해 발행날짜를 결정할 수 있도록 했다.

def add_item_elements(self, handler, item):
    super().add_item_elements(handler, item)

    mime_type, _ = guess_type(item["image_url"])
    if mime_type:
        handler.addQuickElement(
            "media:content", "", {"url": item["image_url"], "type": mime_type}
        )
    if item["pub_date"] != "":
        handler.addQuickElement("pubDate", item["pub_date"])

이제 이 부분에선 각 아이템에 보충해야 할 내용들을 넣을 수 있다. 자기가 원하는 사이트의 양식에 맞게 처리해주면 된다. 사이트마다 RSS 피드를 등록할때 넣으면 편리한 정보같은것이 따로 존재하기 때문이다.

만들어진 RSS 예시


<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:media="http://search.yahoo.com/mrss/">
<channel>
    <title>KoreaTeam Win RSS ko10</title>
    <link>https://ko10.koreateam.win/</link>
    <description>Updates specifically formatted for Pinterest. ko10.koreateam.win</description>
    <atom:link href="https://ko10.koreateam.win/rss/rss-ko10.xml" rel="self" />
    <lastBuildDate>Wed, 22 Nov 2023 05:04:01 +0000</lastBuildDate>

    <item>
        <title>고려의 소드 마스터였던 척준경의 일화.jpg</title>
        <link>https://ko10.koreateam.win/post/detail/0f8c59f5c7fe42ce9c975b5fd155362a/</link>
        <description>고려의 소드 마스터였던 척준경의 일화.jpg</description>
        <guid>https://ko10.koreateam.win/post/detail/0f8c59f5c7fe42ce9c975b5fd155362a/</guid>
        <media:content type="image/jpeg"
            url="https://ko10.koreateam.win/ktw_thumbnails/0f8c59f5c7fe42ce9c975b5fd155362a/7d078af9e08f4643b04126cb98db8ae2.jpg" />
        <pubDate>Tue, 21 Nov 2023 18:38:31 +0000</pubDate>
    </item>

    <item>
        <title>아반떼 할부 금리 19.5% 지른 상남자.JPG</title>
        <link>https://ko10.koreateam.win/post/detail/712df43d8a3a495bb7876126dcdcc7ab/</link>
        <description>아반떼 할부 금리 19.5% 지른 상남자.JPG</description>
        <guid>https://ko10.koreateam.win/post/detail/712df43d8a3a495bb7876126dcdcc7ab/</guid>
        <media:content type="image/jpeg"
            url="https://ko10.koreateam.win/ktw_thumbnails/712df43d8a3a495bb7876126dcdcc7ab/8182e2ad3fa94364a7d8f1e3040bd33c.jpg" />
        <pubDate>Tue, 21 Nov 2023 18:38:31 +0000</pubDate>
    </item>

    <item>
        <title>조상이외국인인우리나라성씨들.jpg</title>
        <link>https://ko10.koreateam.win/post/detail/60b94e7f54674702bae2bfa2b250a225/</link>
        <description>조상이외국인인우리나라성씨들.jpg</description>
        <guid>https://ko10.koreateam.win/post/detail/60b94e7f54674702bae2bfa2b250a225/</guid>
        <media:content type="image/jpeg"
            url="https://ko10.koreateam.win/ktw_thumbnails/60b94e7f54674702bae2bfa2b250a225/088376ca76a54fcf9f6de1168eed6978.jpg" />
        <pubDate>Tue, 21 Nov 2023 18:38:33 +0000</pubDate>
    </item>

    <item>
        <title>순수한 사람의 마음을 가지고 논 중학생들.jpg</title>
        <link>https://ko10.koreateam.win/post/detail/2466eb86b7384add830544c30c55a840/</link>
        <description>순수한 사람의 마음을 가지고 논 중학생들.jpg</description>
        <guid>https://ko10.koreateam.win/post/detail/2466eb86b7384add830544c30c55a840/</guid>
        <media:content type="image/jpeg"
            url="https://ko10.koreateam.win/ktw_thumbnails/2466eb86b7384add830544c30c55a840/a82b55fca74a46a4bd150438dd6489d1.jpg" />
        <pubDate>Tue, 21 Nov 2023 18:38:35 +0000</pubDate>
    </item>
    <item>
        <title>길냥이들 겨울 아지트</title>
        <link>https://ko10.koreateam.win/post/detail/b32577aa6aba4340b60a6ef13d93cab3/</link>
        <description>길냥이들 겨울 아지트</description>
        <guid>https://ko10.koreateam.win/post/detail/b32577aa6aba4340b60a6ef13d93cab3/</guid>
        <media:content type="image/jpeg"
            url="https://ko10.koreateam.win/ktw_thumbnails/b32577aa6aba4340b60a6ef13d93cab3/8ee2776345734e8ead1042a25f3b93ed.jpg" />
        <pubDate>Tue, 21 Nov 2023 18:38:36 +0000</pubDate>
    </item>
</channel>
</rss>

브라우저에서 확인할 수 있는 rss 모습 위와 같이 예제 코드들에서 보았던 부분들이 들어가있다는 것을 확인할 수 있다. 상당히 귀찮은 작업이 될 수 있는데 장고에서 주어진 클래스들을 잘 설정한다면 정말 딸깍 한번에 만들어낼 수 있다.