🔖

Algoliaのクローラーのconfig設定

2024/04/12に公開

やること

クローラーを動かすうえで重要となるConfigファイルについて読み解いていきます。
記事の内容としては翻訳+自分の解釈です。
https://docsearch.algolia.com/docs/legacy/config-file/

動作確認前のドキュメントを読み解きながら理解を進めている段階で記事を書いているため、あまり正確な情報ではないと思いますが、理解の助けになればと思い残しておきます。

全体的にボリュームが多く感じましたが、「クローラーのデフォルトの挙動がどうで、セレクタの設定によりどうなるか」が掴めたら(selectorsの項目が理解できたら)、他の項目は怖くないと思います(たぶん)。

公式ドキュメントを読むときに、、

気づいたこととしてクローラーのことをDocSearch scraperとかって別の表現を使い始めたりするので、適宜読み替えて進めると良さげです。crawl、scrapは厳密には違うニュアンスを持っているかと思いますが、コンフィグファイルの全体像を掴むことを優先し、本記事では”クローラー”に統一して表現するようにします。

DocSearch scraper:クローラー
scraping:クローリング

index_name インデックス名

レコードがプッシュされるインデックス名の指定です。
DcoSearchクローラーを自分で動かしている場合、インデックス名はこのConfigファイルで指定した名前とります。

{
  "index_name": "example"
}

クロールが実行されると一時的な(Temporaryな)インデックスが作成されます。
これはコンソール画面でも確認でき、Configファイルで指定したindex_nameに_tmpが付け足され、example_tmpのような名前となります。

この一時的なインデックスの名前を変更するには、コンテナに環境変数として渡すための.envファイル(APPLICATION_IDとAPI_KEYを記載しているやつ)にINDEX_NAME_TMPの環境変数を追加します。

start_urls 開始URL

クロール対象のサイトのURLを指定します。
ここでは複数のURLを指定することができます(複数指定する際のユースケースについては後述)。
クローラーはここで指定したURLから、そのページに含まれるリンク(<a>タグ)を再帰的にたどる挙動をします。
複数のURLを指定した場合、上のURLから順番にクロールが行われます。
リンクをたどる際に、別のドメインのリンクへ行くことはありません。
また、stop_urlsにマッチするリンクへ行くこともありません。

{
  "start_urls": ["https://www.example.com/docs"]
}

selectors_key, tailor your selectors セレクタキーとその指定

start_urlsに指定したURLとセレクタ(selectors)を対応させることができます。
selectorsとは、そのページ内のどの要素をインデックスに登録するかを指定する設定のことです。
start_urlsselectorsを対応させる場合は、selectors_keyを指定します。

下記のサンプルでは、1つ目のURLでselectors_keyに”faq”が設定されている一方で、2つ目のURLでは特に指定はありません。
selectorsを見ると"default"と"faq"が存在し、これらが対応していることがわかります。

{
  "start_urls": [
    {
      "url": "http://www.example.com/docs/faq/",
      "selectors_key": "faq"
    },
    {
      "url": "http://www.example.com/docs/"
    }
  ],
  [],
  "selectors": {
    // selectors_keyの指定がない場合=2つ目のURLの設定
    "default": {
      "lvl0": ".docs h1",
      "lvl1": ".docs h2",
      "lvl2": ".docs h3",
      "lvl3": ".docs h4",
      "lvl4": ".docs h5",
      "text": ".docs p, .docs li"
    },
    // selectors_keyが"faq"の場合=1つ目のURLの設定
    "faq": {
      "lvl0": ".faq h1",
      "lvl1": ".faq h2",
      "lvl2": ".faq h3",
      "lvl3": ".faq h4",
      "lvl4": ".faq h5",
      "text": ".faq p, .faq li"
    }
  }
}
勝手に誤解して読み解くのに躓いたところ

"http://www.example.com/doc/"は"http://www.example.com/doc/faq/"へのリンクを含んでいるだろうという思い込みをしてました。
実施は含んでいても、含んでいなくてもどっちでも良くて、、、

含んでいた場合=クロール範囲は一部重複するが、セレクタの設定が異なるのでインデックスに登録される情報も異なる
"http://www.example.com/doc/"に対し、docの設定でクロールを行う。
このとき"http://www.example.com/doc/faq/"の内容もdocの設定でクロールを行う
"http://www.example.com/doc/faq/"に対し、再度、別の設定(faqの設定)でクロールを行う。

含んでいない場合=重複しない範囲を、異なる設定でインデックスに登録する

Considering the URL http://www.example.com/en/api/ with the configuration:

{
  "start_urls": [
    {
      "url": "http://www.example.com/doc/",
      "selectors_key": "doc"
    },
    {
      "url": "http://www.example.com/doc/faq/",
      "selectors_key": "faq"
    },
   [],
  ]
}

Only the set of selectors related to doc will be applied to the URL. The correct configuration should be built the other way around (as primarily described).

If one start_urls item has no selectors_key defined, the default set will be used. Do not forget to set this fallback set of selectors.

Using regular expressions 正規表現の使用

正規表現を利用することでより複雑にURLを指定することができます。
正規表現を利用する場合、正規表現内で使用する変数を定義することができます。
正規表現を利用する場合、少なくとも到達可能なURLが存在する必要があります。

正規表現を使うことの有益な副作用として、下記の例ではインデックスに登録されるレコード全てにlang: enversion: latestのタグが付与されて、facetFiltersの機能で利用することができるようになります。
(動作確認してないのでよくわかっていません)

{
  "start_urls": [
    {
      "url": "http://www.example.com/docs/(?P<lang>.*?)/(?P<version>.*?)/",
      "variables": {
        "lang": ["en", "fr"],
        "version": ["latest", "3.3", "3.2"]
      }
    }
  ]
}

Using custom tags

正規表現を使わない場合でもレコードにタグ付けすることが可能です。
タグを指定する場合は下記のように配列で指定します。
タグを付与すると、正規表現のときと同じようにfacetFiltersの機能で利用することができるようになります。

{
  "start_urls": [
    {
      "url": "http://www.example.com/docs/concepts/",
      "tags": ["concepts", "terminology"]
    }
  ]
}

Using Page Rank ページランクの使用

レコードに対するランク付け(重み付け)をすることができます。
負の値を含め、任意の数値を設定することができます。
ランク付けを行うと検索した際に、ランクの値の高いものから表示されるようになります。

{
  "start_urls": [
    {
      "url": "http://www.example.com/docs/concepts/",
      "page_rank": 5
    },
    {
      "url": "http://www.example.com/docs/contributors/",
      "page_rank": 1
    }
  ]
}

Using custom selectors per page ページごとのカスタムセレクタの使用

selectors_keyのところでも触れているとおり、URLに対応したselectorsを利用することができます(正直ここでもう一度登場した理由がわかっていません、、、)。

なお、一番最初のURLである"http://www.example.com/docs/"のページに "http://www.example.com/docs/concepts/"へのリンク(<a>タグ要素)が含まれている場合、"http://www.example.com/docs/concepts/"配下の情報はdefaultとconceptsの両方の設定で、2度にわたってクロールされることになるかと思われます。

{
  "start_urls": [
    "http://www.example.com/docs/",
    {
      "url": "http://www.example.com/docs/concepts/",
      "selectors_key": "concepts"
    },
    {
      "url": "http://www.example.com/docs/contributors/",
      "selectors_key": "contributors"
    }
  ],
  "selectors": {
    "default": {
      "lvl0": ".main h1",
      "lvl1": ".main h2",
      "lvl2": ".main h3",
      "lvl3": ".main h4",
      "lvl4": ".main h5",
      "text": ".main p"
    },
    "concepts": {
      "lvl0": ".header h2",
      "lvl1": ".main h1.title",
      "lvl2": ".main h2.title",
      "lvl3": ".main h3.title",
      "lvl4": ".main h5.title",
      "text": ".main p"
    },
    "contributors": {
      "lvl0": ".main h1",
      "lvl1": ".contributors .name",
      "lvl2": ".contributors .title",
      "text": ".contributors .description"
    }
  }
}

selectors セレクタ

レコード階層を作成するための設定で、0から5の6つのレベルとテキストを含むことができます。
デフォルトの設定だとページタイトルかh1要素をLv0(一番上)にして、h2をLv1、h3をLv3,
pはテキストしているようですが、実際のHTMLの構造に大きく依存します。

textキーは必須なのと、適切な関連性の深さを持たせるためにLv0〜Lv2も設定することを推奨しています。

また、selectorsのキーの値にはstring以外に、「selectorキー(単数形になっている)を含むオブジェクト」を設定することができる。このオブジェクトには他の特別なキーと値を設定できます。

{
  "selectors": {
    "lvl0": "#content header h1",
    "lvl1": "#content article h1",
    "lvl2": "#content section h3",
    "lvl3": "#content section h4",
    "lvl4": "#content section h5",
    "lvl5": "#content section h6",
    "text": "#content header p,#content section p,#content section ol"
  }
}

{
  "selectors": {
    // Lv0の値にオブジェクトを設定
    "lvl0": {
     // selectorキーを含んでいる
      "selector": "#content header h1"
    }
  }
}

Using global selectors グローバルセレクタ

セレクタを使ってコンテンツを抽出する際のデフォルトの挙動は上から下に解析していくことで、この流れは半構造化コンテンツではうまく動作します。一方でタイトルがヘッダーやサイドバーの一部でない場合など、関連する情報が同じフローに含まれない場合にはこの方法だと破綻してしまいます。

セレクタをグローバルに設定することで、ページ全体でマッチしたものを抽出することができます。

半構造化とは?

半構造化されたコンテンツとは、ある程度の構造を持ちながらも、完全に構造化されていないコンテンツのことを指します。例えば、HTMLの場合、見出しタグや段落タグを使用している場合、コンテンツはある程度の構造を持っていますが、すべての情報が厳密に階層化されているわけではありません。

半構造化されたコンテンツの例としては、ブログ記事やニュース記事などが挙げられます。これらのコンテンツは通常、見出しや段落で構造化されていますが、その中には特定のパターンに従わないテキストや画像、リンクなども含まれています。このようなコンテンツは、完全に構造化されたデータとは異なり、ある程度の解析が必要な場合があります。

一方、完全に構造化されたコンテンツは、例えばデータベースのテーブルやJSONファイルのように、明確なフィールドと値の組み合わせで表現されます。このようなデータは通常、解析が比較的容易であり、特定のフィールドを取得するためのセレクタを簡単に定義できます。

したがって、半構造化されたコンテンツの場合、構造を持つ部分と持たない部分が混在しているため、セレクタを使用して情報を抽出する際には、しばしば調整や工夫が必要です。そのため、特定のセレクタがページ内の特定の場所にマッチするだけでなく、ページ全体に対して適用されるセレクタが必要な場合があります。

textセレクタをグローバルに設定することは推奨されません。

{
  "selectors": {
    "lvl0": {
      "selector": "#content header h1",
      "global": true
    }
  }
}

常の挙動では、Algolia のクローラーはページを上から順に解析していき、セレクターにマッチする要素が見つかった時点でその要素を抽出し、処理を終了します。そのため、下記のような構造のHTMLファイルをクローラーが解析するとき、通常は"Title"のみが抽出されます。
対してglobal: trueとなっていると、"Title"および"Sub Title"が抽出されます。

<html>
<head>
  <title>Sample Page</title>
</head>
<body>
  <div id="content">
    <header>
      <h1>Title</h1>
    </header>
    <div>
      <h1>Sub Title</h1>
    </div>
  </div>
</body>
</html>

Setting a default value 初期値の設定

マッチする要素がなかったときの初期値としてdefault_valueが設定できます。

{
  "selectors": {
    "lvl0": {
      "selector": "#content header h1",
      "default_value": "Documentation"
    }
  }
}

Removing unnecessary characters 不要な文字の削除

見出しに#など特殊文字を使用しているケースについて、文体上の価値はあるが、データ構造としての意味はないためインデックスされるべきではない情報です。
strip_cahrsキーを設定することで、最終的なインデックスの値から除外したい文字のリストを定義することができます。

{
  "selectors": {
    "lvl0": {
      "selector": "#content header h1",
      // #と>を除外するように指定されている
      "strip_chars": "#›"
    }
  }
}


// 設定ファイルのルートに記述することで全てのセレクタに設定することもできる
{
  "strip_chars": "#›"
}

Targeting elements using XPath instead of CSS XPathでの指定

CSSセレクタ(CSSでの抽出対象の要素の指定)は、明確で簡潔な方法ですが限界があります。
例えば、CSSセレクタを使用しているとCSSカスケード(スタイルシートが要素に適用される順序と優先順位)を上る(上位のCSSにアクセスする)ことができません。

より機能的なセレクタ指定が必要な場合は、typeキーに"xpath"を設定することで、XPathを使ってセレクタを記述することができます。

下記の例ではli.chapter.active.doneを対象に、<a>を見つけるまでDOMの2階層上に移動します。

XPathセレクタは読みにくい場合があり、(意図した設定となっていない可能性が発生しやすいため)まずブラウザでテストして、期待するものと一致していることを確認することを強く推奨します。

XPathについて
  • XPath は XML Path Language の略称
  • XML文章のアドレッシング(アドレスの表現)をすることができる
  • ロケーションパス(ディレクトリパスのような感じ)として表現する

だそうです。

https://qiita.com/rllllho/items/cb1187cec0fb17fc650a

https://developer.mozilla.org/ja/docs/Web/XPath

{
  "selectors": {
    "lvl0": {
      "selector": "//li[@class=\"chapter active done\"]/../../a",
      "type": "xpath",
      "global": true
    }
  }
}

custom_settings Optional

custom_settingsキーを利用するとAlgoliaの設定を上書きすることができます。
デフォルトで全てのウェブサイトで機能するようになっているので変更は推奨されません。

推奨されないということなので機械翻訳にて割愛

custom_settings.separatorsToIndexOptional

1つの使用例は、separatorsToIndex設定を構成することです。デフォルトでは、アルゴリアはすべての特殊文字を単語の区切り文字とみなします。メソッド名のような文脈では、_、/、#の意味を維持したい場合があります。

{
  "custom_settings": {
    "separatorsToIndex": "_/"
  }
}
custom_settings.synonyms Optional

custom_settingsには、同義語の配列であるsynonymsキーを含めることができます。各要素は1単語の同義語の配列です。これらの単語は互換性があります。

"custom_settings": {
    "synonyms": [
      [
        "js",
        "javascript"
      ],
      [
        "es6",
        "ECMAScript6",
        "ECMAScript2015"
      ]
    ]
  },

scrape_start_urls Optional 開始URLをクロール対象とするか

デフォルトではクローラーはstart_urlsで定義されたページを含む、そのページを起点としてコンテンツを抽出していきます。start_urlsに価値のあるコンテンツが無い場合、または他のページと重複しているなどで、抽出対象から除外したい場合はfalseに設定してください。

{
  "scrape_start_urls": false
}

selectors_exclude Optional セレクタの除外設定

selectors_excludeのいずれかにマッチする要素を抽出対象から除外することができます。
この設定は他のセレクタを書きやすくするためのもので、コンテンツのテーブル、サイドバー、フッターを削除するために使用できます。
指定する場合は対象のCSSを配列で記述します。

{
  "selectors_exclude": [".footer", "ul.deprecated"]
}

stop_urls Optional 停止URL

stop_urlsを設定すると、クロール対象のページを制限することができます。
クローラーが<a>タグをたどってリンクにアクセスしようとする際に、そのリンクがstop_urlsマッチした場合、クローラーはそのリンクをたどりません。

指定する場合、文字列または正規表現の配列で指定します。

{
  "stop_urls": ["https://www.example.com/docs/index.html", "license.html"]
}

min_indexed_level Optional インデックスに登録されるレコードのセレクタレベルの最小値

セレクタで定義したレベルのうち、インデックスに登録する最小のレベルを指定します。
デフォルト値は0となっています。
min_indexed_levelに2を指定した場合、クローラーは少なくともlvl0、lvl1、lvl2がセットされた一時レコードにインデックスを作成します。

同じlvl0とlvl1を共有するページがある場合に便利です。このような場合、共有されているレコードをすべてインデックス化するのではなく、ページ間で異なるコンテンツをインデックスとして保持したいからです。

ちょっと分かりづらい表現で書かれていますが、インデックスに登録されるレコードにはベースとしてlvl0, lvl1, lvl2があり、そこにその他の要素(lvl4のみかもしれないし、lvl5とテキストかもしれない)が加わったものが登録されるということのようです。

最低でも「lvl0, lvl1, lvl2 + αの要素」でレコードが生成されるため「lvl0〜lvl?」はレコードにぜたったい含めたいといったときには、指定が必要になりそうです。

{
  "min_indexed_level": 2
}

ここでちょっとばかり私の理解を妨げたのが、セレクタというのはlvl0から順に評価されるわけですが、lvl3がマッチしなかったからと言ってlvl4がマッチしないわけでもないということです。
lvl3でマッチしなかったとしても、lvl4以降も評価は実施されるので、マッチする可能性があるようで、極端な例ですが下記のようなHTMLとセレクタでは、lvl0、lvl1、およびlvl2にマッチしないがlvl3にマッチするという状況が発生するようです。
(’ようで’と表現しているのは、明確な根拠が見つけられなかったのと、動作確認できていないので理解が憶測に留まっているためです。もし分かる方いたら教えていただきたいです)

<div class="main">
    <h4>This is a lvl3 heading</h4>
    <p>This is a paragraph</p>
</div>
"default": {
      "lvl0": ".main h1",
      "lvl1": ".main h2",
      "lvl2": ".main h3",
      "lvl3": ".main h4",
      "lvl4": ".main h5",
      "text": ".main p"
    },

only_content_level Optional セレクタ単位でのレコード作成の除外

only_content_levelをtrueにした場合、クローラーは指定したlvl〜のセレクタのレコードを作成しないようになります。これを使うと、min_indexed_levelの設定が無視されます。

奇妙に聞こえるかもしれませんが、この設定を使用するとセレクタの指定というのは意味をなさなくなります。代わりにどのような挙動になるかと言うと、クローラーがそのページに含まれるすべてのテキストやコンテンツ情報を1つのレコードとして保持するようになります。すべてとは、具体的にはページの本文、見出し、リンク、画像のURL、およびその他のテキスト情報などです。

通常、ウェブページから情報を収集する場合、特定の要素やデータを抽出してデータベースに保存しますが、only_content_level: trueが設定されている場合、クローラーはページ全体のコンテンツ情報を1つのレコードとして保存します。このため、特定のレベルの情報に基づいてデータを絞り込むのではなく、ページ全体のコンテンツを保存することになります。

{
  "only_content_level": true
}

nb_hits Special

DocSearchによって抽出されて、インデックスに登録されたレコード数を意味します。
設定ミスに起因する意図しないレコード数の急増や減少をトラッキングするために、内部的にこのキーの値をチェックしています。

なお、nb_hitsはDocSearchの実行のたびに自動的に更新され、ユーザーが編集する必要のない情報です。このドキュメントに記載しているのは、ユーザーがnb_hitsについて疑問を持つだろうことを見越してのことです。

DocSearchをCLIで実行した場合はnb_hitsの値を更新するかどうかの確認を行います。
この確認の表示有無は、UPDATE_NB_HITS環境変数をtrue(有効にする)またはfalse(無効にする)で制御でき、変数は.envファイルでAPPLICATION_IDやAPI_KEYと一緒に設定することができます。

no_hitsのサンプル

CLIからDocSearchを実行した結果のサンプルです。
このときはConfigの設定をてきとうにやっていて、抽出されたレコードが0件でした。
最後の行に"nbHits 0 for Sample"と表示があるのが確認できます。

/> DocSearch: https://example.com/ 0 records)
/> DocSearch: https://example.com/sample 0 records)
/> DocSearch: https://example.com/sample2 0 records)
/> DocSearch: https://example.com/sample/sample_test1 0 records)
/> DocSearch: https://example.com/sample/sample_test2 0 records)
/> DocSearch: https://example.com/sample3 0 records)
/> DocSearch: https://example.com/sample/sample_test3 0 records)
/> DocSearch: https://example.com/sample2/sample2_test1 0 records)
/> DocSearch: https://example.com/sample2/sample2_test2 0 records)
/> DocSearch: https://example.com/sample2/sample2_test3)
/> DocSearch: https://example.com/sample2/sample2_test4 0 records)
/> DocSearch: https://example.com/sample3/sample3_test1 0 records)
/> DocSearch: https://example.com/sample3/sample3_test2 0 records)
/> DocSearch: https://example.com/sample3/sample3_test3 0 records)
/> DocSearch: https://example.com/sample3/sample3_test4 0 records)
/> DocSearch: https://example.com/sample3/sample3_test5 0 records)

Crawling issue: nbHits 0 for Sample

Sitemaps

sitemap_urls Optional

サイトマップファイルを指すURLの配列を渡すことができます。
この値が設定されている場合、DocSearch は starts_urls のすべてのリンクをたどるのではなく、サイトマップから URL を読み込もうとします。

クローラーはrobots.txtを読み込まない仕様のため、(robots.txtの代わりとして)sitemap_urlsで明確に指定する必要があります。

robots.txtについて

この記事ではAlgoliaを取り扱っていますが、クローラーと言えば有名なところでGoogle クローラーもありますよね(この記事を書くまであまり意識しない存在でしたが、、笑)。
Webサイトがクローラーに対して、このパスはクロールしても良くて、このパスはクロールしてはいけないという許可・拒否を明示するためのファイルがrobots.txtだそうです。

https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers?hl=ja

https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt?hl=ja

{
  "sitemap_urls": ["http://www.example.com/docs/sitemap.xml"]
}

サイトマップを使用することで、URLの代替リンクを含めることができます。
代替リンクは同じページの別バージョンや、異なる言語、または異なるULRを指定します。
デフォルトではDocSearchはこれらのURLを無視するため、これらの別バージョンに対してもクロール詩たい場合にはtrueに設定する必要があります。

下記の構成例ではhttp://www.example.com/docs/ と http://www.example.com/docs/de/ の両方がクロールされます。

{
  "sitemap_urls": ["http://www.example.com/docs/sitemap.xml"],
  // デフォルトでは無視されるので、trueすることで無視されない=クロール対象とする
  "sitemap_alternate_links": true
}
<!-- sitemap.xml -->
  <url>
    <loc>http://www.example.com/docs/</loc>
    <!-- 同じコンテンツだが異なる言語バージョンのURL -->
    <xhtml:link rel="alternate" hreflang="de" href="http://www.example.com/de/"/>
  </url>

JavaScript rendering

デフォルトでは、DocSearchはウェブサイトがサーバーサイドレンダリング、つまりHTMLソースがサーバーから直接返されることを想定しています。コンテンツがフロントエンド(クライアントサイド)によって生成される場合、Seleniumを通してブラウザをエミュレートするようにDocSearchに指示する必要があります。

クライアントサイドで生成されるコンテンツのクロールは、サーバーサイドで生成されるコンテンツのクロールと比較してはるかに遅いため、サーバーサイドレンダリングを有効にするようにサイトを更新することを強く推奨します。

js_render Optional

クライアント側でのレンダリングが必要な場合は、js_renderの値を true に設定します。これにより、DocSearch は Selenium プロキシを起動し、すべてのウェブページを取得します。

{
  "js_render": true
}

js_wait Optional

ウェブサイトの読み込みが遅い場合(クライアント側でのデータ取得やHTML生成に時間を要する場合など)は、js_waitを使用して、コンテンツを抽出する前にページの読み込みを特定の時間(秒単位)待つようにDocSearchに指示することができます。

このオプションは、あなたのウェブサイトをクロールするのに必要な時間に大きな影響を与える可能性があることに注意してください。

js_renderがfalseに設定されている場合、このオプションは影響を与えません。

{
  "js_render": true,
  "js_wait": 2
}

use_anchors Optional

クライアント・サイド・レンダリングを使用しているウェブサイトでは、完全なURLを使用せず、URLハッシュ(#の後の部分)を利用することが多くあります。

クロール対象のウェブサイトがそのような URL を使用している場合、DocSearch がすべてのコンテンツをインデックスするように use_anchors を true に設定する必要があります。

{
  "js_render": true,
  "use_anchors": true
}

user_agent Optional

ウェブサイトをクロールするために使用されるクローラーの自身のユーザーエージェントを上書きすることができます。デフォルトではAlgolia DocSearch Crawlerとなっています。クロール時にブラウザのエミュレーションを必要とする場合(つまりjs_renderがtrueの場合)、クローラーのuser_agentはMozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/71.0.3578.98 Safari/537.36となります。

修正の必要に応じて、下記のように上書きできます。

{
  "user_agent": "Custom Bot"
}

Discussion