CSS セレクター学習ノート

このノートは CSS のセレクター仕様について簡単に記す。スクレイピングに関する技術でXPath に並んで重要なものだ。ここに書いてあることを習得することで、スクレイピング以外にも CSS コードをエレガントに記述することもできるようになる。

練習方法

ブラウザーの開発ツールのコンソールを利用するか、Scrapy のシェルを利用する。

XPath 学習ノート の XPath 練習方法の節に記したものと同じ方法が使える。違いは次の通り:

  • ブラウザーの開発用コンソールでは関数 $x() の代わりに関数 $()$$() を用いる。

  • Scrapy シェルではメソッド .xpath() の代わりにメソッド .css() を用いる。

いずれも引数は CSS セレクターを表す文字列とすればよい。

他にも BeautifulSoup を使う方法や、HTML-XML-utils のコマンド hxselect を利用する方法がある。

Level 1

セレクター仕様にはレベルという概念があるようで、数字が小さいものほど基本的な仕様となっている。

ただし、ここで E, F は任意の HTML 要素名を表すものとする。例えば a, div, img, h1 などだ。また、link, visited, active はすべてリテラルとする。

Level 1

セレクター例

選択要素

E

文書中の <E></E> すべてを指定する。

E.warning

文書中の <E class="warning"></E> すべてを指定する。

E#myid

文書中の <E id="myid"></E> を指定する。

E:link

要素 E がハイパーリンクであって、リンク先が未訪問であるようなものを指定する。

E:visited

E:link の訪問済み版。

E:active

要素 E であって active であるものを指定する。

E F

要素 F であって要素 E の子孫であるもの。

ここまではふつうの CSS を書くときにも頻繁に指定するものなので馴染みがある。むしろスクレイピングではこのような interactive 関連パターンは使わないはずだ。

E:active については、何をもって要素が active であるのかを決めるのかは要素によって異なる。

仕様書によれば FE の子である必要はなく、例えば孫でもひ孫でも可とある。空白文字で区切るのは子孫指定と憶えたい。

Level 2

レベル 2 から要素の属性に対する条件を指定できるようになる。

Level 2

セレクター例

選択要素

*

すべての要素を表す。

E[attr]

要素 E であって、属性 attr を有するものを指定する。

E[attr="value"]

要素 E であって、属性 attr の値が value に厳密に等しいものを指定する。

E[attr~="value"]

要素 E であってその属性 attr の値が空白文字区切りの文字列パターンであるとする。value がそのいずれかの「単語」に厳密に等しいものを指定する。

E[attr|="value"]

要素 E であってその属性 attr の値が厳密に value に等しいか、あるいは value の直後に - が続き、任意の文字列が続くというパターンであるものを指定する。

E:hover

省略。

E:focus

省略。

E:first-child

要素 E であって、別の方法で指定された何らかの要素の最初の子要素であるものを指定する。

E > F

要素 F であって要素 E の子であるものを指定する。

E + F

要素 F であって、要素 E と同じ要素を親として持ち、かつ E のすぐ次に来るような F を指定する。

セレクター * は単体で用いるのではなく、他のパターンと組み合わせて用いる。ただし *.warning*#myid はレベル 1 仕様を用いてそれぞれ単に .warning#myid と書ける。

セレクター E[attr] の例を一つ挙げる。h1[title] は``title`` を属性に持つ h1 要素すべてを指定する。

セレクター E[attr="value"] はよく用いるだろう。

  • 例:span[class="example"]

  • 例:span[hello="Cleveland"][goodbye="Columbus"]

  • アルファベットの大文字小文字オプションもあるが割愛。

セレクター a[rel~="copyright"] は要素 a であって、属性 rel の値が例えば "copyright copyleft copyeditor" であるならば、それらすべてがマッチする。

セレクター a[hreflang|="en"] がマッチするのは次のような要素だ:

  • <a hreflang="en"></a>

  • <a hreflang="en-US"></a>

  • <a hreflang="en-scouse"></a>

E:hover および E:focus はスクレイピング向けではないので、本ノートでは説明しない。

木構造に関わるセレクターは上下方向と横方向があることを意識するとよい。

セレクター E:first-child は後述する :nth-child(1) と同値だ。例として div > p:first-child を挙げる。これは要素 div 内に最初にある要素が p であるならば、それを指定する:

<p>The last P before the note.</p> <!-- マッチしない -->
<div class="note">
   <p>The first P inside the note.</p> <!-- マッチする -->
</div>

<p>The last P before the note.</p> <!-- マッチしない -->
<div class="note">
   <h2>Note</h2>
   <p>The first P inside the note.</p> <!-- マッチしない -->
</div>

セレクター E > F は要素 F であって要素 E の子であるものを指定する。

  • 例:body > p: 要素 body の子であるような要素 p すべてを指定する。

  • 例:div ol>li p: 要素 li の子孫にあたる要素 p すべてを指定する。ただしそのような li はいずれも要素 ol の子であるものとし、さらにそのような ol は要素 div の子孫であるものとする。

Level 3

Level 3 で一気にセレクターのバリエーションが増える。

Level 3

セレクター例

選択要素

E:not(sel)

要素 E であって、セレクター sel にマッチしないもの。

E[attr^="val"]

要素 E であって、その属性 attr の値が val で始まるようなものを指定する。

E[attr$="val"]

要素 E であって、その属性 attr の値が val で終わるようなものを指定する。

E[attr*="val"]

要素 E であって、その属性 attr の値が部分文字列として val を含むようなものを指定する。

E:target

要素 E であって、現在の文書のターゲットであるようなものを指定する。

E:enabled

ユーザーインターフェイス要素 E であって有効状態であるもの。

E:disabled

上記の無効状態版。

E:checked

チェックボックスまたはラジオボタン E であって、選択状態であるもの。

E:root

ふつうは <html>...</html> を指定する。

E:empty

要素 E であって子要素を有しないもの。ただし空白文字はあるかもしれない。

E:nth-child(i)

要素 E であって、その親要素の先頭から i 番目の子要素であるものを指定する。

E:nth-last-child(i)

要素 E であって、末尾から先頭に向かって数えて i 番目の子要素であるものを指定する。

E:last-child

要素 E であって、その親要素の最後の子要素であるものを指定する。

E:only-child

要素 E であって、一人っ子であるものを指定する。

E:nth-of-type(i)

要素 E であって i 番目に現れるものを指定する。

E:nth-last-of-type(i)

要素 E であって末尾から先頭に向かって i 番目に現れるものを指定する。

E:first-of-type

要素 E であって最初のものを指定する。

E:last-of-type

要素 E であって最後のものを指定する。

E:only-of-type

唯一の要素 E を指定する。

まず否定を覚えておこう。ここで sel は有効なセレクターを表すものとする。

  • 例:button:not([DISABLED]): 要素 button のうち有効状態のものすべてを指定する。

  • 例:*:not(FOO): FOO を除くすべての要素を指定する。

  • 例:html|*:not(:link):not(:visited): これは宿題とする。

次はレベル 2 で習った属性セレクターの仲間だ。これらはスクレイピングで活躍しそうだ。

  • E[attr^="val"]

  • E[att$="val"]

  • E[att*="val"]

以上のいずれにおいても、val が空である場合にはセレクターは何も表していないものとする。

  • 例:object[type^="image/"]

  • 例:a[href$=".html"]

  • 例:p[title*="hello"]

擬似クラスを含むセレクター仕様がいくつか存在する。E:target についてはよくわからない。

全体と空。E:root<html>...</html> を指定するのがふつうなので、スクレイピングではたぶん使わない。

E:empty は要素 E であって子要素を有しないものすべてを指す。例えば p:empty<p></p>, <p> </p>, のようなものをすべて指定する。スクレイピングでうまい使い方がありそうな気がする。

レベル 3 の目玉と思われる、子要素を序数で指定するセレクターでは序数の指定方式にクセがある。

  • インデックスは 1 始まり。

  • even, odd を指定することが許される。

  • An+B 記法というものがある。詳しくは仕様書を見たほうがいいが、これのせいでインデックスが 0 始まりでない。

スクレイピングでは表要素の何番目の列を取得するという用途が頻繁にあるので、習得必須かもしれない。

E:last-child は要素 E であって、その親要素の最後の子要素であるものを指定する。例えば ol > li.last-child とすると <ol> 要素すべてに対する最後の``<li>`` 要素をすべて指す。

親要素を意識しない序数によるセレクターもある。例をまとめて挙げる:

img:nth-of-type(2n+1){ float: right; }
img:nth-of-type(2n){ float: left; }

body > h2:not(:first-of-type):not(:last-of-type){ /* ... */ }

dl dt:first-of-type { /* ... */ }

/* 各行に対して最後のセルを指定する */
tr > td:last-of-type { /* ... */ }

E:only-of-type は唯一の要素 E を指定する。複数存在する E なら指定しないということなのでうまく利用できる状況があるかもしれない。

Level 4

レベル 4 で頭の片隅にあるといつか使うかもしれないものを。ただし、これを実装している処理系は現在私の手許にないかもしれない。少なくとも Chrome ベースの某ブラウザーではダメ。

Level 4

セレクター例

選択要素

E:not(s1, s2, ..., sn)

引数が複数あっても構わなくなった。

E:is(s1, s2, ..., sn)

要素 E であって、セレクター s1, s2, …, sn に and/or でマッチする。

E:where(s1, s2, ..., sn)

E:is() の「指定度」無視版。

E:has(rs1, rs2, ..., rsn)

要素 E であって、相対セレクター rs1, rs2, …, rsn のうちいずれかがスコープ要素として存在するならば、それにマッチする。

:any-link

要素であって、ハイパーリンクを表すようなものにマッチ。

F || E

要素 E であって、要素 F が表す列に属する表にあるセルを表すようなもの。

E:nth-col(n)

要素 E であって、表における n 番目の列に属するセルを表すもの。

E:nth-last-col(n)

要素 E であって、表における末尾から逆方向に数えて n 番目の列に属するセルを表すもの。

E:is() の例を挙げる。

  • *|*:is(:hover, :focus): マウスが乗っているか、フォーカスが合っているような要素ならなんでもマッチ。

  • *|*:is(*:hover, *:focus): デフォルトの名前空間限定で、マウスが乗っているか、フォーカスが合っているような要素ならなんでもマッチ。

E:where() は説明が少々難しい。まず、次のコード片は期待通り働かない:

a:not(:hover) {
  text-decoration: none;
}

 nav a {
   text-decoration: underline;
 }

こういうときに :where() を用いる。次なら期待通り働く。

a:where(:not(:hover)) {
  text-decoration: none;
}

nav a {
  /* Works now! */
  text-decoration: underline;
}

指定度の理解をする必要がある。これについては後述する。

E:has(rs1, rs2, ..., rsn) は例を見たほうがわかりやすい。

  • a:has(> img): 要素 <a> であって、子に要素 <img> を含むようなものにマッチ。

  • dt:has(+ dt): 要素 <dt> であって、直後に別の <dt> が続くようなものにマッチ。

  • section:not(:has(h1, h2, h3, h4, h5, h6)): 要素 <section> であって、いかなる <h[1-6]> を含まないようなものにマッチ。

  • section:has(:not(h1, h2, h3, h4, h5, h6)): 要素 <section> であって、 <h[1-6]> のどれでもない要素を含むようなものにマッチ。

セレクター :any-link URL を抽出するスクレイピングで使えるかもしれない。平たく言えば属性 href のある要素にマッチする。さらに論理的には :is(:link, :visited) と同値。

レベル 4 にしてやっと表関連専門のセレクターが仕様に含まれる。次の例は C, E, G を灰色にする。HTML では C, E は 3 列目にあり、G は 2 列目と 3 列目にまたがっている。 G の文字は何も指定がなければ 2 列目に描画されると思う。

col.selected || td {
  background: gray;
  color: white;
  font-weight: bold;
}
<table>
  <col span="2">
  <col class="selected">
  <tr><td>A <td>B <td>C
  <tr><td colspan="2">D <td>E
  <tr><td>F <td colspan="2">G
</table>

E:nth-col(n) にせよ E:nth-last-col(n) にせよ An+B 記法における位置の決定方法に注意。

指定度

ある要素に対するセレクターの 指定度 とは、次の数からなる三組である:

  • そのセレクターにある ID セレクターの個数

  • そのセレクターにあるクラスセレクター、属性セレクター、擬似クラスの個数

  • そのセレクターにある型セレクターと疑似要素の個数

例:

*               /* (0, 0, 0). universal selector は無視するものとする */
LI              /* (0, 0, 1). HTML タグ名は型セレクターの一つ */
UL LI           /* (0, 0, 2). UL の子孫であるような LI */
UL OL+LI        /* (0, 0, 3). UL の子孫であるような LI であって、直前に OL が先行するもの */

H1 + *[REL=up]  /* (0, 1, 1). 第 2 成分と第 3 成分はそれぞれ REL, H1 による */
UL OL LI.red    /* (0, 1, 3). */
LI.red.level    /* (0, 2, 1). LI 要素であって red クラスでも level クラスでもあるようなもの */

#x34y           /* (1, 0, 0). id の値が x34y であるような要素すべて */

セレクターがセレクターリストであれば、その指定度はリストにあるセレクターそれぞれに対して計算される。リストに対する与えられたマッチング過程に対して、最終的な指定度はマッチするリスト内にある、もっとも具体的なセレクターの指定度である。

ただし擬似クラスのいくつかは別のセレクターに評価コンテキストを提供するので、指定度の計算法が特別なものになる。

  • 擬似クラス :is(), :not(), :has() の指定度は、セレクターリスト引数にある最も具体的な複セレクターの指定度に置き換わる。

  • 類比的に、セレクター :nth-child():nth-last-child() の指定度は次の指定度の和になる:

    • 擬似クラスそれ自身の指定度(一つの擬似クラスセレクターとして勘定)

    • (存在すれば)セレクターリスト引数にある最も具体的な複セレクターの指定度

  • 擬似クラス :where() の指定度はゼロに置き換わる。

例:

  • :is(em, #foo) の指定度は次のいずれかにマッチしたときに (1, 0, 0) となる:<em>, <p id=foo>, <em id=foo>.

  • .qux:where(em, #foo#bar#baz) の指定度は (0, 1, 0) となる。というのも :where() の外部にある .qux しかセレクターの指定度に寄与しないからだ。

  • :nth-child(even of li, .item) の指定度は (0, 2, 0) となる。

    • 自身の指定度

    • 次のいずれかにマッチしたときの擬似クラスの指定度:

      • <li>

      • <ul class=item>

      • <li class=item id=foo>

  • :not(em, strong#foo) の指定度は任意の要素にマッチしたときに (1, 0, 1) となる。この値は strong#foo の指定度と等しい。

指定度の順序関係は辞書式順序で定義される。左の成分同士から比較する。大きい方がより具体的である。

関連ノート