|||||||||||||||||||||

なんぶ電子

- 更新: 

ブログのフィードを作成してHubへ通知

検出 - インデックス未登録

「検出 - インデックス未登録」

Search Consoleで「検出 - インデックス未登録」のまま、いつまでたってもページがインデックスされない問題はあちこちで見聞きします。

筆者もそれに悩んでいる一人です。見聞によれば、内部リンクを充実させて「検出 - インデックス未登録」の対象になっているページにリンクの貼られた状態で、再度クロールを要求すれば来てくれるということでした。

内部リンクは重視してこなかったので、書いてあるように内部リンクを修正してみましたが、そもそも内部リンクの変更(ページの修正)がうまく伝わらりません。修正後にクロールのリクエストはしているのですが、クローラが来ていないようです。

Search Consoleの「設定」の「クロールの統計情報」を見てみると、オーガニックサーチから流入のあったページが主にリストアップされています。内部リンクを、「詳細」からみてみると、インデックスされているページをターゲットに被リンク一覧を出してみると、先のようなページのURLが多い印象でした。暫定的な結論としてページ修正のシグナルををうまくSearch Consolseに伝えてないのではないかと考えました。

そこでいままであまり利用していなかったfeed(フィード)を使ってみようと思いました。

主に筆者の利用しているBloggerについて書いていますが、他のサイトとも共通する部分も多いと思います。

Bloggerフィードの変換

Bloggerで自動生成されるフィードはいくつかありますが、どれも余分な情報がついているような気がします。中には公開前の記事内容も含むものがあります。そのような事を考慮した結果、筆者は今までSearch ConsoleにBloggerのフィードを登録していませんでした。ですので、まずはこのようなフィードの問題を解決します。

注意

以降で紹介するフィードの変換・再配置は、Blogger以外にフィードを配置(ホスト)できるサイトを所有していないとできませんので、あらかじめご了承ください。ちなみにGoogleが管理しているfirebaseにはホスティングの無料枠がありますので、めぼしいホストサーバーが見つからない場合は検討してみてはいかがでしょうか。

Google検索セントラルmedifund:「RSSとATOMの違いと特徴は?配信方法やサンプルフォーマットあり」を参考に元のBloggerのフィードを変換させるコードをNode.jsを使って書きました。

ちなみに、フィードにまつわるRFCにはRFC4287(The Atom Syndication Format)RFC5005(Feed Paging and Archiving)などがあります。

最終的に出力するフィードはAtomで、次の項目にしぼりました。

  • id
  • updated
  • title
  • subtitle

    コンテナ要素(entory要素の外側にある最初に記述する部分)にだけ設定します。

  • link

    あとからコンテナ要素に「hub」と「self」を追加しますが、ここでは「alternate」のみです。

  • author
  • published

    なくてもいいと思いますが、自分でフィードを見た際に利用できそうだったので出力しておきます。

変換元はhttps://Bloggerのアドレス/atom.xmlで出力されるAtomフィードとしました。ここで自動生成されるAtomフィード内のsummaryは今回の構成だと省略可能なようなので省略します。content要素も作成しません。Google検索セントラルでは、一番重要な項目はupdatedだといっています。

変換にはhtmlparser2ライブラリを使います、タグの開始と終了とテキストの検知毎にイベントを設定できる簡易型のパーサーです。これを使って不要な要素を取り除きます。

feedconv.js

const htmlparser2 = require("htmlparser2");
const fs = require('fs');

let outputDir = "d:¥¥";
async function getAtomFeed(strFeedData) {

  //固定項目
  const strAuthorXml = "<author><name>筆者</name><email>noreply@blogger.com</email></author>";
  const strSubtitleValue = "ブログサブタイトル";
  let intFdW = fs.openSync(outputDir + "/atom-feed.xml", "w");

  //パーサー定義
  let parserForBloggerFeed = new htmlparser2.Parser({
    onopentag(name, attributes) {
      parserForBloggerFeed.tagtree.push(name);

      switch (name) {
        case "entry":
          if (parserForBloggerFeed.blnentryStart == false) {
            //ヘッダ分記述
            fs.writeFileSync(intFdW, "<id>" + parserForBloggerFeed.strIdWk + "</id>");
            fs.writeFileSync(intFdW, "<updated>" + parserForBloggerFeed.strUdateWk + "</updated>");
            fs.writeFileSync(intFdW, "<title type='text'>" + parserForBloggerFeed.strTitleWk + "</title>");
            fs.writeFileSync(intFdW, "<subtitle type='text'>" + strSubtitleValue + "</subtitle>");
            fs.writeFileSync(intFdW, "<link rel='alternate' type='text/html' href='" + parserForBloggerFeed.strLinkWk + "'/>");
            fs.writeFileSync(intFdW, strAuthorXml);
          }

          parserForBloggerFeed.blnentryStart = true;
          parserForBloggerFeed.strIdWk = "";
          parserForBloggerFeed.strUdateWk = "";
          parserForBloggerFeed.strPdateWk = "";
          parserForBloggerFeed.strTitleWk = "";
          parserForBloggerFeed.strContentWk = "";
          parserForBloggerFeed.strLinkWk = "";

          break;
        case "link":
          if (attributes.rel == "alternate") {
            parserForBloggerFeed.strLinkWk = attributes.href;
          }
          break;
        default:

      }

    },
    ontext(text) {
      switch (parserForBloggerFeed.tagtree[parserForBloggerFeed.tagtree.length - 1]) {
        case "summary":
          parserForBloggerFeed.strContentWk += text.replace(/[¥r¥n¥t]/g, '');
          break;
        case "subtitle":
          break;
        case "id":
          parserForBloggerFeed.strIdWk += text.replace(/[¥r¥n¥t]/g, '');
          break;
        case "title":
          parserForBloggerFeed.strTitleWk += text.replace(/[¥r¥n¥t]/g, '');
          break;
        case "published":
          parserForBloggerFeed.strPdateWk += text.replace(/[¥r¥n¥t]/g, '');
          break;
        case "updated":
          parserForBloggerFeed.strUdateWk += text.replace(/[¥r¥n¥t]/g, '');
          break;
        default:
      }
    },
    onclosetag(name) {
      parserForBloggerFeed.tagtree.pop();
      switch (name) {
        case "entry":
          fs.writeSync(intFdW, "<entry>");
          fs.writeSync(intFdW, "<id>" + parserForBloggerFeed.strIdWk + "</id>");
          fs.writeSync(intFdW, "<published>" + parserForBloggerFeed.strPdateWk + "</published>");
          fs.writeSync(intFdW, "<updated>" + parserForBloggerFeed.strUdateWk + "</updated>");
          fs.writeSync(intFdW, "<title type='text'>" + parserForBloggerFeed.strTitleWk + "</title>");
          //fs.writeSync(intFdW,"<summary type='text'>"+parserForBloggerFeed.strContentWk+"</summary>");
          fs.writeSync(intFdW, "<link rel='alternate' type='text/html' href='" + parserForBloggerFeed.strLinkWk + "'/>");
          fs.writeSync(intFdW, strAuthorXml);
          fs.writeSync(intFdW, "</entry>");
          break;
        default:

      }

    },
  });
  parserForBloggerFeed.blnentryStart = false;
  parserForBloggerFeed.strIdWk = "";
  parserForBloggerFeed.strUdateWk = "";
  parserForBloggerFeed.strPdateWk = "";
  parserForBloggerFeed.strTitleWk = "";
  parserForBloggerFeed.strSubTitleWk = "";
  parserForBloggerFeed.strContentWk = "";
  parserForBloggerFeed.strLinkWk = "";
  parserForBloggerFeed.tagtree = [];
 
  // ヘッダとページ用のヘッダは自作
  fs.writeSync(intFdW, "<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xml:lang='ja'>");

  parserForBloggerFeed.write(strFeedData);
  parserForBloggerFeed.end();

  fs.writeSync(intFdW, "</feed>");

  fs.closeSync(intFdW);
}

そうして出来上がったフィードの整合性をチェックには、Feed Validation Serviceを利用させていただきました。この結果では、整合性だけでなく、推奨項目やフィードのタイプ(RSS/Atom)やそのバージョンも表示してくれます。

サイトマップが完成したらフィードをWeb上にホストします。筆者はドメインを割り当てたfirebaseでホスティングしました。ホスティングができたらSearch Consoleに登録します。登録の方法はサイトマップと同じです。まずホストサイトの所有権の確認を行います。次に、フィードのアドレスをサイトマップ用の入力欄に入力するだけです。入力したアドレスが「送信されたサイトマップ」の一覧に追加され、「型」の欄がAtom(RSSを作った場合はRSS)になっていれば成功です。

PubSubHubbub

Search Consoleにフィードを登録するだけでも効力を発揮しますが、先のGoole検索セントラルではPubSubHubbubの利用を推奨していました。2022年の時点でそれが有効なのかどうかは微妙ですが。

Bloggerでは先にも書いたようにPubSubHubbub用のフィードが出力されています。また、記事を公開した際に自動で通知もされるようです。なのでこの後に説明するフィードリダイレクトを設定すればもしかしたら問題ないのかもしれませんが、フィードの配布サイトも変わるので念のためPubSubHubbubに通知するようにしておきます。

PubSubHubbubは世に出回るフィードを一括管理するためのハブで、これを利用することでフィードの変更がほぼリアルタイムにフィード購読者に伝えられるということです。このサービスはGoogleが管理していますので、ここへフィードの変更を伝えればクローラにも変更を伝えられることになります。

PubSubHubbubへの通知は、サイトマップにおけるpingと似たような形式をとります。利用するためには、先に作成したフィードにふたつのlinkヘッダーを加える必要があります。Bloggerではこれらのデフォルトで出力されていますが、先の変換の過程で取り除かれていました。

link要素は記事毎にも設定されていますが、ここで設定するのはコンテナ要素(entory要素の外側にある最初に記述する部分)だけでいいと思います。

<link rel='hub' href='https://pubsubhubbub.appspot.com/'/>
<link rel='self' type='application/atom+xml' href='フィードのアドレス'/>

フィードの変更をしたら、通知の設定をします。サイトマップのpingはgetパラメータで送信できましたが、フィードはPOSTする必要があります。ここでの要点をまとめると次のようになります。

  • 通知先:https://pubsubhubbub.appspot.com/
  • Content-Type: application/x-www-form-urlencoded
  • パラメータ
    • hub.mode: "publish"という文字列
    • hub.url: フィードのURL

application/x-www-form-urlencodedは通常のHTMLのformで送信する際に用いられる方法なので、自分でそのようにHTMLを書いて送信してもいいと思いますが、ここでは先ほど利用したNode.jsと、axiosライブラリを使って送信するスクリプトを紹介します。ちなみにWordPressではそれ用のプラグインが存在します。

feedconv.js

const axios = require('axios');
//フィードの更新通知
let params = new URLSearchParams();
const strPubUrl = "https://pubsubhubbub.appspot.com/";

params.append('hub.mode', 'publish');
params.append('hub.url', 'フィードのURL');

axios.post(strPubUrl, params).then(function (response) { 
    console.log(response);
});

// サイトマップ ping(参考)
axios.get("https://www.google.com/ping?sitemap=サイトマップのURL").then(function (response) {
    console.log(response.data);
});

POSTする先のアドレスは通常だとステータスコードが200でトップページが表示されますが、フィードの通知時はレスポンスのステータスコードが204: No Contentとなるようです。

公式サイトでも、publish(公開)の他、publisher Diagnostics(診断)用のフォームが用意されていますので確認時にはこちらを利用するといいと思います。

feedburner

Bloggerではブログサイトを作成した際に自動的にfeedburnerに登録されていると思います。この設定が残っていると、従来のフィードがfeedburner経由で配信されてしまいますので、もとのURLは削除して新しいURLをセットします。削除は既存のブログサイトを表示させた状態から下記のようにDelete Feedから、新規追加はトップページでURLを入力してあとはデフォルト状態でウィザードを進めていけば可能です。

feedburnerのフィードの削除

また、feedburnerで生成されるフィードのアドレスはデフォルトではhttpとして表示されていますが、httpsのアドレスでも表示できるようです。

Bloggerでフィードリダイレクト

Bloggerのフィードリダイレクトにfeedburnerのアドレスを登録しておけばBloggerのサイトに要求されたフィードのリクエストをfeedburnerへ転送することができます。つまりBloggerサイトへのフィード要求に対して編集後のフィードを表示できます。

Bloggerフィードリダイレクト

feedburnerは、もともとフィード購読者の分析を行う為に本家のサイトに代わってフィードを配信してくれるサービスなのですが、このように設定しておけばフィードをホスティングしたサイト(ここではfirebase)の転送量を減らすこともできます。

feedburnerを使わないなら、feedburner側の登録を削除した後で、Bloggerのフィードリダイレクト先をフィードをホスティングしたサイトにしておけばいいです。

ちなみにBloggerでフィードのリダイレクトを指定すると.../defaultなどのフィードはリダイレクト先が表示されますが、フィードのURLパラメータに?redirect=falseを加えるか、アドレスを/defaultではなく/summaryを入力すると元のBloggerのフィードが出力できます。summaryは文字通りダイジェストの出力になります。

Bloggerテンプレートの修正

Bloggerのテンプレートのヘッダ部分に、<data:blog.feedLinks/>の記述があると、自動で次の3つのフィードへのリンクを生成するので、コメントアウトしてフィード用のヘッダを手書きで追加します。

  • https://BloggerのURL/feeds/posts/default

    Atom用のフィードで、このアドレスへのアクセスはリダイレクトされますが、リダイレクト先がRSSの場合でもメディアタイプがatom+xmlと表示されます。

  • https://BloggerのURL/feeds/posts/default?alt=rss

    RSS用のフィードで、このアドレスへのアクセスはリダイレクトされますが、リダイレクト先がAtomの場合でもメディアタイプがrss+xmlと表示されます。

  • https://www.blogger.com/feeds/BloggerのID/posts/default

    ここへのアクセスはリダイレクトされず、下書き記事を含んだフィードが表示されます。

    Atom:link rel="service.post"が示すものは、XML.com:「The Atom Link Model」によると、「サイトに新しい記事を投稿するためのAtomAPIエンドポイントを指すを含めることができます(Google翻訳)」とのことで、Atomを使った新記事投稿の為のURLなのではないかと推測されます。もしそうなら、筆者の場合このアドレスは不要です。

テンプレート.xml

<link href='https://feeds.feedburner.com/...' rel='alternate' title='なんぶ電子 - Atom' type='application/atom+xml'/>

参考にさせていただきましたサイトの皆様、ありがとうございました。

筆者紹介


自分の写真
がーふぁ、とか、ふぃんてっく、とか世の中すっかりハイテクになってしまいました。プログラムのコーディングに触れることもある筆者ですが、自分の作業は硯と筆で文字をかいているみたいな古臭いものだと思っています。 今やこんな風にブログを書くことすらAIにとって代わられそうなほど技術は進んでいます。 生活やビジネスでPCを活用しようとするとき、そんな第一線の技術と比べてしまうとやる気が失せてしまいがちですが、おいしいお惣菜をネットで注文できる時代でも、手作りの味はすたれていません。 提示されたもの(アプリ)に自分を合わせるのでなく、自分の活動にあったアプリを作る。それがPC活用の基本なんじゃなかと思います。 そんな意見に同調していただける方向けにLinuxのDebianOSをはじめとした基本無料のアプリの使い方を紹介できたらなと考えています。

広告