注意: このコラムの記事はクローラーに偏っているため、Python のすべてをカバーすることは不可能ですが、ここでは重要なポイントのみに焦点を当てます。
記載されていない点があると思われる場合は、追加してください〜前回のおすすめ:【Python クローラー開発の基礎①】Python の基礎 (1)
前回の記事で Python の基本的な変数の型について説明しましたが、今日は Python がクローラー開発でよく使用する「正規表現」について見ていきます。
記事ディレクトリ
1 正規表現とは
正規表現 (正規表現、コードでは regex、regexp、または RE と略されることがよくあります) は、テキスト データの検索、置換、分割、および照合に使用されるパターン化された文字列です。基本的な考え方は、いくつかの特殊文字を使用して特定のパターンを表し、テキスト内でこのパターンと一致させることです。
正規表現の役割:
- Matching : 指定された文字列が正規表現のフィルター ロジックを満たすかどうかを判断します。
- 部分文字列の取得: 正規表現を使用して文字列から必要な特定の部分を取得できます。
正規表現の特徴:
- 非常に強力な柔軟性、ロジック、機能性。
- 弦の複雑な制御を非常に簡単な方法で素早く実現できます。
- 初めて連絡を取る人にとって、それは比較的わかりにくいものです。
2 Python での正規表現の使用
Python では、主にreモジュールとregexモジュールを使用して正規表現操作を実装します。以下にそれぞれ詳しく説明します。
2.1 リモジュール
Python の re モジュール (正規表現) は、文字列のパターン マッチング、置換、分割を行うための強力かつ柔軟なツールです。re モジュールを使用すると、テキスト ファイル、ログ ファイル、プログラミング言語コードなどを含むさまざまな文字データを処理できます。re モジュールには、検索、置換、分割、一致、コピー、抽出などを含む多数の正規表現関数が含まれており、ユーザーが効率的なテキスト処理タスクを完了するのに役立ちます。
注: Python には re モジュールが付属しているため、追加のインストールは必要ありません。したがって、他のサードパーティライブラリをインストールする場合のように、ターミナルで pip install コマンドを実行せずに re モジュールを使用することも非常に便利です。
2.1.1 re.search()
re.search() は、Python re モジュールで一般的に使用される関数であり、文字列内の指定された正規表現パターンを検索するために使用されます。検索時、関数はパターンに一致する最初の文字列部分文字列が見つかるまで文字列全体をスキャンし、一致するオブジェクト (Match Object) を返します。一致する部分文字列が見つからない場合、関数は None を返します。
re.search() 関数の一般的な使用法は次のとおりです。
match_object = re.search(pattern, string, flags=0)
このうち、pattern パラメータは照合対象の正規表現を示し、string パラメータは照合対象の文字列を示し、flags パラメータは照合オプション(大文字と小文字を区別するかどうかなど) を示します。関数が一致するオブジェクトを返した場合、match_object.group() メソッドを呼び出して、一致した部分文字列を取得できます。このメソッドのパラメータは、取得する部分文字列のシリアル番号を表します(正規表現に複数の括弧がある場合) 、各括弧内は左から右に向かって通し番号が増加するグループを示します。
たとえば、正規表現の文字列を検索するサンプル コードを次に示します。
import re
text = "Python is a popular programming language"
pattern = "programming"
match_object = re.search(pattern, text)
if match_object:
print("Found a match:", match_object.group())
else:
print("No match found.")
出力結果:
Found a match: programming
2.1.2 re.match()
Python の re.match() モジュールは、文字列内の正規表現の一致を検索するために使用される re.search() モジュールに似ています。ただし、re.match() は文字列の先頭でのみ一致し、先頭で一致が見つからない場合は None オブジェクトを返します。したがって、 re.match() は、文字列の先頭で一致する必要があるシナリオにより適しています。
match の関数の使用法は search の関数と同じです。同じテスト文字列と match によって返される結果を見てみましょう。
text = "Python is a popular programming language"
pattern = "programming"
match_object = re.match(pattern, text)
if match_object:
print("Found a match:", match_object.group())
else:
print("No match found.")
出力:
No match found.
プログラミングは最初の単語ではないので一致しません。
2.1.3 re.findall()
re.findall() は、Python の re モジュールによって提供される別のパターン マッチング関数です。文字列内の正規表現に一致するすべてのパターンを検索し、リストを返します。リスト内の各要素は正規表現と同じです。パターン マッチング部分文字列。と とは異なり
、最初または最後の一致だけでなく、すべての一致が返されます。したがって、特定の正規表現に一致するテキスト内のパターンのインスタンスをすべて検索する必要がある場合、これは非常に便利な関数です。re.search()
re.match()
re.findall()
re.findall()
この関数のサンプルコードは次のとおりです。
import re
# 定义一个正则表达式,匹配以数字开头的子字符串
pattern = r'\d+'
# 定义一个待匹配的字符串
text = "Today is Oct 15, 2021, and the temperature is 20 degrees Celsius."
# 使用re.findall()函数查找所有匹配项,并将它们存储在一个list对象中
matches = re.findall(pattern, text)
# 输出匹配结果
print(matches)
r'\d+'
この例では、まず、数字で始まる部分文字列を照合するための正規表現パターン を定義します。次に、一致する文字列 を定義しますtext
。次に、re.findall()
関数を使用してすべての一致を検索し、リスト オブジェクトに格納しますmatches
。最後にマッチング結果を画面に出力します。
出力結果:
['15', '2021', '20']
これは、例のテキストには、数字で始まる 3 つの部分文字列、つまり 15、2021、および 20 度があるためです。re.findall()
関数はそれらを検索し、リスト オブジェクトに保存します。
2.1.4 re.sub()
re.sub() は、Python re モジュールで提供されるもう 1 つのパターン マッチング関数であり、string 内の特定のパターンと一致する部分文字列を置換するために使用されます。re.sub() 関数は、指定されたパターンに一致するすべての部分文字列が指定された内容に置き換えられた新しい文字列を返します。
以下は、文字列置換に re.sub() を使用する方法の簡単なコード例です。
import re
# 定义一个正则表达式,匹配所有'is'字符
pattern = 'is'
# 定义一个待匹配的字符串
text = "The pattern of the book is not easy to find."
# 使用re.sub()函数将匹配项替换为指定字符串
new_text = re.sub(pattern, "was", text)
# 输出结果
print(new_text)
'is'
この例では、最初にすべての文字に一致する正規表現パターン を定義しますis
。次に、一致する文字列 を定義しますtext
。次に、re.sub()
関数を使用して、すべての一致を に置き換えます"was"
。最後に、置換後の新しい文字列を出力します。
出力:
The pattern of the book was not easy to find.
2.2 正規表現モジュール
標準ライブラリの re モジュールに加えて、regex モジュールなどのサードパーティ正規表現モジュールがいくつかあります。これらは、re モジュールよりも包括的かつ高度で、Perl 正規表現構文と互換性のある関数を提供します。
regex モジュールは re モジュールに似ており、re モジュールのほとんどの機能を提供しますが、複雑なアサーション、Unicode 属性、ネストされた構造の照合など、より多くの正規表現構文と機能をサポートしています。さらに、regex モジュールのパフォーマンスも re モジュールよりも優れており、より大きな正規表現や長いテキスト データを処理できます。
つまり、Python には多数の正規表現モジュールがあり、その中で最もよく使われるのが標準ライブラリの re モジュールです。正規表現を扱うときにより複雑な構文と機能が必要な場合は、regex モジュールを試すことができます。
3 正規表現の分類
正規表現は、いくつかの通常の文字といくつかのメタ文字(メタ文字) で構成されます。通常の文字には大文字、小文字、数字が含まれますが、メタ文字には特別な意味があり、これを照合に使用するトークンとします。
3.1 単純なメタキャラクター
メタキャラクター | 効果 |
---|---|
\ | 次の文字トークン、後方参照、または 8 進エスケープを入れます。 |
^ | 入力行の先頭と一致します。 |
$ | 入力行の末尾と一致します。 |
(アスタリスク)* | 前の部分式と何度でも一致します。 |
(プラス記号) + | 前の部分式と 1 回以上 (1 回以上) 一致します。 |
? | 直前の部分式と 0 回または 1 回一致します。 |
サンプルコード:
import re
# 匹配开头字符
res1 = re.match('^a', 'abandon')
print(res1) # <re.Match object; span=(0, 1), match='a'>
print(res1.group()) # a
# 匹配结尾字符
res2 = re.match('.*d$', 'wood')
print(res2) # <re.Match object; span=(0, 4), match='wood'>
print(res2.group()) # wood
# 匹配至少出现一次的字符
res3 = re.match('a+', 'aabcd')
print(res3) # <re.Match object; span=(0, 2), match='aa'>
print(res3.group()) # aa
# 匹配一次或零次的字符
res4 = re.match('a?', 'aaabandon')
print(res4) # <re.Match object; span=(0, 1), match='a'>
print(res4.group()) # a
3.2 単一文字のマッチングのためのメタキャラクター
メタキャラクター | 効果 |
---|---|
。 (点) | 「\n」と「\r」を除く任意の 1 文字と一致します。 |
\d | 数字と一致します。 |
\D | 数字以外の文字と一致します。 |
\f | フォーム フィード文字と一致します。 |
\n | 改行文字と一致します。 |
\r | キャリッジリターンと一致します。 |
\s | スペース、タブ、フォーム フィードなどの非表示文字と一致します。 |
\S | 表示されている任意の文字と一致します。 |
\t | タブ文字と一致します。 |
\w | アンダースコアを含む任意の単語文字と一致します。 |
\W | 単語以外の文字と一致します。 |
注: このタイプの 1 つ以上の文字と一致するには、メタキャラクターの後にプラス記号 (+) を追加します。
コード例 1 (.):
# 指定要匹配的模式
pattern = "py."
# 测试字符串1
test_str1 = "python"
result1 = re.match(pattern, test_str1)
print(result1) # 输出 <re.Match object; span=(0, 3), match='pyt'>
コード例 2 (\d, \D):
# 指定要匹配的模式
pattern = "\d"
# 测试字符串2
test_str2 = "The price is 199.99 dollars"
result2 = re.findall(pattern, test_str2)
print(result2) # 输出['1', '9', '9', '9', '9']
# 指定要匹配的模式
pattern = "\D"
# 测试字符串3
test_str3 = "My phone number is 555-1234"
result3 = re.findall(pattern, test_str3)
print(result3) # 输出 ['M', 'y', ' ', 'p', 'h', 'o', 'n', 'e', ' ', 'n', 'u', 'm', 'b', 'e', 'r', ' ', 'i', 's', ' ', '-']
コード例 4 (\s, \S):
# 指定要匹配的模式
pattern1 = r"\s+" # 匹配一个或多个空白字符
pattern2 = r"\S+" # 匹配一个或多个非空白字符
# 测试字符串1
test_str1 = "Hello\tworld\n"
result1 = re.findall(pattern1, test_str1)
print(result1) # 输出 ['\t', '\n']
result2 = re.findall(pattern2, test_str1)
print(result2) # 输出 ['Hello', 'world']
# 测试字符串2
test_str2 = " This is a demo. "
result3 = re.findall(pattern1, test_str2)
print(result3) # 输出 [' ', ' ', ' ']
result4 = re.findall(pattern2, test_str2)
print(result4) # 输出 ['This', 'is', 'a', 'demo.']
コード例 4 (\w, \W):
# 指定要匹配的模式
pattern1 = r"\w+" # 匹配一个或多个单词字符
pattern2 = r"\W+" # 匹配一个或多个非单词字符
# 测试字符串1
test_str1 = "Hello, world!"
result1 = re.findall(pattern1, test_str1)
print(result1) # 输出 ['Hello', 'world']
result2 = re.findall(pattern2, test_str1)
print(result2) # 输出 [', ', '!']
# 测试字符串2
test_str2 = "This is a demo."
result3 = re.findall(pattern1, test_str2)
print(result3) # 输出 ['This', 'is', 'a', 'demo']
result4 = re.findall(pattern2, test_str2)
print(result4) # 输出 [' ', ' ', ' ', '.']
3.3 文字セットマッチングのためのメタキャラクター
メタキャラクター | 効果 |
---|---|
[xyz] | キャラクターのコレクション。含まれている文字のいずれかと一致します。 |
コード例:
import re
# 指定要匹配的模式
pattern1 = r"[aeiou]" # 匹配任何元音字母
pattern2 = r"[A-Z]" # 匹配任何大写字母
# 测试字符串1
test_str1 = "Hello, world!"
result1 = re.findall(pattern1, test_str1)
print(result1) # 输出 ['e', 'o', 'o']
# 测试字符串2
test_str2 = "This is a Demo."
result2 = re.findall(pattern2, test_str2)
print(result2) # 输出 ['T', 'D']
3.4 量子マッチングのためのメタキャラクタ
メタキャラクター | 効果 |
---|---|
{n} | n は負ではない整数です。一致が n 回決定されました。 |
{n,} | n は負ではない整数です。少なくとも n 回一致します。 |
{n,m} | m と n は両方とも非負の整数であり、n<=m です。少なくとも n 回、最大で m 回一致します。 |
コード例:
import re
# 指定要匹配的模式
pattern1 = r"[a-z]{3}" # 匹配任何由三个小写字母组成的连续子串
pattern2 = r"\d{2,3}" # 匹配任何由两个或三个数字组成的连续子串
# 测试字符串1
test_str1 = "apple banana cherry"
result1 = re.match(pattern1, test_str1)
print(result1) # 输出 <re.Match object; span=(0, 3), match='app'>
# 测试字符串2
test_str2 = "1234567890 12 123 1234"
result2 = re.findall(pattern2, test_str2)
print(result2) # 输出 ['12', '123', '234']
3.5 グループマッチング用のメタキャラクター
メタキャラクター | 効果 |
---|---|
() | ( と ) の間の式を「グループ」として定義し、この式に一致する文字をタプルに保存します |
サンプルコード:
# 指定要匹配的模式
pattern1 = r"(\d{3})-(\d{4})-(\d{4})" # 匹配格式为 3-4-4 的电话号码
pattern2 = r"<(\w+)>.*</\1>" # 匹配任何形如 <tag>value</tag> 的 XML 节点
# 测试字符串1
test_str1 = "My phone number is 123-4567-8901."
result1 = re.search(pattern1, test_str1)
if result1:
area_code, prefix, line_number = result1.groups()
print("Area code: {}, Prefix: {}, Line number: {}".format(area_code, prefix, line_number))
else:
print("No match.")
# 测试字符串2
test_str2 = "<title>This is a title</title>"
result2 = re.match(pattern2, test_str2)
if result2:
tag = result2.group(1)
print("Tag name: {}".format(tag))
else:
print("No match.")
出力:
Area code: 123, Prefix: 4567, Line number: 8901
Tag name: title
上記の例では、2 つのパターン(\d{3})-(\d{4})-(\d{4})
(ここで、 はそれに含まれる文字セットをキャプチャ グループとして使用する()
ことを意味しgroups()
、グループの内容は後続の処理のメソッドを通じて取得できます) と<(\w+)>.*</\1>
(ここで は最初のキャプチャを参照することを\1
意味します) を定義しました。グループに一致するコンテンツ)。次に、re.search
この関数2 つの異なるテスト文字列を検索し、それらがこれらのパターンに一致するかどうかを確認します。最初のテスト文字列には の形式3-4-4
の、それを照合し、その番号の市外局番、プレフィックス、回線番号の情報をさらに抽出しました。2 番目のテスト文字列は XML ノードであり、これを照合し、さらにノードの名前を抽出しました。
必要に応じてこの例を変更して、より複雑なテキスト処理関数を実装できます。
- 最後に、メタ文字「|」があり、これは 2 つの一致条件に対して論理「or」(または) 演算を実行するために使用されます。
サンプルコードは次のとおりです。
# 指定要匹配的模式
pattern = r"(cat|dog|bird)\d*" # 匹配任何形如 cat\d* 或 dog\d* 或 bird\d* 的字符串
# 测试字符串
test_str = "I have a cat3 and a dog4 but no bird."
results = re.findall(pattern, test_str)
if results:
print("Matching results: ", results)
else:
print("No match.")
出力:
Matching results: ['cat', 'dog', 'bird']
4 正規表現の等価性
正規表現が理解しにくいのは、等価性の概念が存在するためです。この概念により、理解の難しさが大幅に増し、多くの初心者が混乱しているようです。等価性を元の書き方に戻せば、正規表現は非常に簡単になります。話すのと同じように、自分で表現できます。正規表現を書きましょう。
?、*、+、\d、\w はすべて同等の文字です。
- ? 一致する長さ {0,1} と同等
- *一致する長さ {0,} と同等
- + は一致する長さ {1,} と同等です
- \d は [0-9] に相当します。
- \D は [^0-9] と同等です
- \w は [A-Za-z_0-9] に相当します。
- \W は [^A-Za-z_0-9] に相当します。
5 貪欲なマッチング
正規表現の貪欲マッチングと非貪欲マッチングは、正規表現内に一致する可能性のあるテキストが複数ある場合に、異なるマッチング方法を選択することを意味します。
貪欲なマッチングとは、マッチング時にすべての修飾された文字列を可能な限りマッチングすること、つまり、長いテキストを最初にマッチングすることを指します。たとえばa.*b
、a
は で始まり でb
終わることを意味し、中間の文字 (スペースを含む) は少なくとも 1 回は出現し、正規表現エンジンは、条件を満たす文字列を照合するときに、条件を満たすために左端からできるだけ多くの文字を選択します。ルール。たとえば、 string の場合abbbcbbbd
、この正規表現の貪欲一致の結果は ですabbbcbbb
。
これに対応して、非貪欲マッチングは遅延マッチングまたは最小マッチングとも呼ばれます。これは、マッチング中に条件を満たす最短の文字列のみをマッチングすることを指します。デフォルトでは、正規表現エンジンは貪欲マッチング モードを採用し、量指定子の後に ? プレフィックスを追加すると、非貪欲マッチング モードに変換できます。例えばa.*?b
、a
で始まり、 でb
終わり、途中の任意の文字(スペースも含む)が少なくとも1回出現し、?
を追加した後、最短の条件でこの規則を満たすことを意味します。たとえば、 string の場合abbbcbbbd
、この正規表現の非貪欲一致結果は ですabb
。
- 正規表現の貪欲一致または非貪欲一致は、量指定子の後に疑問符があるかどうかによって決まります。これは実際のニーズに応じて決定する必要があります。一般に、貪欲でない方が安全で信頼性が高くなりますが、適格なテキストを見つけるためにエンジンがバックトラックし続ける必要があるため、パフォーマンスが低下することにも注意してください。
サンプルコード:
# 原始字符串
str1 = "hello-world-and-hi"
# 贪婪匹配,获取第一个连字符到最后一个连字符之间的所有字符
result1_greedy = re.findall(r"-.*-", str1)
print(result1_greedy) # 输出 ['-world-and-']
# 非贪婪匹配,只获取第一个连字符到第二个连字符之间的所有字符
result1_non_greedy = re.findall(r"-\w+?-", str1)
print(result1_non_greedy) # 输出 ['-world-']
# 原始字符串
s = "hello world, this is a test string."
# 贪婪匹配,获取以 h 开头、以空格结尾的所有字符
result_greedy = re.findall(r"h.* ", s)
print(result_greedy) # ['hello world, this is a test ']
# 非贪婪匹配,获取以 h 开头、以空格结尾的最短字符
result_non_greedy = re.findall(r"h.*? ", s)
print(result_non_greedy) # ['hello ', 'his ']