Page No.2
Sitemap.xml のインデックスファイル化
と、いうことで、Google のボットさんに読んでもらうには 4日ほど必要だったわけだけれど、これはもしかしたら、インデックスファイルを用意したほうが早かったりするのではないだろうか。
「 Sitemaps.org – サイトマップの XML 形式」のインデックスファイルの項目にある、<lastmod> の説明にも、
「サイトマップファイルの最終更新日のタイムスタンプを提供すると、検索エンジンのクローラがインデックス内の特定の日付以降に更新されたサイトマップのみをクロールできるようになります。このようにサイトマップを段階的にクロールすることで、大規模なサイトでも新しい URL を迅速に見つけることができます。」
と、ある。我が個人サイトであっても、サイトマップにリストさせたページは1万件を超える数となったので、新しいページだけを別ファイルにして件数を少なくすればボットさんの手間も少なくなって、もしかしたら読んでくれやすくなるのではないだろうか。やってみないとわからないけれど、違いがあるのかちょっと楽しみではある。
日付によりファイルを分けることは、ちょっと手をいれるだけでできる。
新旧サイトマップを分ける区切りとなる日数は、最新の投稿から15日という具合にとりあえず設定した。もっと短くても良さげではあるけれど、ボットさんが読んでくれる前に、旧ファイルの方に押しやられる日付の分があると、ファイルを分けた意味がなくなってしまう。
生成した XML データを貯めていく配列を二次元として、投稿の投稿日によってその配列を振り分けていくだけのこと。変更したのは実際に XML データを生成する関数と、あとは管理画面に表示するページ部分のサイトマップのファイル名ぐらい。ただ、投稿を日付で分別する処理は何箇所かで必要だったので、その部分は別関数とした。
で、インデックスファイルのファイル名は、sitemap_idx.xml とし、内容は以下のとおり。これ、始めは sitemap_index.xml というファイル名にしていたのであるけれど、なぜだか bingbot でアクセスできないというエラーとなってしまった。なにも拒否する設定などはしていないはずなのであるが・・・。index を idx へと変更したらアクセスさせられるようになった。(この件、後日、403エラーであったことに気が付き、調べてみると bingbot が使うアドレスのうち一つだけ、アドレス拒否してるなかに含まれるものだったので、たぶんこれが原因)
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="https://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://strix.main.jp/sitemap_rctly.xml</loc>
<lastmod>2022-06-29</lastmod>
</sitemap>
<sitemap>
<loc>https://strix.main.jp/sitemap_prev.xml</loc>
<lastmod>2022-06-13</lastmod>
</sitemap>
</sitemapindex>
ということでまずは投稿の新旧を分別する関数。
<?php
function is_old_post ( $tardate, $lastpost, $ope = 0 ) {
$pdate = new DateTime( $tardate );
$pdiff = $pdate -> diff( $lastpost );
$pdaydiff = $pdiff -> format( '%a' );
if ( 1 === $ope ) {
return $pdaydiff;
}
if ( $pdaydiff > 15 ) {
return true;
}
return false;
}
?>
そして、Ajax により呼び出される本丸の改造した XML 生成関数。
後になって気づいたことなのだけれど、予約投稿などをした場合は、post_modified の日付よりも 投稿日である post_date の方が日付が後に(新しく)なる。ゆえに、このような場合は、その日付を投稿日である post_date の方を使用するべきなのではないかと。と、いうことでそのあたりの処理も加えてある。
<?php
function generatesitemap() {
// ログインしているのが user_id === 1 の管理人であるか確認する
// function 'wp_get_current_user' がないということは WordPress が起動すらしていないということ
if ( ! function_exists( 'wp_get_current_user' ) ) {
exit;
}
$user_id = get_current_user_id();
if ( 1 === $user_id ) {
//nonceを取得して認証を得る
if ( check_ajax_referer( __FILE__, 'nonce', false ) ) {
// if ( wp_verify_nonce( $nonce, wp_create_nonce( __FILE__ ) ) ) {
global $wpdb;// 関数でデータベースを使うときのお約束
// priority が設定してあるオプション値を取得
$sitemap_options = get_option( 'sitemap_options' );
$error = '';
// 自サイトの場合、カスタム投稿 diys の投稿は独自キャッシュ化しているので、そのキャッシュファイルのあるフォルダの指定
// diys の投稿はコンテンツが長い場合ページが分割してあり、そのページ分だけキャッシュファイルも存在している
// ゆえにそのファイルの数でその投稿が何ページあるのかわかる
$diys_cache_dir = get_template_directory() . '/diy_cache/';
// 1ページあたりの投稿数の設定を取得
$posts_per_page = get_option( 'posts_per_page' );
// 投稿(post、page、カスタム投稿、すべて含む)にtaxonomy(category, post_tag, custom taxonomy) を紐づけて取得する。
// left outer join なので投稿に紐づけてあるtaxonomy の数だけ同じ投稿がダブって取得される
// taxonomy が設定されていない投稿も取得できる
$sql = <<< HERE
SELECT p.ID, p.post_title, p.post_date, p.post_modified, p.post_type, p.post_name, t.term_id, x.taxonomy, t.slug, x.count
FROM $wpdb->posts p
LEFT OUTER JOIN $wpdb->term_relationships r on p.ID = r.object_id
LEFT OUTER JOIN $wpdb->term_taxonomy x on r.term_taxonomy_id = x.term_taxonomy_id
LEFT OUTER JOIN $wpdb->terms t on x.term_id = t.term_id
WHERE p.post_status = 'publish'
ORDER BY p.post_modified DESC
HERE;
$pt = $wpdb->get_results( $sql );
$posts = array();
$pages = array();
$diys = array();
$gulls = array();
$blues = array();
$ptags = array();
$pcats = array();
$gtags = array();
$allposts = array();
// 投稿の種別に連想配列を作り、それにより得られる文字列で可変変数を使って各変数を指定する。
// カスタム投稿は、diys、gulls、blues の3つある
$types = array( 'post' => 'posts', 'page' => 'pages', 'diys' => 'diys', 'gulls' => 'gulls', 'blues' => 'blues' );
$post_last_date = array( '', '' );// 0->newest post, 1->first of old posts
$diys_last_date = '';
$gull_last_date = '';
$firstel = array( 0, 0, 0 );
$pt_count = 0;
$newflg = 1;// その投稿データが新ファイルか旧ファイルかのフラグ
$nowdate = new DateTime();
if ( $pt ) {
$pt_count = count( $pt );
foreach ( $pt as $p ) {
// その投稿において、投稿日と編集更新日で新しい日付をつかう
$latestdate = $p->post_modified;
if ( $p->post_modified < $p->post_date ) {
$latestdate = $p->post_date;
}
// $post_last_date[0]は最も新しい投稿の日付
// それが設定されていないということは、一回目のループだということ
if ( '' === $post_last_date[0] ) {
$post_last_date[0] = $latestdate;
$lastpost = new DateTime( $latestdate );
} else {
if ( $newflg ) {
// 最新の投稿日から指定日数経過していたら$newflg を0にする
// それ以降の投稿データは全て$newflg は0なので調べる必要はなし
if ( is_old_post ( $latestdate, $lastpost ) ) {
$newflg = 0;
$post_last_date[1] = $latestdate;
}
}
}
$ptype = $p->post_type;
// post_type により可変変数で変数を指定して、各投稿種別に投稿ID を Key としてデータを蓄積する。
// 投稿ID をKeyにしているので、taxonomy によりタブっているデータも一つになる。
if ( isset ( $types[ $ptype ] ) ) {
${ $types[ $ptype ] }[ $p->ID ] = [ $p->post_title, $latestdate, $p->post_name, $newflg ];
}
// 各投稿データを taxonomy ( category, post_tag ,custom taxonomy )別にterm_id を key としてそれぞれの変数へと蓄積する
// 同じ term_id が既存の場合は入力しない。故にその term_id のデータは一番最初に現れたデータとなり、
// SQL において日付の逆順(新しい順)でソートしているので一番最初に現れたデータが一番新しい投稿の日付となる
if ( 'post' === $ptype ) {
if ( 'category' === $p->taxonomy ) {
if ( ! isset ( $pcats[ $p->term_id ] ) ) {
$pcats[ $p->term_id ] = [ $p->ID, $latestdate, $p->slug, $p->count, $newflg ];
}
} else if ( 'post_tag' === $p->taxonomy ) {
if ( ! isset ( $ptags[ $p->term_id ] ) ) {
$ptags[ $p->term_id ] = [ $p->ID, $latestdate, $p->slug, $p->count, $newflg ];
}
}
} else if ( 'gulls' === $ptype ) {
if ( 'gull' === $p->taxonomy ) {
if ( ! isset ( $gtags[ $p->term_id ] ) ) {
$gtags[ $p->term_id ] = [ $p->ID, $latestdate, $p->slug, $p->count, $newflg ];
}
}
if ( 0 === $firstel[1] ) {
$gull_last_date = $latestdate;
}
++$firstel[1];
} else if ( 'diys' === $ptype ) {
if ( 0 === $firstel[2] ) {
$diys_last_date = $latestdate;
}
++$firstel[2];
}
}
} else {
if ( is_array( $pt ) ) {
$error .= implode( '<br>', $pt );
} else {
$error .= $pt;
}
}
$pt = null;
// カスタム投稿 diys においては各投稿を独自キャッシュ化している
// その上、コンテンツの量が多いものは複数ページに分割している
// それゆえにその投稿が何ページあるのかは、キャッシュファイルを数える事でしか知りようがない
// キャッシュファイルのディレクトリをスキャンして同じIDで複数あるものをリストする処理
$diys_cache_pages = array();
if ( file_exists ( $diys_cache_dir ) ) {
$diy_caches = array_diff( scandir( $diys_cache_dir ) , array( '..', '.' ) );
if ( $diy_caches ) {
foreach ( $diy_caches as $val ) {
$pi = pathinfo( $val );
if ( isset( $pi['extension'] ) and 'php' === $pi['extension'] ) {
$el = explode( '_', $pi['filename'] );
if ( isset ( $el[3] ) ) {
if ( isset ( $diys_cache_pages[ $el[2] ]) ) {
$diys_cache_pages[ $el[2] ] += 1;
} else {
$diys_cache_pages[ $el[2] ] = 1;
}
}
}
}
}
}
if ( $error ) {
echo 'failed SQL : ' . $error;
} else {
$xmlheader = array(
'<?xml version="1.0" encoding="UTF-8"?>' . "\n",
'<urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9">' . "\n",
);
// 0->previous posts、1->new posts
$xml = array( $xmlheader, $xmlheader );
$xml[1][] = '<url><loc>https://strix.main.jp/</loc><lastmod>' . w3cdt( $post_last_date[0] ) . '</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url>' . "\n";
$url = 'https://strix.main.jp/?';
// 固定ページで検索エンジンにインデックスさせたくない物のpost_IDをkeyとした連想配列のリスト
$pages_ignore = array();
if ( $sitemap_options['pages_ignore'] ) {
$tmpel = explode ( ',', $sitemap_options['pages_ignore'] );
foreach ( $tmpel as $val ) {
$pages_ignore[ ( int ) $val ] = 0;
}
}
// 固定ページのpriority が設定してある物のpost_ID を key とした連想配列のリスト
$pages_priority = array();
if ( $sitemap_options['pages_priority'] ) {
$tmpel = explode ( ',', $sitemap_options['pages_priority'] );
foreach ( $tmpel as $val ) {
$elms = explode ( ':', $val );
if ( isset( $elms[1] ) and $elms[0] ) {
$pages_priority[ ( int ) $elms[0] ] = $elms[1];
}
}
}
$blues_count = 1;
$posts_count = 0;
$ans = array();
$show_types = array( 'pages', 'diys', 'gulls', 'blues', 'posts' );
foreach ( $show_types as $val ) {
// 可変変数で対象の post_type の変数を指定、$val === 'pages' の時 $pages
$tartype = ${ $val };
if ( $tartype ) {
$ans[] = $val . ' : ' . ( string ) count( $tartype );
foreach( $tartype as $key => $value ) {
if ( 'pages' === $val ) {
// 固定ページは各ページによって複数のページになっているものがあるので、
// それぞれのページで処理を変える
if ( 69149 === $key ) {// Gulls Wing-tip Patterns
$page_count = ceil( count ( $gulls ) / $posts_per_page );
// w3cdt は時間付きの日付データから時間部分を取り除くシンプルな独自関数
$lastmod = w3cdt( $gull_last_date );
for ( $i = 0; $i < $page_count; ++$i ) {
$paged = '';
if ( $i > 0 ) {
// xml ファイルの場合、& などはエスケープする必要があり
$paged = '&paged=' . ( string ) ( $i + 1 );
}
// 自サイトの場合、パーマリンクはデフォルトのままなので各投稿へのリンクは
// デフォルトの決まりきった形式のパターン
// パーマリンク設定がある場合は、get_permalink() 等を使用
// $value[3]->$newflg により新旧ファイルを指定
$xml[ $value[3] ][] = '<url><loc>' . $url . 'page_id=' . $key . $paged . '</loc><lastmod>' . $lastmod . '</lastmod><changefreq>yearly</changefreq><priority>' . $sitemap_options['gulls_wing_priority'] . '</priority></url>' . "\n";
}
} elseif ( 69697 === $key ) {
$lastmod = w3cdt( $gull_last_date );
// このページは画像の枚数でページ数がきまるが、ある特定の画像なのでデータベースで数を取得するのはやっかい
// ゆえに確実に存在するページ数を指定している
for ( $i = 0; $i < 6; ++$i ) {
$paged = '';
if ( $i > 0 ) {
$paged = '&paged=' . ( string ) ( $i + 1 );
}
$xml[ $value[3] ][] = '<url><loc>' . $url . 'page_id=' . $key . $paged . '</loc><lastmod>' . $lastmod . '</lastmod><changefreq>yearly</changefreq><priority>' . $sitemap_options['gulls_wlist_priority'] . '</priority></url>' . "\n";
}
} else {
$keyflg = $value[3];
// 固定ページはアーカイブページの場合が多くそのページの投稿日ではなく表示している投稿の投稿日による
if ( 46 == $key ) {// imageviewer
$lmod = $post_last_date[0];
if ( ! is_old_post ( $lmod, $lastpost ) ) {
$keyflg = 1;
}
} elseif ( 16227 === $key ) {// WordPress の Diy 備忘録
$lmod = $diys_last_date;
if ( ! is_old_post ( $lmod, $lastpost ) ) {
$keyflg = 1;
}
} else {
$lmod = $value[1];
}
$priority = '0.5';
if ( isset ( $pages_priority[ $key ] ) ) {
$priority = $pages_priority[ $key ];
}
$ignore = $val . '_ignore';
if ( ! isset ( ${ $ignore }[ $key ] ) ) {
$xml[ $keyflg ][] = '<url><loc>' . $url . 'page_id=' . $key . '</loc><lastmod>' . w3cdt( $lmod ) . '</lastmod><changefreq>yearly</changefreq><priority>' . $priority . '</priority></url>' . "\n";
}
}
} elseif ( 'diys' === $val ) {
// カスタム投稿 diys においては投稿の新しさで priority と changefreq の設定を変えた
$daydiff = is_old_post( $value[1], $nowdate, 1 );
$priority = $sitemap_options['diy_priority'];
$chgfrq = 'yearly';
if ( $daydiff < 10 ) {
$chgfrq ='weekly';
$priority = '0.9';
} elseif ( $daydiff < 30 ) {
$chgfrq = 'monthly';
$priority = '0.8';
}
$lastmod = w3cdt( $value[1] );
$xml[ $value[3] ][] = '<url><loc>' . $url . 'diys=' . $value[2] . '</loc><lastmod>' . $lastmod . '</lastmod><changefreq>' . $chgfrq . '</changefreq><priority>' . $priority . '</priority></url>' . "\n";
// キャッシュファイルにより分割されているページを追加
if ( isset ( $diys_cache_pages[ $key ] ) ) {
++$diys_cache_pages[ $key ];
for ( $i = $diys_cache_pages[ $key ]; $i > 1; --$i ) {
$xml[ $value[3] ][] = '<url><loc>' . $url . 'diys=' . $value[2] . '&dev=' . ( string) $i . '</loc><lastmod>' . $lastmod . '</lastmod><changefreq>' . $chgfrq . '</changefreq><priority>' . $priority . '</priority></url>' . "\n";
}
}
} elseif ( 'gulls' === $val ) {
$xml[ $value[3] ][] = '<url><loc>' . $url . 'gulls=' . $value[2] . '</loc><lastmod>' . w3cdt( $value[1] ) . '</lastmod><changefreq>never</changefreq><priority>' . $sitemap_options['gulls_priority'] . '</priority></url>' . "\n";
} elseif ( 'blues' === $val ) {
if ( 1 === $blues_count ) {
$xml[ $value[3] ][] = '<url><loc>' . $url . 'post_type=blues</loc><lastmod>' . w3cdt( $value[1] ) . '</lastmod><changefreq>yearly</changefreq><priority>' . $sitemap_options['blues_priority'] . '</priority></url>' . "\n";
}
$xml[ $value[3] ][] = '<url><loc>' . $url . 'blues=' . $value[2] . '</loc><lastmod>' . w3cdt( $value[1] ) . '</lastmod><changefreq>never</changefreq><priority>' . $sitemap_options['blues_priority'] . '</priority></url>' . "\n";
++$blues_count;
} else {
$xml[ $value[3] ][] = '<url><loc>' . $url . 'p=' . $key . '</loc><lastmod>' . w3cdt( $value[1] ) . '</lastmod><changefreq>never</changefreq><priority>' . $sitemap_options['standard_priority'] . '</priority></url>' . "\n";
++$posts_count;
}
}
}
}
// ここから taxonomy に関しての処理
// すでにそれぞれの taxonomy の配列にまとめてあるので順に出力するだけ
// それぞれの taxonomy にはそれ専用の画像だけをまとめたページもあるので、それも一緒にその1ページ目だけを出力する
$rooptar = array( 'category' => 'pcats', 'post_tag' => 'ptags', 'gull' => 'gtags' );
foreach ( $rooptar as $key => $tax ) {
$tartax = ${ $tax };
if ( $tartax ) {
$ans[] = $key . ' : ' . ( string ) count( $tartax );
foreach ( $tartax as $key => $row ) {
$lmod = $row[1];
$lastmod = w3cdt( $lmod );
$target = array(
'pcats' => [ 'cat=' . ( string ) $key, 'z' . $row[2], 'category_priority' ],
'ptags' => [ 'tag=' . $row[2], 'x' . $row[2], 'tag_priority' ],
'gtags' => [ 'gull=' . $row[2], '', 'gull_tax_priority' ],
);
$priority = $sitemap_options[ $target[ $tax ][2] ];
$xml[ $row[4] ][] = '<url><loc>' . $url . $target[ $tax ][0] . '</loc><lastmod>' . $lastmod . '</lastmod><changefreq>yearly</changefreq><priority>' . $priority . '</priority></url>' . "\n";
$pcount = ceil( $row[3] / $posts_per_page );
if ( $pcount > 1 ) {
++$pcount;
for ( $i = 2; $i < $pcount; ++$i ) {
$xml[ $row[4] ][] = '<url><loc>' . $url . $target[ $tax ][0] . '&paged=' . ( string ) $i . '</loc><lastmod>' . $lastmod . '</lastmod><changefreq>yearly</changefreq><priority>' . $priority . '</priority></url>' . "\n";
}
}
// 画像専用ページの出力
if ( 'gtags' === $tax ) {
$xml[ $row[4] ][] = '<url><loc>' . $url . 'page_id=69697&' . $target[ $tax ][0] . '</loc><lastmod>' . $lastmod . '</lastmod><changefreq>yearly</changefreq><priority>' . $priority . '</priority></url>' . "\n";
} else {
$xml[ $row[4] ][] = '<url><loc>' . $url . 'page_id=48&tagname=' . $target[ $tax ][1] . '</loc><lastmod>' . $lastmod . '</lastmod><changefreq>yearly</changefreq><priority>' . $priority . '</priority></url>' . "\n";
}
}
}
}
$xml[0][] = '<url><loc>https://strix.main.jp/canus/</loc><lastmod>2017-10-04</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>' . "\n";
$xml[0][] = '</urlset>';
$xml[1][] = '</urlset>';
$index = array(// sitemap_index.xml の中身
'<?xml version="1.0" encoding="UTF-8"?>' . "\n",
'<sitemapindex xmlns="https://www.sitemaps.org/schemas/sitemap/0.9">' . "\n",
'<sitemap><loc>https://strix.main.jp/sitemap_rctly.xml</loc><lastmod>' . w3cdt( $post_last_date[0] ) . '</lastmod></sitemap>' . "\n",
'<sitemap><loc>https://strix.main.jp/sitemap_prev.xml</loc><lastmod>' . w3cdt( $post_last_date[1] ) . '</lastmod></sitemap>' . "\n",
'</sitemapindex>',
);
$file_ary = array(
'sitemap_rctly.xml' => $xml[1],
'sitemap_prev.xml' => $xml[0],
'sitemap_index.xml' => $index,
);
// ファイルを3つ作成
foreach ( $file_ary as $key => $ary ) {
$fh = fopen( ABSPATH. $key, 'w' );
if ( $fh ) {
fwrite( $fh, implode( '', $ary ) );
fclose( $fh );
} else {
echo 'error: Disable open ' . $key . ' file.';
break;
}
}
echo 'All posts : ' . $pt_count . '<br>' . implode( '<br>', $ans ) . '<br>xml objects count : ' . ( count( $xml[0] ) + count( $xml[1] ) - 6 );
}
} else {
echo 'error: Access denied.';
}
} elseif ( 0 === $user_id ) {
echo 'error:none log-in';
} else {
echo 'error! log-in not admin : ' . $user_id;
}
exit;
}
// 管理画面だけでしか使わないのでフックは一つだけ
add_action( 'wp_ajax_generatesitemap', 'generatesitemap' );
?>
と、まぁ、これで以上である。
サイトマップをインデックス化して、Google Search Console にそれを知らせるために送信すると、やはりというかすぐには読んではくれない。が、今回は少し早く2日後には読み込んでくれた。その後、送信などということはしていないけれど、一日おきにはインデックスファイルを読んでくれているようだ。そしてクロールによるアクセスの回数も減っているように思える。
一方、Bing の方はと言えば、送信した時に読み込んでくれた後は、一度も読んではくれていない。各ページへのクロールによるアクセスは相変わらず多く、こちらは変化は無いように思える。
もう一つ、apple のボットにおいては bing ボット同様、クロールによるアクセス回数は非常に多く、事の前後で変化はないようだ。applebot においては sitemap.xml は意味のないことなのだろうか。
その後、二、三回ほど新規投稿をしたときに、思い出したように XMLサイトマップを更新してる。こちらから何も送信しなくとも、googlebot も bingbot も何日かおきに読んでくれている。まぁ、なんというか、インデックス化した効果はあったように思う。ん~、思いたい。

Post : 2022/06/17 12:53