Trang mồ côi (orphan page) là trang không có link nội bộ nào trỏ tới. Nó vẫn có thể được Google tìm thấy qua sitemap, nhưng thiếu tín hiệu để hiểu trang thuộc chủ đề nào và quan trọng đến đâu, còn người đọc không có đường vào. Bài này viết một crawler nhỏ để dựng đồ thị link nội bộ, tìm trang mồ côi và xem trang nào nhận nhiều link nhất.
Google đọc link thế nào
Theo hướng dẫn về link của Google Search Central, Google chỉ chắc chắn đọc được link viết bằng thẻ <a> có thuộc tính href. Nút bấm chạy JavaScript không có href, hoặc link nằm trong ảnh mà không có chữ thay thế, đều có thể bị bỏ qua. Google cũng nói mọi trang quan trọng nên có link từ ít nhất một trang khác trên website.
Crawl và dựng đồ thị
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from collections import defaultdict
def crawl(goc: str, gioi_han: int = 500):
mien = urlparse(goc).netloc
hang, da_xem = [goc], set()
vao = defaultdict(set) # url -> tập trang trỏ tới nó
anchor = defaultdict(list) # url -> danh sách anchor
while hang and len(da_xem) < gioi_han:
url = hang.pop(0)
if url in da_xem:
continue
da_xem.add(url)
r = requests.get(url, timeout=15)
if "text/html" not in r.headers.get("content-type", ""):
continue
soup = BeautifulSoup(r.text, "html.parser")
for a in soup.select("main a[href], article a[href]"):
dich = urljoin(url, a["href"]).split("#")[0]
if urlparse(dich).netloc != mien or dich == url:
continue
vao[dich].add(url)
anchor[dich].append(a.get_text(" ", strip=True))
if dich not in da_xem:
hang.append(dich)
return da_xem, vao, anchor
Selector main a, article a chỉ lấy link trong thân bài. Link trong menu, footer và sidebar xuất hiện trên mọi trang, nên nếu tính cả thì mọi trang đều "có link" và trang mồ côi thật bị che mất.
Tìm trang mồ côi
Crawler chỉ đi được tới trang có link, nên trang mồ côi thật sự sẽ không bao giờ xuất hiện trong da_xem. Phải so với một danh sách khác, thường là sitemap:
import xml.etree.ElementTree as ET
def url_trong_sitemap(sitemap_url: str) -> set:
xml = requests.get(sitemap_url, timeout=15).text
ns = {"s": "http://www.sitemaps.org/schemas/sitemap/0.9"}
return {loc.text.strip() for loc in ET.fromstring(xml).findall(".//s:loc", ns)}
def mo_coi(sitemap: set, vao: dict) -> list:
return sorted(u for u in sitemap if not vao.get(u))
Với WordPress dùng Yoast, sitemap là file chỉ mục trỏ tới nhiều sitemap con, nên cần đọc đệ quy.
Trang nào nhận nhiều link nhất
def xep_hang(vao: dict, n: int = 15):
return sorted(((len(v), u) for u, v in vao.items()), reverse=True)[:n]
So danh sách này với danh sách trang quan trọng về kinh doanh. John Mueller của Google khuyên nhìn internal link một cách chiến lược: cấu trúc link nên phản ánh trang nào anh em quan tâm nhất. Nếu trang dịch vụ chính nằm cuối bảng còn trang "giới thiệu" đứng đầu, cấu trúc đang nói ngược với ưu tiên.
Soát anchor
Từ anchor[url], đếm các anchor rỗng (link ảnh không alt), anchor chung như "xem thêm", và anchor lặp. Một trang mà mọi link trỏ về dùng đúng một cụm từ là dấu hiệu không tự nhiên. Khung chia anchor theo 5 nhóm, cùng năm chiến thuật xây liên kết nội bộ cho người viết nội dung, có trong bài Internal link là gì trên Onic.vn.
Lưu ý khi chạy
- Crawl chậm, có
time.sleepgiữa các request, và chỉ chạy trên website của chính mình. - Chuẩn hóa URL (dấu gạch chéo cuối, tham số) trước khi so, nếu không một trang sẽ bị đếm thành nhiều trang.
- Không cần gắn
nofollowcho link nội bộ; nếu crawler thấy nhiều link nội bộ córel="nofollow", đó là chỗ cần xem lại.
Top comments (0)