Web クローラー | 入門用の正規表現チュートリアル

Web クローラー開発のための実用的なソース コード: https://github.com/MakerChen66/Python3Spider

オリジナルであることは簡単ではありません。この記事では盗作と転載が禁止されています。長年の実用的なクローラー開発経験の要約であり、侵害を調査する必要があります。 !

1. 正規表現の導入

正規表現とは何ですか?
正規表現は文字列を処理するための強力なツールであり、独自の文法構造を持ち、文字列の検索、置換、一致検証を簡単に実現できます。クローラーにとって、HTMLから必要な情報を抽出できるのは非常に便利です。

正規表現の応用
Webサイト開発、クローラー開発、ゲーム開発、データベース開発など。つまり、正規表現は文字列さえあれば使えます。

正規表現のインポート
正規表現には Python に独自の標準ライブラリがあり、インストールする必要はなく、インポートするだけです

import re



2 番目に、正規表現の使用

例の紹介
Open Source China が提供するオンライン正規表現テスト Web サイトを開きます:
https://tool.oschina.net/regex/

照合する文字を入力し、一般的な正規表現を選択するか、自分で記述すると、対応する照合結果が得られます。たとえば、ここに照合するテキストを次のように入力します。

hello,my phone number is 010-85623654 and email is mackerchen@aliyun.com,and my website is https://makerchen.com

この文字列には電話番号、電子メール、URL が含まれており、次の図に示すように、正規表現を使用して抽出します。
ここに画像の説明を挿入
Web ページの右側にある[ URL URL と一致] を選択すると、下部にテキストが表示されます。 URL。「メールアドレスと一致する」を選択すると、以下の本文にメールが表示されます。

これらはすべて正規表現マッチングを使用しています。つまり、特定のルールを使用して特定のテキストを抽出していますたとえば、URL の先頭はプロトコルの種類で、その後にコロンと二重スラッシュが続き、最後にドメイン名とパスが続きます。さらに、電子メールは文字列で始まり、次に @ 記号、最後にドメイン名という特定の構成形式を持ちます。

URL の場合、次の正規表現を使用して一致させることができます。

[a-zA-z]+://[^\s]*

この正規表現を使用して文字列を照合します。文字列に URL に類似したテキストが含まれている場合、それが抽出されます。

そこには特定の文法規則がありますたとえば、az は任意の小文字と一致することを意味し、\s は任意の空白文字と一致することを意味し、上記の ^\s は空白以外の文字 (\S に等しい) と一致することを意味し、* は任意の数の文字と一致することを意味します。前の文字

次の表に、一般的に使用される一致ルールを示します。

\w 文字、数字、アンダースコアを一致させる
\W 文字、数字、アンダースコア以外の文字と一致します
\s [\t\n\r\f] および ^\S に相当する任意の空白文字と一致します。
\S ^\s に相当する空でない文字と一致します。
\d [0-9] に相当する任意の数値に一致します
\D 数字以外の文字に一致します
\A 文字列の先頭と一致します
\Z 文字列の末尾に一致します。改行がある場合は、改行の前の最後の文字列にのみ一致します。
\z 文字列の末尾に一致します。改行がある場合は、改行も一致します。
\G 最後に試合が行われた場所での試合
\n 改行と一致する
\t タブに一致します
^ 文字列の行の先頭と一致します
$ 文字列の行末に一致します
改行を除く任意の文字と一致します。re.DOTALL フラグが指定されている場合は、改行を含む任意の文字と一致します。
[…] [amk] は a、m、または k に一致するなど、個別にリストされた文字のグループを表すために使用されます。
[^…] [] に含まれない文字 ([^abc] など) は、a、b、c 以外の文字と一致します。
* 0 個以上の式に一致します
+ 0 個以上の式に一致します
? 前述の正規表現で定義された 0 または 1 つのフラグメントと一致します。貪欲ではありません
{n} 先行する n 個の式と完全に一致します。たとえば、\d{10} は 10 個の数値と一致することを意味します。
{n,m} 前の正規表現で定義されたセグメントの n ~ m 倍に一致します。greedy
ある b
() グループを示す括弧で囲まれた式と一致します

読んだ後、少しめまいを感じるかもしれませんが、共通ルールの使用方法については後で詳しく説明しますので、心配しないでください

同様に、クローラは Java などの他のプログラミング言語でも実装できますが、Python が提供するクローラ ライブラリは他のプログラミング言語よりもはるかに豊富です

3. マッチング方法

3.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)
print(result)
print(result.group())
print(result.span())

出力は次のとおりです。

41
<_sre.SRE_Match object; span=(0, 25), match='Hello 123 4567 World_This'>
Hello 123 4567 World_This
(0, 25)

(^Hello\s\d\d\d\s\d{4}\s\w{10})
この長い文字列と一致するには、上記の正規表現を使用します。先頭の ^ は一致する文字列の先頭です。つまり、Hello で始まり、\s は空白文字に一致し、\d は数字に一致し、3 \ds は 123 に一致します。その後、スペースに一致する 1 \s を書き込みます。 ; 後ろに 4567 があります。実際には、まだ 4 \d を使用して照合できますが、このように書くのは面倒なので、前のルールに 4 回照合する、つまり照合することを表すために {4} を続けます。 4 つの数字、その後に 1 つの空白文字が続き、最後の \w{10} は 10 個の文字とアンダースコアに一致します。ここではターゲット文字列が一致していないことがわかりましたが、この方法でも一致する可能性はありますが、一致結果は短くなります。

出力から、結果が SRE_Match オブジェクトであることがわかり、一致が成功したことがわかります。このオブジェクトには 2 つのメソッドがあります: group() メソッドは一致したコンテンツを出力でき、結果は Hello 123 4567 World_This であり、これは正規表現ルールによって一致したコンテンツです、span() メソッドは一致した範囲を出力できます。結果は ( 0, 25) です。これは、元の文字列内の一致した結果文字列の位置範囲です。

3.2 検索()

前述したように、match() メソッドは string の先頭からマッチングを開始しますが、マッチング メソッドが先頭で一致しないと、マッチング全体が失敗します。これは別のメソッド search() です。このメソッドは、一致するときに文字列全体をスキャンし、最初に成功した一致結果を返します。または、検索後に見つからなかった場合は None を返します。

import re

content = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'
result = re.search('Hello.*?(\d+).*?Demo', content)
print(result)

出力は次のとおりです。

<_sre.SRE_Match object; span=(13, 53), match='Hello 1234567 World_This is a Regex Demo'>

マッチングの便宜のために、search() メソッドを使用しようとします。上記のコードの検索を一致するように変更すると、出力は None になります。

3.3 findall()

前述の search() メソッドは、正規表現に一致する最初のコンテンツを返しますが、正規表現に一致するすべてのコンテンツを取得したい場合は、 findall() メソッドを使用する必要があります。例は次のとおりです。

import re

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>'''

上記の HTML テキスト内のすべてのノードのハイパーリンク、歌手、曲名を取得したい場合は、search() メソッドを findall() メソッドに置き換えることができ、返される結果はリスト タイプです。リストの内容内のグループを取得するために走査される場合、コードは次のようになります。

results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S)
print(results)
print(type(results))
for result in results:
    print(result)
    print(result[0], result[1], result[2])

出力結果は次のとおりです。
ここに画像の説明を挿入
ご覧のとおり、findall() メソッドによって返される型は「list」であり、リスト内の各要素はタプル型であり、対応するインデックスを使用して取得できます。

3.4 サブ()

正規表現を使用して情報を抽出し、場合によってはテキストを変更する必要があります。このとき、replace() メソッドと同じ機能を持つ sub() メソッドを使用できますが、使用方法が異なり、replace ()メソッドは面倒なので、sub() メソッドを使用することをお勧めします例は次のとおりです。

import re

content = '54aK54yr5oiR54ix5L2g'
content = re.sub('\d+', '', content)
print(content)

sub() メソッドには 3 つの仮パラメータがあります。1 つ目は照合する内容を示す正規表現、2 つ目のパラメータは置換する文字列 (空でも可)、3 つ目のパラメータは元の文字列です。上記は数値を空に置き換えること、つまり数値を削除することを意味します

出力は次のとおりです。

aKyroiRixLg



3.5 コンパイル()

上で説明したメソッドはすべて文字列の処理に使用されますが、最後に、正規表現文字列を正規表現オブジェクトに変換して、以降の一致で再利用できるcompile()メソッドを紹介します。

例は次のとおりです。

import re

content1 = '2016-12-15 12:00'
content2 = '2016-12-17 12:55'
content3 = '2016-12-22 13:21'
pattern = re.compile('\d{2}:\d{2}')
result1 = re.sub(pattern, '', content1)
result2 = re.sub(pattern, '', content2)
result3 = re.sub(pattern, '', content3)
print(result1, result2, result3)

ここには 3 つの日付があるので、それぞれの日付から時刻を削除したいのですが、このとき、sub() メソッドを使用できます。このメソッドの最初のパラメータは正規表現ですが、同じ正規表現を 3 つ繰り返して記述する必要はありません。このとき、compile() メソッドを使用して正規表現を正規表現オブジェクトにコンパイルし、再利用できます。

出力は次のとおりです。

2016-12-152016-12-172016-12-22

さらに、compile() は re.S やその他の修飾子などの修飾子を渡すこともできるため、search() や findall() などのメソッドに追加で渡す必要はありません。したがって、compile() メソッドは正規表現をより適切に再利用できるようにするためのカプセル化層であると言えます。


4. マッチングモード

4.1 マッチターゲット

マッチング メソッドでは、match() メソッドを使用して一致した文字列の内容を取得できますが、文字列から内容の一部を抽出したい場合はどうすればよいでしょうか。前の例と同様に、テキストから電子メールや電話番号などのコンテンツを抽出します

ここでは、() 括弧を使用して、抽出する部分文字列を囲むことができます。() は、実際には部分式の開始位置と終了位置をマークします。マークされた各部分式は、順番に各グループに対応します。group( ) メソッドを呼び出してインデックスを渡します。グループの抽出結果を取得します。例は次のとおりです。

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())

ここでは文字列から 1234567 を抽出したいのですが、このとき数値部分の正規表現を () で囲んで group(1) を呼び出すと一致結果が得られます。

出力は次のとおりです。

<_sre.SRE_Match object; span=(0, 19), match='Hello 1234567 World'>
Hello 1234567 World
1234567
(0, 19)

ご覧のとおり、1234567 を取得することに成功しました。ここでは、group() とは異なる group(1) を使用します。後者は完全な一致結果を出力し、前者は () 結果で囲まれた最初の一致を出力します。正規表現の後ろに()で囲まれた内容がある場合は、group(2)、group(3)などを使用して取得できます。

4.2 ユニバーサルマッチング

先ほど書いた正規表現は実はもっと複雑で、空白文字があれば\sを書いて一致させ、数字があれば\dを書いて一致させるという作業が非常に重いです。実際には、使用できる別の汎用一致(ポイント スター) があるため、これを行う必要はまったくありません。その中で、 (ドット) は任意の文字に一致します (改行文字を除く)、 (スター) は前の文字に無制限に一致することを意味するため、これらを組み合わせて任意の文字に一致させることができます。一

上記の例に従って、正規表現を書き換えることができます。

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())

中間部分を直接省略して .* に置き換え、最後に終了文字列を追加すると、出力は次のようになります。

<_sre.SRE_Match object; span=(0, 41), match='Hello 123 4567 World_This is a Regex Demo'>
Hello 123 4567 World_This is a Regex Demo
(0, 41)

group() メソッドは、一致するすべての文字列を出力します。つまり、作成した正規表現はターゲット文字列の内容全体と一致します。span() メソッドは、文字列全体の長さである (0, 41) を出力します。

したがって、.* を使用して正規表現の記述を簡素化できます。

4.3 貪欲と非貪欲

上記の一般的なマッチング*を使用すると、場合によっては希望どおりのマッチング結果が得られない場合があります。次のように:

import re

content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*(\d+).*Demo$', content)
print(result)
print(result.group(1))

ここではまだ中央の数値を取得したいので、(\d+) は依然として中央に書かれています。数字の両側の内容がごちゃごちゃなので省略して と書きたいと思います最後に ^He.(\d+).*Demo$ の構成は同じと思われます。出力を見てみましょう。

<_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
7

奇妙なことが起こります。数字は 7 しか出ません。何が起こっているのでしょうか?

これには、貪欲なマッチングと非貪欲なマッチングの問題が関係します。貪欲一致では、.* は可能な限り多くの文字と一致します正規表現では、.* の後に \d+ (少なくとも 1 つの数値) が続き、特定の数値は指定されていません。したがって、.* は可能な限り多くの文字に一致します。ここでは、123456 が一致し、\d+ は数値のままになります。条件を満たす7、最終的な中身は7のみ

しかし、それは明らかに私たちにとって多大な不便となるでしょう。場合によっては、マッチング結果に何らかのコンテンツが不可解に欠落していることがあります。実際、ここでは非貪欲マッチングを使用するだけで済みます。非貪欲マッチングの記述方法は.*? 、もう一つ追加?、それではどのような効果が得られるのでしょうか?

例を挙げてもう一度見てみましょう。

import re

content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*?(\d+).*Demo$', content)
print(result)
print(result.group(1))

. を . ? に変換するだけで、貪欲でない一致になります。出力は次のとおりです。

<_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
1234567

この時点で、1234567 が正常に取得できます。その理由は想像できます。貪欲なマッチングはできるだけ多くの文字と一致すること、非貪欲なマッチングはできるだけ少ない文字と一致することですHello の後の空白文字に . ? が一致する場合、後続の文字は数字であり、\d+ が一致する可能性があるため、ここでは . ? は一致せず、 \d+ が次の数字の一致に使用されます。したがって、このように.*? は可能な限り少ない文字に一致し、\d+ の結果は 1234567 になります。

したがって、マッチングを行うときは、文字列の途中で非貪欲マッチングを使用するようにしてください。つまり、 . ?を使用してください。一致結果の欠落を避けるため

ただし、ここで注意が必要なのは、一致結果が文字列の末尾にある場合、 .*? は何も一致しない可能性があるため、 .* に変更する必要があることです

4.4 修飾子

正規表現には、一致するパターンを制御するためのオプションのフラグ修飾子をいくつか含めることができます。例は次のとおりです。

import re

content = '''Hello 1234567 World_This
is a Regex Demo
'''
result = re.match('^He.*?(\d+).*?Demo$', content)
print(result.group(1))

上の例と同様に、文字列に改行文字を追加しただけで、正規表現は同じであり、その中の数値を照合するために使用されます。出力を見てみましょう。

AttributeError: 'NoneType' object has no attribute 'group'

この操作はエラーを直接報告します。つまり、正規表現が文字列と一致せず、戻り結果は None になります。また、group() メソッドを呼び出して AttributeError を発生させます。

では、なぜ改行文字を追加すると一致しないのでしょうか? これは、改行文字以外の文字が一致するためで、改行文字が見つかった場合、.*? は一致しないため、一致は失敗します。このエラーを修正するには、修飾子 re.S を追加するだけです。

result = re.match('^He.*?(\d+).*?Demo$', content,re.S)

この修飾子の役割は、改行を含むすべての文字に一致させることです。この時点での出力は次のようになります。

1234567

re.S は Web ページのマッチングによく使用されます。HTML ノードには改行が含まれることが多いため、これを追加するとノード間の改行と一致する可能性があります。

さらに、次の表に示すように、いくつかの修飾子があります。

re.I 一致する大文字と小文字を区別しないようにする
re.L ローカリゼーション識別マッチング
レム 複数行の一致、^ と $ に影響する
レス make . 改行を含むすべての文字に一致します
リユー 文字は Unicode 文字セットに従って解析され、\w、\W、\b、\B に影響します。
レックス より柔軟な正規表現形式を提供する

4.5 エスケープマッチング

. は改行文字を含むすべての文字に一致しますが、ターゲット文字列に . が含まれている場合はどうすればよいかなど、多くの一致モードは正規表現で定義されています。

現時点では、以下に示すようにエスケープ マッチングを使用する必要があります。

import re

content = '(百度)www.baidu.com'
result = re.match('\(百度\)www\.baidu\.com', content)
print(result)

通常の一致パターンで使用される特殊文字に遭遇した場合は、バックスラッシュを追加してエスケープします。ここでは を使用します。一致させると、出力は次のようになります。

<_sre.SRE_Match object; span=(0, 17), match='(百度)www.baidu.com'>

ご覧のとおり、ここでは元の文字列が正常に一致しています。

これらは正規表現を作成するための一般的なマッチング パターンです。これらをマスターすると、正規表現マッチングを作成するのに非常に役立ちます。

4.6 結論

ここまでは正規表現の基本的な使い方を紹介しましたが、この後は正規表現を使ってまおやん動画データや修行僧データなどをクローリングする場合など、正規表現の使い方を具体例を用いて説明していきます。Webクローラに対応した実践記事もご覧ください


5. 原文へのリンク

私のオリジナルの公開アカウントの原文へのリンク:原文を読む

オリジナリティを高めるのは簡単ではありません。役に立ったと思ったら、高評価をいただければ幸いです。ありがとうございます。

6. 著者情報

著者: Xiaohong の釣り日報、目標: プログラミングをもっと面白くする!

元の WeChat 公開アカウント:「Xiaohong Xingkong Technology」。アルゴリズム、クローラー、ウェブサイト、ゲーム開発、データ分析、自然言語処理、AI などに重点を置いています。ご注目をお待ちしております。一緒に成長し、コーディングしましょう!

転載指示: この記事は盗作と転載を禁止しており、違反者は起訴されます。

おすすめ

転載: blog.csdn.net/qq_44000141/article/details/121457192