Shopifyでコレクションページに対するスパムリンクを受けた場合の対応方法

窓辺

英語版フォーラムで問題になっていた件を少し調査したのでメモ的に。

前提

上記が起きている問題ですが、状況としては以下のようになります。

  • サーチコンソールでサイト内に存在しないURLがインデックスされていることを確認
  • Shopifyのストアがハッキングされていてるのではと疑う

SEOを多少学んでいればハッキングではない可能性が高いと判断できると思います。

URLのパターン

  • コレクションページのベンダー一覧表示用のURLにパラメータをつけた形
  • パラメータはスパムサイトのURL付き文字列で、タイトルやh1などに出力される

実際には以下のような形のパラメータになります。


https://your-store.myshopify.com/collections/vendors?q=Visit%20Cheapfifa23coins.com%2030%25%20OFF%20code%3AFIFA2023%7C%20Fantastic%2C%20quick%20service%2C%20highly%20recommended%20when%20you%20need%20%20fifa

該当のパラメータで検索すると結構な数のサイトが被害にあっていることが確認できると思います。

影響

ストア内にまったく違うジャンルのワードが入り込むことで、ストアに対するジャンルの認識がぼやけて評価が微妙に薄まる可能性は考えられるかもしれません。

なお、以下のようにSEO専門家の辻さんがツイートした以下内容と類似だと思われます。

原因の推測

  • 該当のURLに対して外部からリンクが貼られている
  • Shopifyの該当ページはステータスコードで404を返さず200を返すので、リンクを認識した検索エンジンにインデックスされてしまう

該当のURLに関して

Generates an HTML <a> tag with an href attribute linking to a collection page that lists all products of a given product vendor.


{{ "Polina's Potent Potions" | link_to_vendor }}
上記で以下が出力される
<a href="/collections/vendors?q=Polina%27s%20Potent%20Potions" title="Polina's Potent Potions">Polina's Potent Potions</a>

上記ドキュメントに書かれていますが、/collections/vendors?q=vendorの一覧ページのURLです。

類似のURLとして以下のtypeも同種のパラメータ付きURLが生成されます。


{{ 'Health' | link_to_type }}
上記で以下が出力される
<a href="/collections/types?q=Health" title="Health">Health</a>

対応策の一例

検索結果ページに対しても同様の問題が起こっているので、まとめて記載します。
なお、noindexrobots.txtは素人が気軽に操作しない方が良いので、知識がない方の実行は全くお勧めしません。

目的の動作

  • ベンダーを指定してコレクションを表示した際に、商品が1つも含まれない場合はnoindexを出力する
  • 検索結果にnoindexを出力する
  • robots.txtの検索結果用Disallowを取り除く

検索結果に関しては、Shopifyはデフォルトでrobots.txtDisallow: /searchが設定されているようなので、noindexrobots.txtに合わせて、検索結果が0でも1件以上でもnoindexを出力する状態にします。

robots.txtのDisallow削除

上記ページを参考にして、robots.txt.liquidを作成後、以下のようにデフォルトのコードを変更してrobots.txt内のDisallow: /searchを削除します。


# we use Shopify as our ecommerce platform
{%- comment -%}
# Caution! Please read https://help.shopify.com/en/manual/promoting-marketing/seo/editing-robots-txt before proceeding to make changes to this file.
{% endcomment %}
{% for group in robots.default_groups %}
  {{- group.user_agent -}}

  {% for rule in group.rules %}
    {%- unless rule.directive == 'Disallow' and rule.value == '/search' -%}
      {{- rule -}}
    {%- endunless -%}
  {% endfor %}

  {%- if group.sitemap != blank -%}
    {{ group.sitemap }}
  {%- endif -%}
{% endfor %}

スパムによるインデックス数が膨大な場合

実際に調査していないのでどの程度真実なのか不明ですが、Shopifyのフォーラムにあるスレッドで何百万という単位でインデックスが追加されているという書き込みを目にします。

このレベルであれば再度クロールを促すことも難しく、できてもクロールバジェットなどの大規模サイトが気にする要素が絡んでくるように思います。

この方面は経験がないので判断がつきませんが、やはりSEO専門の方に相談されるのがよいように思います。

noindex出力

テーマごとに違いがあるかもしれませんが、おそらくtheme.liquidheadタグが記載されていると思います。
headタグを見つけたら、headタグの中に以下を追加します。


{%- liquid
  assign flag_noindex = false
  case request.path
    when '/search'
      assign flag_noindex = true
    when '/collections/vendors'
      if collection.all_products_count == 0
        assign flag_noindex = true
      endif
    endcase
-%}
{% if flag_noindex %}
  <meta name="robots" content="noindex">
{% endif %}

case文内のwhenでページタイプの判定を行い、if文でさらに条件を絞ることでフラグを操作しています。

404を返す状態の補足

全ストアに反映済みかは不明ですが、手持ちの開発ストアでは2023.1.18現在以下の状態が確認できます。

  • vendorsの0件表示時には404ページとステータスが返される
  • 404ページはtemplatesディレクトリ内の404.liquid(または404.json)が用いられる

そのため、対象ストアが同じ状態であれば前出のコードからvendrosの部分は削除して問題ないとは思います。
request.pathで取得できる値が/404に変化してしまうため分岐条件が動作しないためです。
もっとも害もないので今後なんらかの変更があった場合を想定して残しておくことも視野に入るかもしれません。

おそらく近いうちに検索結果0件ページに関しても同様に404を返すようになるのではと思いますが、以下の点から思ったより遠い話になるのかもしれません。

  • 現在の仕組みでは、ステータスコードだけではなく使用するテンプレート自体を404.liquidに差し替えている
  • 検索結果0件で404.liquidが表示されると、表示内容的に違和感が強い

上記を踏まえ、仮にテンプレートはそのままにステータスコードだけを404に変更する仕組みに変わる可能性を考えると、念のための予防措置としてnoindexを設置しておく意味はあるのかもしれません。

結び

noindexcanonicalは使用箇所を間違うと大変なことになるので、当ページの方法を検討される場合は十分に留意ください。

なお、冒頭のスレッドではShopify側からの返答がないため、当記事公開時点では正確な原因や状態は不明です。

[追記2022.11.1]

ベストソリューションかつShopifyスタッフおすすめの方法が出てきましたが、個人的には微妙な方法という印象です。

[追記2023.1.12]

Our developers have recently shipped a change that will generate a 404 page if the vendor is unknown (based on query string). It will still print out the query string, but should block it from indexing on Google. These changes will only apply to vendor pages, but our teams will investigate whether other pages may be vulnerable to this abuse.

上記のように404ページを出力させるようにしたとのことです。

反映まで時間がかかるため実際に確認できるのは少し先になりそうですが、noindexはテーマの領分なのでおそらくステータスコードを404で返すのではと思います。

[追記2023.4.29]

その後上記のようにweb pixelが含まれたインデックスが問題となっていますが、スパムではありません。
SEO的に問題があるのか判断がつかず真偽不明ではあるものの、被害を訴えている投稿者が複数人います。
(当追記時点では未解決の模様)

続いてタグに対するスパムも発生しています。
URL+クエリで実行可能な点でvendorと同種の原因に基づくスパム手法と思われます。
テーマなのか独自対応なのか不明ですが、見出しタグへの出力は防いでもtitleタグは未対策というストアもあるためスパムが成功しています。

ShopifyのSEOに関する私見

2022年7月末頃には発生していた問題の解決案提示が2023年1月中旬というのは、率直に言って遅すぎると思います。

以下のようなShopify公式の回答も含めて、今回の件でShopifyのSEOに対する諸々が垣間見えたようにも思います。

ShopifyのSEOチーム

I’m on the internal SEO team, so I can’t comment on anything regarding Shopify product. Please keep this thread updated as you implement your solutions.

上記のような書き込みされているため、Greg-Bernhardtさんは「ShopifyのSEOチームに属している」という理解でよいかと思います。
そのためこの方の発言内容を追うことは参考になるかもしれません。

8人がこの記事を評価

役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。

連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。

コメント:6件

  1. Where do you put this code? I put it in theme.liquid and added an else clause to show the original "follow,index" meta robots tag, but it doesn't work. View page source and inspect element are not showing the noindex meta tag when I visit my site with the spam url.

    1. Hi jhon.
      It will work if you include it in the head tag.
      Can you please describe only a part of it as follows and check how it will be displayed?

      {%- if request.path == '/collections/vendors' -%}

      If the above code doesn't work either, it might be because you put it in the wrong place or it might be the theme or app.

  2. Thank you for your explanation, I found the link to your website through the Shopify community thread.

    I have implemented the proposed fix and am hoping for the best.

  3. Hi – I wandered over from the Shopify boards… Is there a string of code, similar to this that you shared but for sites using "/search?q=" in their site search queries:

    {%- if request.path == '/collections/vendors' and collection.all_products_count == 0 -%} {%- endif -%}

    Am trying to stem an attack and your vendors block above worked great. Wondering if you can help with the search queries.

    – Jen in California

    1. Hi jen.
      We have added it to the article, so please check it.
      However, please check it carefully when you use it.
      I will also post it in the forum.

コメント欄