Blog Post and URLs Relationship Internal

本ブログでは、全記事のメタデータを HTML ビルド時に解析し、Cypher クエリに変換した上で Neo4j Aura に保存しています。

今回、記事本文に含まれる外部サイトへのリンク数も解析対象に付与しました。 データモデリングの一例としてご紹介します。

課題

ブログの記事には、Wikipedia やドキュメントへの外部リンクを追加することがよく有ります。

外部リンクの問題は、時々リンク先がリンク切れを起こしており、読者の方がクリックしても本来意図した情報にたどり着くことができない点です。

そこで、ブログ記事に含まれる外部リンクをまずは分析できるようにする必要が有りました。もちろん、「どの記事からリンクされているのか」という関係性もモデルに含む必要 がありました。

また、ブログの価値を向上するために、どの外部リンクへのリンクをよく貼っているのか、どういった記事からどの種類のサイトへのリンクを貼ることが多いのか、分析していく必要が有りました。

解決

以前 "Blog Relevant Tags Internal" の記事でもご紹介したとおり、本ブログは Gatsby を利用して、事前に静的 HTML をビルドしています。今回も、そのビルドプロセスで全記事のメタデータを解析し、Cypher クエリに変換し、Neo4j Aura に Write する形を取りました。

実装

本ブログは、Gatsby のプラグインである gatsby-transformer-remark を利用しています。記事は Markdown ファイルで書いていますが、ビルド時にこのプラグインが remark.js を用いて Abstract Syntax Tree (AST) に変換し、最終的には HTML へと変換して出力しています。

remark.js では、unified.js が策定している一連の API に沿った形で AST を定義しています。allMarkdownRemark が返却するフィールドの中にある html がそれに該当します。

{
    postsRemark: allMarkdownRemark(
        limit: 100
    ) {
        edges {
            node {
                id
                html
            }
        }
    }

返却結果のイメージとしては、以下のような AST が JSON で返却されます。

"html": {
    "type": "root",
    "children": [
    {
        "type": "element",
        "tagName": "h3",
        "properties": {},
        "children": [
            {
                "type": "text",
                "value": "About This Blog"
            }
        ]
    },
    {
        "type": "text",
        "value": "Hello, world!\n"
    },

ここまで手に入ったら、後は AST を歩いて適切な Cypher クエリに変換するコードを書くだけです。

データモデリング

データモデリングとしてはシンプルです。記事を表す (:Post) ノードが、それぞれリンクを示す (:URL) ノードに対して、-[:INCLUDES_LINK_TO] リレーションを含みます。

MERGE (p:Post { slug: '...', title: '...' })
MERGE (u:URL { hostname: $hostname, pathname: $pathname, protocol: $protocol, href: url.href})
MERGE (p)-[:INCLUDES_LINK_TO]->(u)`

また、Hostname ごとの分析を容易にしたかったので、(:Hostname) ノードも作成しました。(:URL) ノードに対して、-[:IS_HOSTED_AT]-> リレーションを作成しています。

MERGE (h:Hostname { value: $hostname })
MERGE (h)<-[:IS_HOSTED_AT]-(u)`

執筆時点における (:Post), (:URL), (:Hostname) の全体像は以下のようになっています。

Post and URLs Relationship

利用例

被リンク数の多い Hostname

以下の Cypher クエリでは、被リンクが 3 つ以上の外部サイトの Hostname を、被リンク数とともにテーブルで表示させます。

MATCH p=(h:Hostname)<-[:IS_HOSTED_AT]-(:URL)<-[:INCLUDES_LINK_TO]-(:Post)
WITH h, count(p) as score
WHERE score >= 3
RETURN h.value as hostname, score
ORDER BY score DESC

例えば、執筆時点では以下のような結果になっています。

hostname score
neo4j.com 17
en.wikipedia.org 4
www.linkedin.com 3

被リンク数の多い URL

以下の Cypher クエリでは、被リンク数が 3 つ以上の外部サイトの URL を表示させます。Hostname に正規化させない結果を表示させたい場合は以下を使います。

MATCH p=(u:URL)<-[:INCLUDES_LINK_TO]-(:Post)
WITH u, count(p) as score
WHERE score >= 3
RETURN u.href, score

最後に

以上、本サイトに導入した記事と外部 URL との関連性を分析するためのデータモデリングと、実際の仕様例を幾つか紹介しました。

2021-10-09