正規表現(Regular expression, RE)
Web Scrapingを行っていると正規表現の知識が必要となりました。そこでlinuxとpythonによる正規表現をまとめました。ここでは、標準ライブラリreを使用します。
特殊文字 | 意味 | 例 |
---|---|---|
. | 任意の文字にマッチ。 | |
^ | (キャレット)文字列の先頭にマッチ | csvTest.csvのとき^csvで先頭のcsvのみにマッチ |
$ | 文字列の末尾にマッチ | csvTest.csvのときcsv$で末尾のcsvのみにマッチ |
* | 直前にあるREに作用してREを0回以上繰り返したものにマッチ | |
+ | 直前にあるREに作用してREを1回以上繰り返したものにマッチ | |
? | 直前にあるREに作用してREを0 or 1回繰り返したものにマッチ。 | ab?cならac, abcとマッチ |
{m} | m回の正確なコピーとマッチ。 | ab{3}cならabbbcとマッチする |
{m, n} | mからn個の繰り返しマッチ。 | a{2,4}ならaa, aaa, aaaaとマッチする |
(...) | 丸括弧中のREとマッチ | |
[] | 文字の集合を指定する。 | [amp]なら'a', 'm', 'p'とマッチ。 [0-10]なら0,1,...,10までマッチ。 [^a-d]だとa-d以外 |
(?=...) | ...に続くものだけマッチする。先読みアサーション(lookahead assertion) | Issac(?=Asimov)だと'Issac'に'Asimov'が続く場合だけ'Issac'とマッチする。 |
(?!...) | ...に続くものとマッチしなければマッチする。否定先読みアサーション(negative lookahead assertion) | 上の例のIssac(?!Asimov)だとマッチしない。 |
(?<=...) | ...の後から出てくるものとマッチする。 後読みアサーション(lookbehind assertion) |
(?<=bar)fooだとbarfooのときマッチ。 |
(?<!...) | 否定後読みアサーション(negative lookbehind assertion) |
略記表現
略記法 | 同等表記 | 説明 |
---|---|---|
\d | [0-9] | 数字 |
\D | [^0-9] | 非数字 |
\s | [ \f\n\r\t\v] | 空白 |
\S | [^ \f\n\r\t\v] | 非空白 |
\w | [a-zA-Z0-9_] | 単語構成 |
\W | [^a-zA-Z0-9_] | 非単語 |
空白, 特殊文字、_
, 数字とマッチする.
$ echo csv_test_123_csv.csv | grep -Po '([^\s\w]|_|[0-9])+'
_
_123_
.
例題
行から目的の文字列を抜き出す
sedコマンド;置換や抜き出しができる。
-
sed - E "s/.*(抜き出したい箇所のRE).*/\1/"
: \1でキャプチャできる。 sed - E "s/置換前の文字列/置換後の文字列/g"
$ echo abcdefg | sed -E 's/.*(d.).*/\1/'
> de
$ echo thee best time to see thee flowers is in thee spring. | sed -E 's/thee/the/g'
> the best time to see the flowers is in the spring
$ echo '<li id="name">apple</li>' | sed -E 's/<[^>]*>//g'
> apple
grepコマンド;一応、行の抜き出しができる。
- grep - E "RE" : Linux標準の正規表現
- grep - Po "RE" : Pオプション → PCRE(Perl Compatible Regular Expressions) Perl互換の正規表現、oオプション→ only match text
$ echo value,1.2345cm, ... | grep -Po '(?<=value,)[0-9].*?(?=cm)'
> 1.2345
pythonによる先読み・後読みアサーションの例
import re
# 先読み. 後ろにbarがあるfooとマッチさせたい.
re.search('foo(?=bar)', 'foobar').group(0)
> foo
# 後読み. 前にabcがあるdefとマッチさせたい.
re.search('(?<=abc)def', 'abcdef').group(0)
> 'def'
# ハイフンに続く単語を探す。(\w は任意のUnicodeにマッチ, [a-zA-Z0-9_]と同じ)
re.search(r'(?<=-)\w+', 'spam-egg').group(0)
> 'egg'
# 否定先読み; eggとマッチしない1文字を抽出(先読みでマッチしないもの)。
re.search(r'.(?!egg)', 'spam-egg').group(0)
> 's'
# 先読みならば'-'がマッチする。
# 上記grepの例。
re.search('(?<=value,)\d.+(?=cm)', "ac, value,1.2345cm,...").group(0)
> '1.2345'
複数とマッチさせるとき
findall
次のようなXMLがあったとします。
formula.xml
<Formula>H</Formula>
<Formula>He</Formula>
<Formula>Li</Formula>
単文のみならば、re.search
で良いですが、全てマッチさせたいときは、findall
を使います。
import re
data = open(file_name, 'r').read()
re.findall('(?<=Formula>).+(?=\<)', data)
> [ 'H', 'He', 'Li']
マッチオブジェクト
マッチオブジェクトのブール値は常に True である。 match()
および search()
はマッチがないとき None を返すので、マッチがあるか単純な if 文で判定できる。欠損値を処理できるため、上記よりこちらの方が実用的。
import re
lst=[]
for i in data:
match = re.search('(?<=Formula>).+(?=\<)', i)
if(match):
lst.append(match.group(0))
言語処理100本ノック
http://www.cl.ecei.tohoku.ac.jp/nlp100/
第3章正規表現を一部やってみました。
#!/bin/sh
# 正規表現
wget "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/jawiki-country.json.gz"
gzip jawiki-country.json.gz
# 例題20 JSONデータの読み込み
cat jawiki-country.json | grep -Po '({\"text\":\s\"{{redirect|UK}).+\"title\":\s\"イギリス\"' > uk.json
# 例題21 カテゴリ名を含む行を抽出
cat uk.json | grep -Po '(Category).+]]'
# 例題22 カテゴリ名の抽出
cat uk.json | grep -Po '(?<=Category:).*?(?=[\||\]])'