내부 링크는 어느 정도 걸고 있다고 생각했습니다. 세어보니 글당 평균 3개였고, 흔히 권장되는 수준이었습니다.
정리하는 개발자 워니즈입니다. 이 글에서는 제 블로그 216편을 전수 조사해 나온 숫자와, 같은 조사를 직접 돌려보는 방법을 다룹니다.
결론부터 말씀드리면 평균은 아무것도 알려주지 않습니다. 평균 3개인 상태에서 글의 45%는 아무도 링크해주지 않는 고아였습니다. 오늘 하나만 하신다면 평균 대신 고아 글의 개수를 세어 보세요.

왜 이게 문제인가
검색 엔진은 링크를 따라 이동합니다. 어떤 글에도 링크가 걸려 있지 않으면 그 글로 가는 길이 사이트맵밖에 없습니다. 길이 하나뿐인 글은 늦게 발견되고, 중요도도 낮게 평가됩니다. 구글도 링크 모범 사례 문서에서 크롤러가 링크를 따라간다는 점을 전제로 설명합니다.
실제로 저는 이걸 색인 문제로 먼저 겪었습니다. 제 글의 절반 이상이 구글 색인에 아예 없던 적이 있는데, 그때는 원인을 사이트맵 쪽에서만 찾았습니다. 당시 확인 순서는 색인 누락을 확인한 글에 적어뒀습니다. 링크 구조는 그때 보지 않았고, 이번에 세어보고서야 절반 가까이가 고립돼 있다는 걸 알았습니다.
여기서 착각이 생깁니다. 내부 링크 총량은 늘어나는데 한쪽에만 몰리기 때문입니다. 저는 쿠버네티스 시리즈를 쓰면서 글끼리 서로 촘촘히 연결해 뒀습니다. 그 몇 편이 평균을 혼자 끌어올리고 있었고, 나머지는 그대로 방치돼 있었습니다.
1. 내부 링크를 전부 세는 방법
플러그인 없이 REST API로 셀 수 있습니다. 발행 글을 전부 가져와 본문에서 자기 도메인 링크만 추립니다.
import re, json, base64, urllib.request, collections
SITE = "https://example.com"
AUTH = "Basic " + base64.b64encode(b"아이디:앱비밀번호").decode()
def api(path):
r = urllib.request.Request(SITE + "/wp-json" + path)
r.add_header("Authorization", AUTH)
return json.load(urllib.request.urlopen(r, timeout=90))
posts = []
for page in range(1, 20):
batch = api(f"/wp/v2/posts?per_page=50&page={page}&status=publish&_fields=id,title,link,content")
if not batch:
break
posts += batch
if len(batch) < 50:
break
print(len(posts), "편")
앱 비밀번호는 워드프레스 관리자의 사용자 프로필에서 발급합니다. 일반 로그인 비밀번호가 아닙니다.
2. 고아 글 목록 뽑기
글 주소를 식별자로 바꾼 뒤, 본문에 등장하는 자기 도메인 링크를 세면 됩니다. 자기 자신을 가리키는 링크는 빼야 숫자가 부풀지 않습니다.
slug_of = {}
for p in posts:
m = re.search(r'/(\d{4}/\d{2}/\d{2}/[^/]+)/?$', p["link"])
if m:
slug_of[m.group(1)] = p["id"]
inbound = collections.Counter()
outbound = {}
for p in posts:
found = set()
for href in re.findall(r'href="(https?://[^"]+)"', p["content"]["rendered"]):
mm = re.search(r'/(\d{4}/\d{2}/\d{2}/[^/?"#]+)', href)
if mm and mm.group(1) in slug_of and slug_of[mm.group(1)] != p["id"]:
found.add(slug_of[mm.group(1)])
outbound[p["id"]] = len(found)
for t in found:
inbound[t] += 1
orphan = [p for p in posts if inbound[p["id"]] == 0]
print("고아", len(orphan), "편 /", len(posts), "편")
주소 형식이 /글제목/ 처럼 날짜가 없는 구조라면 정규식의 날짜 부분만 바꾸면 됩니다.
3. 숫자를 읽는 법
제 블로그 216편의 결과입니다.
| 항목 | 값 |
|---|---|
| 전체 내부 링크 | 653개 |
| 글당 평균 | 3.02개 |
| 다른 글로 나가는 링크가 0개인 글 | 128편 (59%) |
| 아무도 링크해주지 않는 글 | 99편 (45%) |
| 가장 많이 링크받은 글 | 20회 |
평균 3.02개는 나쁘지 않은 숫자입니다. 그런데 절반이 넘는 글이 다른 글을 하나도 링크하지 않고 있었습니다. 링크를 20회씩 받은 상위 글들이 평균을 만들고 있었을 뿐입니다.
평균 하나만 보면 이 상태가 안 보입니다. 0인 글이 몇 편인지를 따로 세야 드러납니다.
4. 어디부터 고칠 것인가
전부 고치려 하면 시작을 못 합니다. 순서를 정했습니다.
- 검색으로 들어오는 글에서 나가는 링크를 먼저 만든다. 유입이 있는 글이 통로 역할을 합니다.
- 고아 글 중 최근 것부터 연결한다. 오래된 글은 내용이 낡아 연결해도 효과가 적습니다.
- 주제가 같은 글끼리 묶는다. 억지로 이어붙인 링크는 사용자가 누르지 않습니다.
- 새 글을 쓸 때 기존 글 한 편을 반드시 링크한다. 이게 제일 싸게 먹힙니다.
마지막 항목만 지켜도 고아는 더 늘어나지 않습니다. 이 글도 그 규칙을 지켜서 위에 기존 글 하나를 걸어뒀습니다. 규칙을 세워두면 매번 고민할 일이 없어집니다.
한 가지 덧붙이자면, 링크를 걸 때 앵커 텍스트를 ‘여기’나 ‘이 글’로 쓰지 않는 편이 낫습니다. 어떤 내용인지 앵커에 드러나야 사람도 누르고 검색 엔진도 관계를 이해합니다. 저는 예전 글에서 ‘자세한 내용은 여기’라고만 써둔 게 꽤 있었는데, 이번에 세면서 같이 눈에 들어왔습니다.
내부 링크에 관해 자주 묻는 것
Q. 내부 링크는 몇 개가 적당한가요?<br>개수보다 0인 글을 없애는 게 먼저입니다. 제 경우 평균은 이미 3개였는데도 절반이 넘는 글이 0이었습니다. 평균을 목표로 잡으면 이 상태가 그대로 남습니다.
Q. 관련 글 위젯이 있으면 내부 링크가 되나요?<br>자동 위젯은 주제 관련도가 떨어지는 경우가 많고, 테마에 따라 본문 밖에 놓여 비중이 낮게 평가되기도 합니다. 본문 안에서 문맥에 맞게 건 링크와 같게 보긴 어렵습니다.
Q. 오래된 글까지 전부 손봐야 하나요?<br>아닙니다. 검색 유입이 있는 글과 최근 글만 손봐도 충분합니다. 유입이 없는 오래된 글은 링크를 걸어도 전달할 힘 자체가 없습니다.
Q. 플러그인으로 자동으로 걸면 안 되나요?<br>자동 삽입 플러그인은 키워드가 겹치기만 하면 문맥과 무관하게 링크를 답니다. 관계가 없는 글끼리 이어지면 사람이 누르지 않고, 그런 링크는 오래 쌓일수록 본문도 지저분해집니다. 저는 개수를 늘리는 것보다 0인 글을 없애는 쪽이 먼저라고 봅니다.
정리
- 평균 내부 링크 수는 상태를 알려주지 않습니다. 몰려 있어도 평균은 올라갑니다.
- 0인 글의 개수를 따로 세세요. 저는 59%가 나가는 링크 0개, 45%가 고아였습니다.
- REST API로 플러그인 없이 전수 조사가 가능합니다. 자기 자신 링크는 제외하세요.
- 고칠 때는 유입 있는 글과 최근 글부터 손댑니다.
- 새 글마다 기존 글 하나를 링크하는 규칙 하나면 고아는 더 안 늘어납니다.
오늘 하나만 하신다면, 위 코드로 고아 글 개수만 세어 보세요. 평균과 전혀 다른 숫자가 나올 겁니다.
함께 읽으면 좋은 글
- [블로그운영] 제 블로그 글 절반 이상이 구글 색인에 없었습니다 — 글의 절반이 구글 색인에 없던 이야기
- [블로그운영] 블로그 서버비, 프리티어 끝나면 얼마 나올까 — 서버비를 0원으로 만든 과정
- [블로그운영] 워드프레스 이미지가 안 보일 때 mixed content 해결하는 법 — 이미지가 안 뜨던 원인을 찾은 기록