75 장고 검색엔진최적화 - json-ld 적용
75.1 구글검색 갤러리
구글 검색엔진에게 내 사이트의 특성에 맞는 구조화된 데이터를 제공함으로써 구글 검색 결과를 다른 글들과 다르게 표현할 수 있게 됩니다.
구글검색 갤러리에서 어떤 데이터를 제공하면 어떤 결과가 나오는지 미리 확인 할 수 있으며, 기능가이드를 통해 요구하는 데이터를 확인 할 수 있습니다.
검색갤러리 : https://developers.google.com/search/docs/appearance/structured-data/search-gallery
내 글이 남들과 다른 구글검색 결과화면을 원하신다면 잘 살펴보시면 좋습니다.
75.2 구조화된 데이터 기능가이드 - HowTo
제 블로그는 step by step 형식으로 내용을 작성하였기에 방법(howto type) 형식이 맞는거 같습니다.
json-ld, 마이크로데이터 2가지중 하나로 제공하면 된다고 가이드에 나와있습니다.
기능가이드>방법 : https://developers.google.com/search/docs/appearance/structured-data/how-to
저는 json-ld로 장고에 만들겠습니다.
75.3 장고 django-json-ld 모듈 설치 - base.py
파이썬에 django-json-ld 모듈을 설치합니다.
pip install django-json-ld
모듈 설치 완료 후 Setting 파일인 base.py에 django_json_ld app을 추가하고 JSON_LD_EMPTY_INPUT_RENDERING 등의 옵션을 설정합니다.
django_json_ld 모듈 옵션
| 옵션 | 설명 | 
|---|---|
| JSON_LD_CONTEXT_ATTRIBUTE | CBV에서 context에 등록되는 이름입니다. 기본 sd | 
| JSON_LD_MODEL_ATTRIBUTE | JsonLdDetailView 사용시 모델 프라퍼티 이름입니다. 기본 sd | 
| JSON_LD_DEFAULT_CONTEXT | CBV에서 기본 컨텍스트 입니다. 기본은 https://schema.org/ | 
| JSON_LD_INDENT | 디버그 모드에서 사용되며, json을 들여쓰기하여 보기좋게 표시해줍니다. | 
| JSON_LD_DEFAULT_TYPE | CBV 사용시 기본유형입니다. 기본 Thing | 
| JSON_LD_GENERATE_URL | CBV 를 사용할 때 json-ld의 url을 생성합니다. 기본 True | 
| JSON_LD_EMPTY_INPUT_RENDERING | json-ld가 없으면 아무것도 렌더링 하지 않는 옵션입니다. strict기본적으로 TemplateSyntaxError를 발생시킵니다. silent아무것도 렌더링하지 않습니다. generate_thing현재 페이지의 URL로 객체를 생성합니다 | 
pypi.org에서 설치 방법과 사용방법을 확인할 수 있습니다.
75.4 template에 render_json_ld 추가 - base.html
75.5 JsonLdContextMixin - view.py 구현
pypi.org 가이드에서는 JsonLdContextMixin, JsonLdDetailView의 사용법을 알려주고 있습니다.
pyp django-json-ld : https://pypi.org/project/django-json-ld/
테이블 1개만 사용했으면 가이드의 Class-Based View example 처럼 사용해도 되겠지만
저는 2개의 테이블을 조인해서 사용하고 있어 그대로 사용할 수 가 없습니다.
view 구현시 query_set을 내용을 get_structured_data()에 담아 json-ld를 만들어야 되는데 
get_structured_data()가 get_context_data()보다 더 빨리 처리되어  
model에서 가져온 데이터를 담을 수 없었습니다.
그래서 get_context_data에서 get_structured_data()을 가져와 처리하는 방식으로 변경하였습니다.
#myapp/blog/views.py
from django.shortcuts import render
from django.views import generic
from django_json_ld.views import JsonLdContextMixin
# from django_json_ld.views import JsonLdDetailView
from .models import PyBlog
from .models import PyBlogDetail
#sidebar
from myapp.common.common_views import MenuMixin
import time
class BlogDetail(MenuMixin, JsonLdContextMixin, generic.DetailView):
    model = PyBlogDetail
    template_name   = "blog/blogDetail.html" 
    structured_data = {"@type": "HowTo",
                        "estimatedCost": {"@type": "MonetaryAmount","currency": "USD","value": "0"},
                        "supply": [{"@type": "HowToSupply","name": "server"}, ],
                        "tool": [{"@type": "HowToTool","name": "visual studio code"}, {"@type": "HowToTool","name": "python"}, ],                    
                        "totalTime": "P1D",
                       }
    def get_context_data(self, **kwargs):       
        context   = super().get_context_data(**kwargs)              
        query_set = PyBlog.objects.filter(id=self.kwargs['pk'], use_yn = 'Y')
        context['pageInfo'] = query_set[0].get_page_info()      
        page_title = query_set[0].title
        query_set = query_set.values('id','title','update_dt','regist_dt',
                                    'pb_detail__id',
                                    'pb_detail__detail_id',
                                    'pb_detail__sub_title','pb_detail__img_url','pb_detail__img_size','pb_detail__new_img_url',
                                    'pb_detail__content_body'
                                    ).order_by('pb_detail__sub_title')
        # print("query_set[0].pb_detail__content_body : ", query_set[0]['pb_detail__content_body'])
        page_img_url = query_set[0]['pb_detail__img_url']
        page_desc = query_set[0]['pb_detail__content_body'].replace('```','').replace('"','')[:140]
        context['dataList'] = query_set
        context['pageInfo'].update({ "title": page_title, "get_descript":' '.join(page_desc.split()), "img_url":page_img_url})
        context.update(self.getMenuList)        
        #json-ld 생성
        self.setJsonLD(query_set, page_title, context)
        return context
    def replaceImg(self, new_img, org_img):
        if new_img != None and new_img !='':
            return new_img.replace('/image/upload/','/image/upload/h_306,w_406,q_auto:best/')
        if org_img == None or org_img =='':
            return ''
        return org_img      
    def replaceText(self, text):
        return text.replace("```"," ").replace("<br/>"," ").replace("<br>"," ").replace("\r","").replace("\t","").replace("\n"," ").replace("<","").replace(">","")
    def setJsonLD(self, query_set, page_title, context):
        structured_data = super().get_structured_data()
        structured_data.update({'name':page_title, 'image':{ "@type": "ImageObject","height": "306","width": "406",
                                                "url": self.replaceImg(query_set[0]['pb_detail__new_img_url'],query_set[0]['pb_detail__img_url']),                           
                                               },
                 })     
        stepList = []       
        for i in query_set:
            stepList.append({
                  "@type": "HowToStep",
                  "url": f"{structured_data['url']}#{i['pb_detail__id']}",
                  "name": f"{i['pb_detail__sub_title']}",
                  "itemListElement":[{"@type": "HowToDirection", "text": f"{self.replaceText(i['pb_detail__content_body'][:400])}"}],
                  "image": {"@type": "ImageObject",
                            "url": f"{self.replaceImg(i['pb_detail__new_img_url'],i['pb_detail__img_url'])}",
                            "height": "306",
                            "width": "406"}
                })          
        structured_data.update({'step':stepList})       
        context.update({'sd':structured_data})
75.6 구조화 데이터 테스트
작업한 모든 결과를 서버에 반영 후에 
구글에서 제공하는 스키마 마크업 테스트 도구를 통해 잘 적용되 었는지 확인합니다.
구조화 데이터 테스트 도구로 이동하여 
구조화 데이터가 적용된 페이지의  URL을 입력합니다.
테스트도구 URL : https://developers.google.com/search/docs/appearance/structured-data
75.7 구조화 데이터 테스트 확인
에러 없이 잘 적용되었다면 
구글에 사이트맵을 다시 읽도록 재요청 해주시면 됩니다.
구글 크롤링 재요청: https://www.google.com/ping?sitemap=https://pythonblog.co.kr/sitemap.xml
제가원하는대로 구글검색결과에서 남들과 다르게 나왔으면 좋겠습니다.
75.8 구글웹마스터도구 - 서치콘솔 확인
구조화가 적용된 페이지를 구글에서 다시 크롤링하면 
 구글 웹마스터 도구 - Google Search Console 에서
 
위 그림은 제가 운영하는 다른사이트에 리뷰스니펫을 적용했을때 구조화데이터에 관련된 섹션이 
나온 내용입니다.

몇일 후에 모바일에서 확인해보니 위와 같이 
 구글 검색엔진에 howTo가 적용되어 검색 결과에 반영 된 모습니다.
75.9 구조화데이터 관련 링크 모음
| json-ld 관련 | 링크 | 
|---|---|
| 구글검색엔진 검색갤러리 | https://developers.google.com/search/docs/appearance/structured-data/search-gallery | 
| PYPI django-json-ld | https://pypi.org/project/django-json-ld/ | 
| GITHUB django-json-ld | https://github.com/hiimdoublej/django-json-ld | 
| 구조화 데이터 테스터 도구 | https://developers.google.com/search/docs/appearance/structured-data | 
| 구글서치콘솔 | https://search.google.com/search-console | 
| json-ld 모듈 | https://json-ld.org/ - json-ld 구현을 위한 각 언어별로 제공되는 모듈 및 명세서 등을 확인 할 수 있습니다. | 
 
               
                         
                         
                         
                         
                         
                         
                         
                         
                         
                         
                         
                         
                         
                         
                         
                        