プラグインなしでGoogleの検索結果に星を表示する方法【コピペ】

検索結果で「星」や「点数」「レビュアー」が表示されている以下のようなページを見かけたことはありませんか?

実はこれ、サイト側で設定すればサイトの規模に関係なく表示させることができます。「レビューの構造化データ」と言います。

「パンくずリスト」と同じく、毎回必ず表示されるわけではありませんが、設定しない限り表示されることはないので、気になっていた方はぜひこの機会に導入してみてください。

コピペしてすぐに使えるコードを紹介します。

同様の機能を提供しているWordPressプラグインもあるみたいですが、ぶっちゃけ今回紹介するコードを使った方が簡単に設定できます。

テーマ関係なく使えて、失敗リスクもないので、おすすめですよ。

今のWordPressは画面真っ白にならないのでご安心ください。

ちなみに、僕が開発しているWordPressテーマ「4536」にはレビュー設定項目がすでに実装されているので、4536ユーザーはコピペしなくてもOKです。

【追記】アルゴリズムの変更でこの記事のやり方だとエラーが出るようになりました。他の構造化データ同様にエラーが出ても検索順位への影響はありませんが、星やレビュアーが表示されるかわかりませんのでご理解の上お試しください。

【追記】エラーが出ない仕様にアップデートしました!



functions.phpにコピペするコード

以下のコードをすべてfunctions.phpにコピペするだけで準備完了です。ちょっと長いですが、上から下まで全部コピーしてくださいね。

あと、今のWordPressはFTPソフトなどでコードを追加する方が危険なので、WordPress管理画面からコードを追加することをおすすめします。


/**
 * Review Class
 *
 * @author Chef
 * @link   https://4536.jp
 */
class Review_4536 {

	/**
	 * Constractor
	 */
	public function __construct() {
		add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] );
		add_action( 'transition_post_status', [ $this, 'save' ], 10, 3 );
		add_action( 'wp_head', [ $this, 'script' ] );
		add_action( 'wp_head', [ $this, 'style' ] );
		add_filter( 'the_content', [ $this, 'add_rating_star_to_content' ], 1000 );
	}

	/**
	 * Get Post Meta
	 *
	 * @param string $key is get_post_meta key.
	 */
	private function get_post_meta( string $key ) {
		global $post;
		$value = get_post_meta( $post->ID, $key, true );
		if ( 'review_rating' === $key ) {
			// For Old Rating.
			if ( $value > 5 ) {
				$value = intval( $value ) / 2;
			}
		}
		return esc_html( $value );
	}

	/**
	 * Add Meta Box
	 */
	public function add_meta_box() {
		$title    = __( 'レビュー', '4536' );
		$id       = 'review';
		$callback = $id;
		add_meta_box( $id, $title, [ $this, $callback ], 'post', 'side', 'low' );
		add_meta_box( $id, $title, [ $this, $callback ], 'page', 'side', 'low' );
	}

	/**
	 * Save
	 *
	 * @param string $new_status is new post status.
	 * @param string $old_status is old post status.
	 * @param string $post       is post content.
	 */
	public function save( $new_status, $old_status, $post ) {
		switch ( $old_status ) {
			case 'auto-draft':
			case 'draft':
			case 'pending':
			case 'future':
				if ( 'publish' === $new_status ) {
					return $post;
				}
				break;
			default:
				add_action(
					'save_post',
					[ $this, 'post_meta_action' ]
				);
				break;
		}
	}

	/**
	 * Save Function Callback
	 *
	 * @param int $post_id .
	 */
	public function post_meta_action( $post_id ) {
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return $post_id;
		}
		if ( 'inline-save' === filter_input( INPUT_POST, 'action' ) ) {
			return $post_id;
		}
		$meta_key_arr = [
			'review_name',
			'review_rating',
			'review_type',
		];
		foreach ( $meta_key_arr as $meta_key ) {
			if ( filter_input( INPUT_POST, $meta_key ) ) {
				update_post_meta( $post_id, $meta_key, filter_input( INPUT_POST, $meta_key ) );
			} else {
				delete_post_meta( $post_id, $meta_key );
			}
		}
	}

	/**
	 * Callback Function
	 */
	public function review() {
		$review_name   = $this->get_post_meta( 'review_name' );
		$review_rating = $this->get_post_meta( 'review_rating' );
		$review_type   = $this->get_post_meta( 'review_type' );
		?>
		<p><small>この記事がレビュー記事の場合に「評価」と「レビュー対象の名前」を設定すると検索結果にレビュー項目が表示されることがあります。</small></p>
		<p>
			<label>レビュー対象の種類</label><br>
			<select name="review_type" type="text">
				<?php $selected = ( $review_type === $x ) ? ' selected' : ''; ?>
				<option value="" hidden>選択してください</option>
				<?php
				$review_type_arr = [
					'Product'    => '製品・サービス',
					'Restaurant' => 'レストラン(お店)',
				];
				foreach ( $review_type_arr as $type => $name ) {
					$selected = ( $review_type === $type ) ? ' selected' : '';
					echo '<option value="' . esc_attr( $type ) . '"' . esc_attr( $selected ) . '>'
							. esc_html( $name ) . '</option>';
				}
				?>
			</select>
		</p>
		<p><label>レビュー対象<input type="text" name="review_name" value="<?php echo esc_attr( $review_name ); ?>" size="60" style="width:100%"/></label></p>
		<label>評価</label><br>
		<select name="review_rating" type="text">
		<option value="" hidden>選択してください</option>
		<?php
		for ( $i = 2; $i <= 10; $i++ ) {
			$x        = strval( $i / 2 );
			$selected = ( strval( $review_rating ) === $x ) ? ' selected' : '';
			echo '<option value="' . esc_attr( $x ) . '"' . esc_attr( $selected ) . '>' . esc_html( $x ) . '</option>';
		}
		?>
		</select>
		<?php
	}

	/**
	 * Star
	 *
	 * @param string $color        is star color.
	 * @param string $rating_float is float value.
	 *
	 * @return string
	 */
	private function star( $color = '#cccccc', $rating_float = 0 ) {
		if ( 0 !== $rating_float ) {
			$gradient = <<< EOM1
<defs>
    <linearGradient id="gradient">
        <stop offset="0%" stop-color="{$color}" />
        <stop offset="50%" stop-color="{$color}" />
        <stop offset="50%" stop-color="#cccccc" />
    </linearGradient>
</defs>
EOM1;
			$fill     = 'url(#gradient)';
		} else {
			$gradient = '';
			$fill     = $color;
		}
		$star = <<< EOM
<svg xmlns="http://www.w3.org/2000/svg"
    width="32" height="32" viewBox="0 0 24 24" class="star">{$gradient}
    <path d="M12 17.27L18.18
    21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
        stroke="none" fill="{$fill}"/>
</svg>
EOM;
		return $star;
	}

	/**
	 * Rating convert to star
	 *
	 * @param int $rating is rating value.
	 *
	 * @return string
	 */
	private function rating_value_to_star( $rating ) {
		if ( $rating < 1 || $rating > 5 ) {
			return;
		}
		$int   = intval( $rating );
		$blank = 5 - $int;
		$html  = str_repeat( $this->star( '#d56e0c' ), $int );
		if ( is_float( $rating ) ) {
			$html .= $this->star( '#d56e0c', 0.5 );
			$blank--;
		}
		if ( $blank > 0 ) {
			$html .= str_repeat( $this->star(), $blank );
		}
		return $html;
	}

	/**
	 * Add Rating Star To First Paragraph of Content
	 *
	 * @param string $content is post content.
	 *
	 * @return string
	 */
	public function add_rating_star_to_content( $content ) {
		if ( ! is_singular() ) {
			return $content;
		}
		$rating = $this->get_post_meta( 'review_rating' );
		$name   = $this->get_post_meta( 'review_name' );
		if ( ! $rating || ! $name ) {
			return $content;
		}
		if ( 1 < strlen( $rating ) ) {
			$rating = floatval( $rating );
		} else {
			$rating = intval( $rating );
		}
		$stars = $this->rating_value_to_star( $rating );

		$stars = '<p data-display="flex" data-align-items="center" data-font-size="x-large">評価:'
				. '<span id="review_rating_value">' . $rating . '</span>' . $stars . '</p>';
		return $stars . $content;
	}

	/**
	 * Script
	 */
	public function script() {
		if ( ! is_singular() ) {
			return;
		}
		$review_name   = $this->get_post_meta( 'review_name' );
		$review_rating = $this->get_post_meta( 'review_rating' );
		$review_type   = $this->get_post_meta( 'review_type' );
		if ( ! $review_type ) {
			$review_type = 'Thing';
		}
		if ( ! $review_name || ! $review_rating ) {
			return;
		}
		global $post;
		$posted_date = get_the_date( 'c' );
		$image_arr   = wp_get_attachment_image_src( get_post_thumbnail_id(), true );
		$image_url   = $image_arr[0];
		if ( 'LocalBusiness' === $review_type && ! $image_url ) {
			return;
		}
		$author         = get_userdata( $post->post_author )->display_name;
		$publisher_name = get_bloginfo( 'name' );
		// Here Document Begin.
		?>
<script type="application/ld+json">
{
	"@context": "http://schema.org",
	"@type": "Review",
	"itemReviewed": {
		"@type": "<?php echo esc_html( $review_type ); ?>",
		"name": "<?php echo esc_html( $review_name ); ?>",
		"image": "<?php echo esc_url( $image_url ); ?>",
		"review": {
			"author": {
				"@type": "Person"
			}
		}
	},
	"reviewRating": {
		"@type": "Rating",
		"ratingValue": "<?php echo esc_html( $review_rating ); ?>"
	},
	"datePublished": "<?php echo esc_html( $posted_date ); ?>",
	"author": {
		"@type": "Person",
		"name": "<?php echo esc_html( $author ); ?>"
	},
	"publisher": {
		"@type": "Organization",
		"name": "<?php echo esc_html( $publisher_name ); ?>"
	}
}
</script>
			<?php
	}

		/**
		 * Style
		 */
	public function style() {
		?>
<style>
	[data-display="flex"] {
		display: -webkit-box;
		display: -ms-flexbox;
		display: flex;
		-ms-flex-wrap: wrap;
			flex-wrap: wrap;
	}
	[data-align-items="center"] {
		-webkit-box-align: center;
		-ms-flex-align: center;
			align-items: center;
	}
	[data-font-size="x-large"] {
		font-size: 24px;
	}
	#review_rating_value{
		color: #d56e0c;
		margin-right: .5em;
	}
</style>
		<?php
	}

}

$review_class = new Review_4536();
add_action( 'init', [ $review_class, '__construct' ] );

設定方法

このコードをコピペして保存すると、「投稿」と「固定ページ」のエディター画面のサイドバーにレビュー設定項目が表示されます。

レビュー設定項目

ここに、レビューする対象の種類、名前、評価を入力するだけ。やることはそれだけです!

レビュー設定例

ちゃんと設定できたかの確認

この構造化データはサイトには表示されないので、ちゃんと設定できたかがこの段階ではわかりません。

手順通り進めたのであれば問題ありませんが、心配な方は以下の手順で確認してみてください。

構造化データテストツールにアクセスし、確認したいページのURLを貼り付けてテストを実行。

Reviewの構造化データラベル

「Review」をクリック。

レビューの構造化データテストツール
  • name→対象名
  • ratingValue→評価数
  • author→レビュアー
  • publisher→サイト名

などを確認する。

これで入力内容に間違いがなければ、Googleの検索結果に星や評価が表示されます。

「Review」の項目がなかったり、エラーになっている場合はコピペが間違っているか、テーマ側に問題があります。

また、正しく設定されていると、サーチコンソールの「拡張」メニューにも有効かどうかのステータスが表示されるようになるので、そこでも確認できます。

【追記】本文にも星を表示するように

アップデート前のコードは構造化データのみ出力していましたが、記事本文内にも表示した方が(ペナルティ的に)安全らしいので、本文の最初に評価が表示されるようにしました。

【追記】レビューの種類も要指定

以前はどんな種類のレビュー記事でも検索結果に星や評価を表示することができましたが、少し前の仕様変更でレビューする対象の種類も正確に指定しなければいけないことになりました。

(スパム対策だと思われます)

レビューできる種類は、レシピ、音楽、動画、本…などたくさんありますが、今回紹介しているコードでは「Product(製品・サービス)」と「Restaurant(レストラン)」に限定しています。

※Productは製品以外にもレンタルサーバーやサブスクリプションなどの「無形物の製品(サービス)」なども含まれていると思います。

この2つの種類に限定している理由は大きく2つあってですね。

Amazonで買ったものやネット上で登録できる何かのサービス、カフェやレストランをレビューしたい人がほとんどかなということが1つ。

もう1つは、その他の種類のレビュー(音楽や動画など)だと記載しなければいけない情報が多く、基本的にそれらの構造化データと組み合わせることがほとんどかなということ。

例えば、音楽、映画、レシピなどは、レビューの構造化データとして頑張って表示するものではなく、Music(音楽)、Movie(映画)、Recipe(レシピ)などの専用の構造化データのオプションとしてレビューの構造化を使うのが正しいやり方だと思います。

【追記】ユーザーが投稿する口コミなどの集計データを使いたい人へ

レビューの種類が限定された話にも通じますが、実は、個人でレビュー記事を書けないものもありまして。

例えば、ローカルビジネス(会社やホテル)のレビュー記事は、個人が更新するタイプのブログでは書けなくて、他のユーザーに投稿してもらう必要があります。

(特に、自分たちで自分たちの会社やお店をレビューする記事を書くのはペナルティ対象になります)

これはReveiw(レビュー)の構造化データではなく、Aggregate Rating(総合評価)の構造化データと言います。

Aggregate Rating(総合評価)の検索結果画面のサンプル
Aggregate Rating(総合評価)の検索結果画面のサンプル

別記事に書きましたが、この口コミ投稿ができるプラグインを先日開発したので興味がある方はぜひ見てください。

(クライアントから依頼されたもので、そのプラグインの配布や販売はしていませんのでご留意ください!)

レビューの構造化データを使うメリット

実は、今回ご紹介したレビューの構造化データを使ったからといって、現時点ではSEO的に有利になるわけではありません。

ただ、とにかく目立つので設定するメリットはかなり大きいと思います。

例えば、以下の画像はレンタルサーバーのConoHa WING(コノハウィング)のレビュー記事に関する検索結果なんですが、星があるとやっぱり目立ちますよね。

(タイミングによっては画像も表示されます)

「コノハウィング レビュー」と検索した時の検索結果画面(スマホ)
「コノハウィング レビュー」と検索した時の検索結果画面(パソコン)

あと、あまりレビューの構造化データを設定しているサイトを見かけないので、単純に差別化にもなります。

注意点

注意点というか、当たり前の話なんですが、レビュー記事以外で設定したり、関係ない情報を入力しないでくださいね。

オピニオン記事なのにレビュー情報を入力するとか、ドッグフードのレビュー記事なのにワイヤレスイヤホンのレビュー情報を入力するとか。

こういうことをすると手動ペナルティの対象になるそうです。

あと、冒頭でお話ししたように、設定したからと言ってすぐに検索結果に反映されるわけでもありませんし、検索した時に必ず表示されるわけでもないので、そこだけはご留意ください。

レビュー記事は個人サイトの強みになる

自作テーマの「4536」に同機能を実装しようと思ったきっかけでもありますが、レビュー記事がこれから個人サイトの強みの1つになると思っているんですね。

例えば、「〇〇 おすすめ」「〇〇 比較」といったクエリを狙うのもいいですが、それらは競争率が高いですし、必然的に複数の商品を買う必要があるので、個人レベルで勝負するとユーザーの検索意図とミスマッチをおこしそうだなと。

一方、レビュー記事であれば、自分が思ったことや体験したことをそのまま書けるので、そういった検索意図とのミスマッチが起こりにくいと思います。実際、レビュー記事はニーズがありますからね。

ということで、レビュー記事を書く時には今回ご紹介した方法も活用してみてはいかがでしょうか。

ご質問やご要望があれば、コメント欄または専用のお問い合わせフォームにてどうぞ。


カテゴリー:カスタマイズ

シェフ

このサイト「Fantastech」を運営している人。WordPressテーマ「4536」の開発中にインプットした情報などを発信中。お仕事のご依頼については「Shinobi Works」までどうぞ