Webページ内を検索するときに使う正規表現

正規表現を使うことで、通常の検索では実現できない高度な検索が可能になります。

たとえば、「Ruby」か「Python」か「Perl」のどれかに一致してほしいときや、「JavaScript」ではなく「Java」に一致してほしいときや、円周率の小数点以下100万桁から同じ数字が6つ以上続いているところを検索したい、などの用途に使えます。この他にも様々なことが正規表現で可能です。

ということで、Webページ内を検索するときに使う正規表現を紹介していきます。

## 正規表現を使う際の注意点

正規表現を使った検索を行う際に留意すべき点があります。正規表現には、特別な意味を持つ文字がいくつかあります。具体的には`^!$()=|+*{}[]<>.?\`です。これらは**メタ文字**と呼ばれ、正規表現の中に含めたときに特別な意味を持ちます。そのため、たとえば検索したい文字列の中にこれらの文字が含まれている場合、修正する必要が出てきます。

もしこれらの文字そのものに一致させたい場合は、メタ文字の前に`\`をつけます。たとえば、「(>.<)」に一致する正規表現は`\(\>\.\<\)`となります。

## 実例

### 「Ruby」か「Python」か「Perl」のどれかに一致してほしい

一般的にor検索と呼ばれるものです。or検索には`|`を使います。たとえば、「Ruby」か「Python」か「Perl」のどれかに一致してほしい場合は`Ruby|Python|Perl`と記述します。

![ruby.png](https://qiita-image-store.s3.amazonaws.com/0/59884/1cc41949-f477-2004-a612-1b821f60e8ea.png)

### 「父上」「母上」「兄上」「姉上」のどれかに一致してほしい

これは、先ほど紹介した`|`を使うことで`父上|母上|兄上|姉上`と記述できますが、`()`を使うことでもう少しシンプルに記述することができます。`()`は部分的にグループ化するときに使われるメタ文字で、これを使うことで正規表現の一部に`|`を適用することが可能になります。具体的には、今回の例である4つの用語には、後ろに共通した文字「上」がつくため、「「父」「母」「兄」「姉」のどれか ＋ 上」と言い換えることができます。こういったときに、`()`を使って`(父|母|兄|姉)上`と記述できます。

![chichi.png](https://qiita-image-store.s3.amazonaws.com/0/59884/28719198-8fce-d184-5e11-03aa7888ecd0.png)

### 「google」や「gooooooooooooogle」に一致してほしい

「o」の数が2個以上続いているような「google」という文字列を検索することを考えます。このときに使用する正規表現は`{}`です。`{}`は、「`{`の直前にある文字またはグループが、指定した回数だけ繰り返されている」ことを表す正規表現です。`{}`は繰り返しを表現するときに使う正規表現だと言えます。具体的には、

`a{n}`は「aがn回繰り返されている」ことを表し、
`a{n,}`は「aがn回以上繰り返されている」ことを表し、
`a{,n}`は「aが0回以上n回以下繰り返されている」ことを表し、
`a{n,m}`は「aがn回以上m回以下繰り返されている」ことを表します。

ということで、「「o」の数が2個以上続いているgoogle」に一致する正規表現は、`go{2,}gle`です。

![google.png](https://qiita-image-store.s3.amazonaws.com/0/59884/342058fd-2c8e-708d-448b-fd6746fd98f5.png)

### 「JavaScript」ではなく「Java」に一致してほしい①

「JavaScript」ではなく「Java」に一致してほしいことがあります。通常の検索でこのような複雑な条件を指定することはできませんが、正規表現を使うことで可能となります。このときに使う正規表現は`(?!)`です。これは**否定先読み**と呼ばれるもので、後に続く文字列が`(?!)`の括弧内の正規表現（`!`と`)`の間に記述します）に一致する場合に、一致しない文字列と判断してくれます。

たとえば、「Java」に一致してほしいけど「JavaScript」には一致してほしくない場合の正規表現は、`Java(?!Script)`となります。この正規表現は、「後に続く文字列が`Script`でない`Java`に一致する」ことを表します。否定先読みは他にも、「child」に一致してほしいけど「children」には一致してほしくない、「たこ」に一致してほしいけど「たこ焼き」には一致してほしくない、などのときに使えます。

![java.png](https://qiita-image-store.s3.amazonaws.com/0/59884/59ee77e4-b893-4d4f-a678-8a2e88aa15d8.png)

### 「JavaScript」ではなく「Java」に一致してほしい②

正規表現は表現力が多彩なため、ひとつの要望に対して複数の方法が用意されていることがほとんどです。ということで、否定先読みではなく**単語境界**を使った方法も紹介します。

**単語境界**を表す正規表現は`\b`です。単語境界とは、「英単語に使われそうな文字」と「それ以外の文字」との境界のことです。具体的に`This is a pen.`の単語境界を`|`で表すと、`|This| |is| |a| |pen|.`となります。このように、正規表現には「文字」に一致するものだけでなく「位置」に一致するものもあります。それが正規表現の面白いところでもあります。

> 実は先ほど紹介した否定先読みも、文字ではなく位置に一致する正規表現です。`(?!Script)`は、「後に続く文字列が`Script`でない**位置**」に一致する正規表現です。たとえば`TypeScript`という入力を与えた場合、`|T|y|p|eS|c|r|i|p|t|`の`|`の場所に一致することになります。

「an」に一致してほしいけど、「can」や「and」などの、単語の一部のanには一致してほしくない場合などに`\b`は使えます（`\ban\b`と表現します）。ということで、「Java」に一致して「JavaScript」に一致しない正規表現は`Java\b`となります。

![java2.png](https://qiita-image-store.s3.amazonaws.com/0/59884/f4cdd197-08a0-3082-8e5a-dfa21a12b1b5.png)

### 「湯豆腐」ではなく「豆腐」に一致してほしい

「「JavaScript」ではなく「Java」に一致してほしい①」では、直後に一致してほしくない文字列がない位置に一致する正規表現`(?!)`を紹介しました。それとは逆に、直前に一致してほしくない文字列がある場合には、**否定後読み**である`(?<!)`を使います。たとえば「湯豆腐」ではなく「豆腐」に一致してほしい場合の正規表現は`(?<!湯)豆腐`となります。

![toufu.png](https://qiita-image-store.s3.amazonaws.com/0/59884/576ddf26-e27e-0b75-6f3b-37d01de344a9.png)

## まとめ

この記事では、選択の`|`、グループ化の`()`、繰り返しの`{}`、否定先読みの`(?!)`、否定後読みの`(?<!)`、単語の境界`\b`について紹介しました。他にも有用なものはありますが、Webページ内から特定の文字列を検索したい場合は、とりあえずこの6つだけを覚えておけば十分だと思います。

## この記事を書く動機

この記事を書く主な動機は、自作の検索ツールを紹介することです。

1か月ほど前にエゴサーチをしていたのですが、私の持つ名前は複数あり、通常の検索方法ではわざわざ入力し直さないといけませんでした。そこで「正規表現でWebページ内を検索できればいいな」と思い、そういった機能を持つChromeの拡張機能をダウンロードして色々試したのですが、なかなか自分の感覚にマッチするものがありませんでした。エンターキーを押さないと検索されなかったり、検索履歴が保存されなかったり、検索ボックスを閉じると検索結果も消えてしまったり、マーカーの色が黄-オレンジ固定であったりなどです。

* 検索開始のタイミングをこちらで指定できるようにしたい
* 適切なタイミングで検索履歴を保存してほしい
* 検索ボックスを閉じても検索結果は消えてほしくない
* マーカーの色を好きに変えたい
* 画面がフリーズしてほしくない

こういった要望を満たしたものが、私の作成した「[Regular Expression Search](https://chrome.google.com/webstore/detail/regular-expression-search/mjhoonbmfplgchoobbcjggndgdbomdhl)」です。オプション画面で、検索開始のタイミングやマーカーの色を変えることができ、画面がフリーズしないよう注意深く設計されています。無料でダウンロードできます。お使いいただけると嬉しいですm(_ _)m

![option.png](https://qiita-image-store.s3.amazonaws.com/0/59884/6bb84c88-a2ef-5f1b-c886-b9eb9a509206.png)

## 蛇足

ソースコードは、すべて[GitHubのリポジトリ](https://github.com/penguinshunya/regular-expression-search)に置いています。

画面がフリーズしないように非同期処理を多用しているのですが、その際にLinuxカーネルのプロセスモデルを参考にしました。ですので、アイドルプロセスがあったり、ゾンビプロセスという状態があったりします。Linuxカーネルの勉強は趣味で行っていたのですが、まさかこんな形でLinuxカーネルの知識を使うことになるとは思いませんでしたｗ 他にも、「JavaScriptはシングルスレッド」ということを肌で感じたり、`yield`の使い方をより深く理解したりと、様々な学びがあり、楽しく開発を進めることができました（`yield`の使い方については、いずれQiitaに投稿したいと思っています）。

修正したい点として、Pjaxに対応できていないというのがあります。YouTubeやGitHubの画面遷移はPjaxで行われているのですが、そのときに前回の検索結果が削除されないため、画面遷移しているのにマーカーは残っているというよくわからない状態になります。そういったところを今後修正していきたいです。