目次
1. 一般的なマッチングルールの概要
正規表現の一般的に使用されるマッチング ルール (学習 Web サイトを参照):正規表現 – 文法 | 初心者チュートリアル
2. 一般的に使用されるマッチング方法
1.マッチ
match メソッドでは、最初のパラメータは正規表現で渡され、2 番目のパラメータは一致する文字列です。
match メソッドは、文字列の「開始位置」から正規表現との照合を試み、一致する場合は成功した一致の結果を返し、一致しない場合は None を返します。例は次のとおりです。
import re
content = 'Hello 123 4567 World_This is a Regex Demo'
print(len(content)) #查看字符串的长度
result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}',content)
#result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}.*Demo$',content) #匹配所有字符
print(result)
print(result.group()) #返回匹配结果
print(result.span()) #输出范围
操作結果:
41 <re.Match オブジェクト; スパン=(0, 25), マッチ='Hello 123 4567 World_This'> Hello 123 4567 World_This (0, 25)
- ターゲットを一致させる
テキストの一部を抽出するには、大括弧 () を使用して、抽出する部分文字列を囲みます。() は実際には部分式の開始位置と終了位置をマークします。マークされた各部分式は順番に各グループに対応します。グループ メソッドを呼び出してグループのインデックスを渡し、抽出結果を取得します。例は次のとおりです:
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello\s(\d+)\sWorld',content)
print(result)
print(result.group()) #输出完整的匹配结果
print(result.group(1)) #输出第一个被()包围的匹配结果
print(result.span())
操作結果:
<re.Match オブジェクト; スパン=(0, 19), マッチ='こんにちは 1234567 世界'> こんにちは 1234567 世界 1234567 (0, 19)
- ユニバーサルマッチ
上記の正規表現は複雑なため、空のパケット文字がある場合は\sと一致し、数字がある場合は\dと一致する必要があります。一致する内容が多数ある場合は、この種の作業負荷は比較的大きいです。実際、ユニバーサル一致「.*」を使用できます。「.」は任意の文字と一致し、「*」は前の文字と無限に一致することを意味し、この 2 つの組み合わせは任意の文字と一致します。上記の例に従って、「.*」を使用して正規表現を書き換えます。
import re
content = 'Hello 123 4567 World_This is a Regex Demo'
result = re.match('^Hello.*Demo$',content)
print(result)
print(result.group()) #输出完整的匹配结果
print(result.span())
操作結果:
<re.Match オブジェクト; スパン=(0, 41), マッチ='こんにちは 123 4567 ワールド_これは正規表現のデモです'> こんにちは 123 4567 世界_これは正規表現のデモです (0, 41)
- 貪欲な人とそうでない人
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*(\d+).*Demo$',content)
print(result)
print(result.group(1))
操作結果:
<re.Match オブジェクト; スパン=(0, 40), マッチ='こんにちは 1234567 ワールド_これは正規表現のデモです'> 7
貪欲マッチングでは、数値 7 のみが取得されることがわかります。貪欲一致では、「.*」は可能な限り多くの文字に一致し、「.*」の後には \d+ (つまり少なくとも 1 つの数字) が続き、特定の数字は指定されていないため、「.*」を試してみてください。複数の数字を 123456 に一致させることができ、条件を満たす数字 7 が 1 つだけ残るため、最終的な内容は数字 7 のみになります。
もちろん、これによって同じコンテンツが得られることもあれば、一部のコンテンツが不可解に欠落することもあります。次に、非貪欲マッチングを比較し、2 つがどのように機能するかを確認します。
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*?(\d+).*?Demo$',content)
print(result)
print(result.group(1))
操作結果:
<re.Match オブジェクト; scan=(0, 40), match='こんにちは 1234567 World_これは正規表現のデモです'> 1234567
非貪欲マッチングでは、1234567 個の数値文字列全体が取得できることがわかります。貪欲マッチングとは対照的に、非貪欲マッチングは、できるだけ少ない文字と一致します。実際のアプリケーションでは、マッチング結果が失われる問題を避けるために、できるだけ非貪欲マッチングを使用する必要があります。
ただし、一致するものが文字列の末尾のコンテンツである場合は、末尾のコンテンツと一致するまで可能な限り多くのコンテンツを一致させるため、「.*」貪欲一致を使用することをお勧めします。また、「.*?」の非貪欲一致は、可能な限り少ないコンテンツと一致します。具体的な例は次のとおりです。
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result1 = re.match('^He.*?a\s(.*?)',content)
result2 = re.match('^He.*?a\s(.*)',content)
print('result1(非贪婪匹配)',result1.group(1))
print('result2(贪婪匹配)',result2.group(1))
操作結果:
result1(非貪欲一致) result2(貪欲一致) 正規表現デモ
- 修飾子
re.I: 一致の大文字と小文字を区別しないようにする
re.L: 局所的な認識 (ローカル認識) マッチングを実現する
re.M: 複数行の一致、^ と $ に影響
re.S: 改行文字を含むすべての文字を一致させる
re.U: Unicode 文字セットに従って文字を解析します。このフラグは \w、\W、\b、\B に影響します。
re.X: このフラグにより、より柔軟な書式設定が可能になり、正規表現をより理解しやすく記述できるようになります。
文字列に改行文字が含まれている場合、それが上記のように一致すると、エラーが報告されます。
import re
content = '''Hello 1234567 World_This
is a Regex Demo
'''
result = re.match('^He.*?(\d+).*?Demo$',content)
print(result.group(1))
-------------------------------------------------- ------------------------
AttributeError トレースバック (最新の呼び出しは最後)
~\AppData\Local\Temp/ipykernel_448/501554157.py (<モジュール>)
4 ''
5 result = re.match('^He.*?(\d+).*?Demo$',content)
----> 6 print(result.group(1))
AttributeError: 'NoneType'オブジェクトには属性「グループ」がありません
上記の操作の結果はエラーを報告します。つまり、正規表現が文字列と一致せず、戻り結果は None で、グループ メソッドが呼び出されるため、AttributeError が発生します。
.*? は改行文字以外の文字と一致するため、改行文字が見つかると一致は失敗します。そして、修飾子 re.S を追加するだけでエラーを修正できます
import re
content = '''Hello 1234567 World_This
is a Regex Demo
'''
result = re.match('^He.*?(\d+).*?Demo$',content,re.S)
print(result.group(1))
操作結果:
1234567
- エスケープマッチ
ターゲット文字列に . などの改行文字以外の文字が含まれている場合は、文字列の前にバックスラッシュ「\」を追加すると、一致が得られます。例は次のとおりです。
import re
content = '(百度)www.baidu.com'
result = re.match('\(百度\)www\.baidu\.com',content)
print(result.group())
操作結果:
(百度) www.baidu.com
2.検索
上記の match メソッドでは、文字列の先頭から照合を開始するため、先頭が一致しないと全体の照合が失敗することになります。matchメソッドは対象文字列の先頭の内容を考慮して使用する必要があるため、マッチングには不便です
この検索メソッドは、一致する文字列全体をスキャンし、最初に成功した一致を返します。つまり、正規表現は文字列の一部にすることができ、match メソッドが文字列の先頭とどのように一致するかを考える必要はありません。一致する場合、検索メソッドは、最初に一致する文字列が見つかるまで各文字で始まる文字列を順番にスキャンし、一致した内容を返します。スキャン後に一致する文字列が見つからない場合は、None を返します。上記のコードの match メソッドを次のように search メソッドに変更します。
import re
content = '''Hello 1234567 World_This
is a Regex Demo
'''
result = re.search('He.*?(\d+).*?Demo',content,re.S)
print(result)
print(result.group(1))
操作結果:
<re.Match オブジェクト; スパン=(0, 40), マッチ='こんにちは 1234567 世界_これは正規表現のデモです'> 1234567
マッチングの利便性を高めるため、できる限り検索メソッドを使用し、その後、検索を使用していくつかの正規表現の例を記述し、対応する情報の抽出を実現することをお勧めします。
html = '''<div id="songs-list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路上有你</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li data-view="4" class="active">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul>
</div>'''
import re
result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>',html,re.S)
if result:
print(result.group(1),result.group(2))
操作結果:
Qi Qin の過去の出来事は風に従って
結果からわかるように、これは、アクティブなクラスの li ノード内のハイパーリンクで、歌手と曲のタイトルが含まれています。active が削除された場合はどうなりますか? コードを次のように書き換えます。
import re
result = re.search('<li.*?singer="(.*?)">(.*?)</a>',html,re.S)
if result:
print(result.group(1),result.group(2))
操作結果:
任仙奇滄海は笑う
active タグを削除した後、文字列の先頭から検索を開始すると、修飾されたノードが 2 番目の li ノードとなり、それ以降は一致しなくなるため、実行結果が変わります。これら 2 つの一致では re.S が使用されているため、.*? は改行と一致し、以下の re.S を削除してコードを次のように書き換えます。
import re
result = re.search('<li.*?singer="(.*?)">(.*?)</a>',html)
if result:
print(result.group(1),result.group(2))
操作結果:
栄光の日々を超えて
re.S が削除されると、一致結果は 4 番目の li ノードの内容になります。これは、2 番目と 3 番目の li ノードの両方に改行文字が含まれていますが、4 番目の li ノードには改行文字が含まれていないため、一致は成功します。
3.フィンダル
上記の検索メソッドは、正規表現に一致する最初の文字列を返します。ただし、正規表現に一致するすべての文字列を取得したい場合は、findall メソッドを使用できます。
html = '''<div id="songs-list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路上有你</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li data-view="4" class="active">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul>
</div>'''
results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>',html,re.S)
print(type(results),results)
for result in results:
print(result) #输出位元组
print(result[0],result[1],result[2]) #输出字符串
操作結果:
<class 'list'> [('/2.mp3', 'Ren Xianqi', 'A Laugh from the Sea'), ('/3.mp3', 'Qi Qin', 'The Past Follows the Wind') 、('/4 .mp3'、'ビヨンド'、'輝かしい年')、('/5.mp3'、'ケリー・チェン'、'メモ帳')、('/6.mp3'、'テレサ・テン'、 「人々が長続きしますように」 )] ('/2.mp3', 'Ren Xianqi', 'A Laugh in the Sea') /2.mp3 Ren Xianqi の Laugh in the Sea ('/3.mp3', 'Qi Qin', 'The Past Follows the Wind') /3. mp3 斉と秦の過去を風と共に ('/4.mp3', 'beyond', 'Glory Years') /4.mp3 Beyond Glory Years (' /5.mp3', 'ケリー・チェン', 'メモ帳') /5.mp3 ケリー・チェンのメモ帳 ('/6.mp3', 'テレサ・テン', 'I wish you a long time') /6.mp3 テレサ・テン長い時間を願っています
この結果から、search メソッドを findall メソッドに置き換えることによって返される結果はリスト型であり、コンテンツの各グループをトラバーサルによって順番に取得する必要があることがわかります。一般に、最初に一致した文字列だけを取得したい場合は search メソッドを使用し、複数のコンテンツを抽出する必要がある場合は findall メソッドを使用します。
4.サブ
正規表現を使用して情報を抽出するだけでなく、テキスト情報を変更することもできます。たとえば、テキスト文字列から数字を削除したい場合、repalce メソッドを使用するのは面倒すぎます。このとき、sub メソッドを使用してそれを実現できます。最初のパラメータは通常のパラメータに渡されます。変更する必要がある情報に一致する式を使用し、2 番目のパラメータが置き換えられるように渡されます。コンテンツ、3 番目のパラメータは元の文字列です。以下に例を入力します。
import re
content = "121fef342dkjdsd87f7hiud3334"
content = re.sub('\d+','',content)
print(content)
操作結果:
fefdkjdsdfhiud
5.コンパイル
コンパイルは、正規文字列を正規表現オブジェクトにコンパイルして、後続の一致で再利用できます。例は次のとおりです。
import re
content1 = '2022/7/23晴'
content2 = '2022/7/24小雨'
content3 = '2022/7/25雷阵雨'
pattern = re.compile('\d{4}/\d{1}/\d{2}')
result1 = re.sub(pattern,'',content1)
result2 = re.sub(pattern,'',content2)
result3 = re.sub(pattern,'',content3)
print(result1,result2,result3)
3. 基本的なクローラケースの実戦
クロールされた URL は: Douban Reading、クロールされたコンテンツは: ハイパーリンク、本のタイトル、著者、出版日です。
import re
import requests
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"}
content = requests.get('https://book.douban.com/',headers = headers).text
pattern = re.compile('<li.*?cover">.*?href="(.*?)".*?title="(.*?)".*?more-meta">.*?author">(.*?)</span>.*?year">(.*?)</span>.*?</li>',re.S)
results = re.findall(pattern,content)
for result in results:
url,name,author,date=result
author = re.sub('\s','',author)
year = re.sub('\s','',year)
print(url,name,author,date)
正規表現を使用して Web ページのコンテンツをクロールする効率は比較的低く、将来的には pyquery や beautifulsoup などのライブラリを学習し続ける方が便利です。