web-dev-qa-db-ja.com

正規表現を使用してHTMLを解析するのはなぜですか?

AskerがHTMLから情報を取得するために正規表現を使用しているstackoverflowのすべての質問には、HTMLの解析に正規表現を使用しないという「答え」が必ずあるようです。

何故なの? Beautiful Soup のように引用符と引用符のない「本物の」HTMLパーサーがあることは知っていますが、強力で便利だと確信していますが、単純なことをしているだけなら、すばやく、または汚いので、いくつかの正規表現ステートメントがうまく機能するのに、なぜそんなに複雑なものを使用するのが面倒ですか?

さらに、正規表現について理解できない基本的なものがあり、一般的に構文解析に悪い選択になりますか?

200
ntownsend

正規表現ではHTML解析全体を行うことはできません。正規表現では不可能な開始タグと終了タグの一致に依存するためです。

正規表現は 正規言語 のみに一致しますが、HTMLは コンテキストフリー言語 およびnota通常の言語(@StefanPochmannが指摘したように、通常の言語はコンテキストフリーでもあるため、コンテキストフリーは必ずしも通常ではないという意味ではありません)。 HTMLの正規表現でできることは、ヒューリスティックだけですが、すべての条件で機能するわけではありません。正規表現と誤って一致するHTMLファイルを提示することは可能です。

202
Johannes Weiss

Quick´n´dirtyの正規表現では問題ありません。しかし、知っておくべき基本的なことは、HTMLを正しく解析する正規表現を構築することは不可能であることです。

その理由は、正規表現が任意にネストされた式を処理できないためです。 ネストされたパターンの一致に正規表現を使用できますか? を参照してください

34
kmkaplan

http://htmlparsing.com/regexes から)

<img>タグからURLを抽出しようとしているHTMLファイルがあるとします。

<img src="http://example.com/whatever.jpg">

したがって、Perlで次のような正規表現を記述します。

if ( $html =~ /<img src="(.+)"/ ) {
    $url = $1;
}

この場合、 $urlは実際にhttp://example.com/whatever.jpg。しかし、次のようなHTMLの取得を開始するとどうなりますか。

<img src='http://example.com/whatever.jpg'>

または

<img src=http://example.com/whatever.jpg>

または

<img border=0 src="http://example.com/whatever.jpg">

または

<img
    src="http://example.com/whatever.jpg">

または、あなたは

<!-- // commented out
<img src="http://example.com/outdated.png">
-->

それはとても単純に見え、単一の不変のファイルに対しては単純かもしれませんが、任意のHTMLデータに対して行うことであれば、正規表現は将来の心痛のレシピにすぎません。

19
Andy Lester

2つの簡単な理由:

  • 悪意のある入力に耐えられる正規表現を書くのは難しい。事前に構築されたツールを使用するよりもはるかに難しい
  • 必然的に行き詰まるとんでもないマークアップで動作する正規表現を書くのは難しいです。事前に構築されたツールを使用するよりもはるかに難しい

一般的な解析に対する正規表現の適合性に関して:それらは適切ではありません。ほとんどの言語を解析するために必要な正規表現の種類を見たことがありますか?

16
Hank Gay

構文解析に関する限り、正規表現は、入力がトークンに分解される「字句解析」(レクサー)段階で役立ちます。実際の「解析ツリーの構築」段階ではあまり役に立ちません。

HTMLパーサーの場合、整形式のHTMLのみを受け入れ、正規表現が実行できない機能を必要とすることを期待します(「カウント」できず、所定の数の開始要素が同じ数でバランスが取れていることを確認します)終了要素の)。

16
Vatine

ブラウザーがかなり自由な方法で処理するHTMLを「台無しにする」多くの方法がありますが、正規表現ですべてのケースをカバーするためにブラウザーの自由な動作を再現するにはかなりの努力が必要になるため、その場合、システムに深刻なセキュリティギャップが生じる可能性があります。

8
Tamas Czinege

問題は、HTMLとregexに関連する質問をするほとんどのユーザーが、機能する独自の正規表現を見つけることができないため、これを行うことです。次に、DOMパーサーまたはSAXパーサーなどを使用すると、すべてが簡単になるかどうかを考える必要があります。これらは、XMLに似たドキュメント構造を操作するために最適化および構築されています。

もちろん、正規表現で簡単に解決できる問題があります。しかし、重点は簡単ににあります。

http://.../のように見えるすべてのURLを検索するだけであれば、正規表現で問題ありません。ただし、クラス「mylink」を持つa要素にあるすべてのURLを検索する場合は、適切なパーサーを使用することをお勧めします。

7
okoman

正規表現は、ネストされたタグ構造を処理するようには設計されていません。実際のHTMLで発生する可能性のあるすべてのEdgeケースを処理するのは、せいぜい複雑(最悪の場合、不可能)です。

6
Peter Boughton

その答えは計算理論にあると思います。正規表現を使用して言語を解析するには、定義により「正規」である必要があります( link )。 HTMLは、通常の言語の多くの基準を満たしていないため、通常の言語ではありません(HTMLコードに固有の多くのレベルのネストに関係しています)。計算理論に興味があるなら、 this bookをお勧めします。

6
taggers

この式は、HTML要素から属性を取得します。以下をサポートします。

  • 引用されていない/引用されている属性、
  • 一重/二重引用符、
  • 属性内のエスケープされた引用符、
  • 等号の前後のスペース、
  • 任意の数の属性、
  • タグ内の属性のみを確認し、
  • コメントをエスケープする
  • 属性値内の異なる引用符を管理します。

(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)

チェックアウト 。デモのように、「gisx」フラグを使用するとうまく機能します。

4
Ivan Chaer

「それは依存します」。正規表現は、ここで示したすべての理由で、真の精度でHTMLを解析せず、解析できないことは事実です。ただし、間違った結果(ネストされたタグを処理しないなど)が軽微で、環境内で正規表現が非常に便利な場合(Perlをハッキングするときなど)は、先に進んでください。

おそらく、あなたがあなたのサイトにリンクしているウェブページを解析していて、おそらくGoogleリンク検索でそれらを見つけていて、あなたのリンクを取り巻くコンテキストの一般的なアイデアを素早く手に入れる方法を望んでいるとしましょう。スパムをリンクするように警告する小さなレポートを実行しようとしています。

その場合、ドキュメントの一部を誤って解析することは大した問題にはなりません。間違い以外は誰も見ることができません。非常に幸運な場合は、個別にフォローアップできるものはほとんどありません。

私はそれがトレードオフだと言っていると思います。正確なパーサーを実装または使用することは、場合によっては簡単かもしれませんが、精度が重要でない場合は面倒な価値がないかもしれません。

前提に注意してください。たとえば、公共の場で表示されるものを解析しようとしている場合、正規表現のショートカットが裏目に出る可能性があるいくつかの方法を考えることができます。

3
catfood

正規表現を使用してHTMLの一部の情報を解析するのが正しい方法である場合は間違いありません。特定の状況に大きく依存します。

上記のコンセンサスは、一般的に悪い考えだということです。ただし、HTML構造がわかっている(変更される可能性が低い)場合、それは依然として有効なアプローチです。

3
Jason

HTML/XMLは、マークアップとコンテンツに分けられます。
正規表現は、字句タグの解析を行う場合にのみ役立ちます。
コンテンツを推測できると思います。
SAXパーサーに適しています。
タグとコンテンツをユーザーに配信できます
要素のネスト/クロージャを定義する関数
を追跡できます。

タグを解析するだけであれば、次のようにできます。
正規表現。ドキュメントからタグを取り除くために使用されます。

長年のテストで、私は秘密を見つけました
wayブラウザは、タグの形式が正しくも不正でも解析します。

通常の要素は次の形式で解析されます。

これらのタグのコアはこの正規表現を使用します

_ (?:
      " [\S\s]*? " 
   |  ' [\S\s]*? ' 
   |  [^>]? 
 )+
_

この_[^>]?_が代替の1つであることに気付くでしょう。
これは、不正な形式のタグからの不均衡な引用符と一致します。

また、正規表現では、最も単一のすべての悪のルートです。
それが使用される方法は、欲張りで、一致しなければならないを満たすために、バンプをトリガーします
数量化されたコンテナ。

受動的に使用する場合、問題はありません。
ただし、forceを散在させて一致させるもの
必要な属性/値のペアであり、適切な保護を提供しない
バックトラックから、それは制御不能の悪夢です。

これは、単なる古いタグの一般的な形式です。
タグ名を表す_[\w:]_に注意してください。
実際には、タグ名を表すlegal文字
は、Unicode文字のすばらしいリストです。

_ <     
 (?:
      [\w:]+ 
      \s+ 
      (?:
           " [\S\s]*? " 
        |  ' [\S\s]*? ' 
        |  [^>]? 
      )+
      \s* /?
 )
 >
_

さらに、特定のタグを検索できないこともわかります
解析なし[〜#〜] all [〜#〜]タグ。
あなたができるという意味ですが、それはの組み合わせを使用する必要があります
動詞は(* SKIP)(* FAIL)に似ていますが、すべてのタグを解析する必要があります。

その理由は、タグの構文が他のタグなどに隠れている可能性があるためです。

したがって、すべてのタグを受動的に解析するには、以下のような正規表現が必要です。
この特定のものは不可視コンテンツにも一致します。

新しいHTMLまたはxml、またはその他の新しいコンストラクトが開発されたら、次のように追加します。
代替の1つ。


Webページの注意-Webページ(またはxhtml/xml)を見たことがない
に問題がありました。見つかった場合はお知らせください。

パフォーマンスノート-速い。これは私が見た中で最速のタグパーサーです
(もっと速いかもしれません、誰が知っていますか)。
特定のバージョンがいくつかあります。スクレーパーとしても優れています
(ハンズオンタイプの場合


完全な生の正規表現

<(?:(?:(?:(script|style|object|embed|applet|noframes|noscript|noembed)(?:\s+(?>"[\S\s]*?"|'[\S\s]*?'|(?:(?!/>)[^>])?)+)?\s*>)[\S\s]*?</\1\s*(?=>))|(?:/?[\w:]+\s*/?)|(?:[\w:]+\s+(?:"[\S\s]*?"|'[\S\s]*?'|[^>]?)+\s*/?)|\?[\S\s]*?\?|(?:!(?:(?:DOCTYPE[\S\s]*?)|(?:\[CDATA\[[\S\s]*?\]\])|(?:--[\S\s]*?--)|(?:ATTLIST[\S\s]*?)|(?:ENTITY[\S\s]*?)|(?:ELEMENT[\S\s]*?))))>

書式設定された外観

_ <
 (?:
      (?:
           (?:
                # Invisible content; end tag req'd
                (                             # (1 start)
                     script
                  |  style
                  |  object
                  |  embed
                  |  applet
                  |  noframes
                  |  noscript
                  |  noembed 
                )                             # (1 end)
                (?:
                     \s+ 
                     (?>
                          " [\S\s]*? "
                       |  ' [\S\s]*? '
                       |  (?:
                               (?! /> )
                               [^>] 
                          )?
                     )+
                )?
                \s* >
           )

           [\S\s]*? </ \1 \s* 
           (?= > )
      )

   |  (?: /? [\w:]+ \s* /? )
   |  (?:
           [\w:]+ 
           \s+ 
           (?:
                " [\S\s]*? " 
             |  ' [\S\s]*? ' 
             |  [^>]? 
           )+
           \s* /?
      )
   |  \? [\S\s]*? \?
   |  (?:
           !
           (?:
                (?: DOCTYPE [\S\s]*? )
             |  (?: \[CDATA\[ [\S\s]*? \]\] )
             |  (?: -- [\S\s]*? -- )
             |  (?: ATTLIST [\S\s]*? )
             |  (?: ENTITY [\S\s]*? )
             |  (?: ELEMENT [\S\s]*? )
           )
      )
 )
 >
_
3
sln

実際、PHPでは正規表現を使用したHTML解析が完全に可能です。 strrposを使用して文字列全体を逆方向に解析して_<_を見つけ、ネストされたタグを取得するために毎回、貪欲な指定子を使用してそこから正規表現を繰り返す必要があります。大きなものでは派手でひどく遅くはありませんが、私は自分のウェブサイト用の自分の個人的なテンプレートエディターにそれを使用しました。私は実際にはHTMLを解析していませんでしたが、データベースエントリをクエリしてデータのテーブルを表示するために作成したいくつかのカスタムタグ(私の<#if()>タグは特別なエントリをこの方法で強調できます)。私はあちこちで(非常に非XMLのデータを含む)自己作成された2、3のタグでXMLパーサーを使用する準備ができていませんでした。

そのため、この質問はかなり死んでいますが、それでもGoogle検索に表示されます。私はそれを読み、「チャレンジは受け入れられた」と考え、すべてを置き換えることなく私の単純なコードを修正しました。同様の理由で検索している人に異なる意見を提供することにしました。また、最後の回答は4時間前に投稿されたので、これはまだホットなトピックです。

2
Deji

HTML自体は規則的ではありませんが、見ているページの一部はmight規則的であることに注意してください。

たとえば、_<form>_タグのネストはエラーです。 Webページが正常に動作している場合、正規表現を使用して_<form>_を取得することは完全に合理的です。

最近、Seleniumと正規表現のみを使用してWebスクレイピングを行いました。必要なデータが_<form>_に入れられ、単純なテーブル形式に入れられたので、私はそれで逃げました(したがって、_<table>_、_<tr>_、および_<td>_ネストされない-実際には非常に珍しいです)。アクセスするために必要な構造の一部がコメントで区切られていたため、ある程度は正規表現もほとんど必要でした。 (Beautiful Soupはコメントを提供できますが、Beautiful Soupを使用して_<!-- BEGIN -->_および_<!-- END -->_ブロックを取得することは困難でした。)

ただし、ネストされたテーブルについて心配する必要がある場合、私のアプローチはうまくいきませんでした!私はBeautiful Soupに頼らなければならなかっただろう。ただし、その場合でも、正規表現を使用して必要なチャンクを取得し、そこからドリルダウンすることができます。

2
alpheus

私もこれを正規表現で試しました。ほとんどの場合、次のHTMLタグとペアになっているコンテンツのチャンクを見つけるのに役立ち、matching closeタグを探しませんが、それを拾いますタグを閉じます。自分の言語でスタックをロールして、それらを確認してください。

「sx」オプションとともに使用します。幸運を感じている場合も「g」:

(?P<content>.*?)                # Content up to next tag
(?P<markup>                     # Entire tag
  <!\[CDATA\[(?P<cdata>.+?)]]>| # <![CDATA[ ... ]]>
  <!--(?P<comment>.+?)-->|      # <!-- Comment -->
  </\s*(?P<close_tag>\w+)\s*>|  # </tag>
  <(?P<tag>\w+)                 # <tag ...
    (?P<attributes>
      (?P<attribute>\s+
# <snip>: Use this part to get the attributes out of 'attributes' group.
        (?P<attribute_name>\w+)
        (?:\s*=\s*
          (?P<attribute_value>
            [\w:/.\-]+|         # Unquoted
            (?=(?P<_v>          # Quoted
              (?P<_q>['\"]).*?(?<!\\)(?P=_q)))
            (?P=_v)
          ))?
# </snip>
      )*
    )\s*
  (?P<is_self_closing>/?)   # Self-closing indicator
  >)                        # End of tag

これはPythonのために設計されています(他の言語でも動作する可能性があり、試していません。ポジティブな先読み、ネガティブな後読み、名前付き後方参照を使用しています。)。

  • タグを開く-<div ...>
  • タグを閉じる-</div>
  • コメント-<!-- ... -->
  • CDATA-<![CDATA[ ... ]]>
  • 自己閉鎖タグ-<div .../>
  • オプションの属性値-<input checked>
  • 引用されていない/引用されている属性値-<div style='...'>
  • シングル/ダブルクォート-<div style="...">
  • エスケープされた引用-<a title='John\'s Story'>
    (これは実際には有効なHTMLではありませんが、私はナイスガイです)
  • 等号の周りのスペース-<a href = '...'>
  • 興味深いビットの名前付きキャプチャ

<または>を忘れたときなど、不正な形式のタグでトリガーしないことについても非常に優れています。

正規表現フレーバーが名前付きキャプチャの繰り返しをサポートしている場合、あなたは黄金色ですが、Python reはそうではありません(正規表現は知っていますが、Vanilla Pythonを使用する必要があります)。あなたが得るもの:

  • content-次のタグまでのすべてのコンテンツ。これは省略できます。
  • markup-すべてを含むタグ全体。
  • comment-コメントの場合、コメントの内容。
  • cdata-<![CDATA[...]]>の場合、CDATAの内容。
  • close_tag-終了タグ(</div>)の場合、タグ名。
  • tag-開始タグ(<div>)の場合、タグ名。
  • attributes-タグ内のすべての属性。繰り返しグループを取得しない場合、これを使用してすべての属性を取得します。
  • attribute-各属性で繰り返されます。
  • attribute_name-各属性名が繰り返されます。
  • attribute_value-各属性値が繰り返されます。引用された場合、これには引用が含まれます。
  • is_self_closing-自己終了タグの場合は/、それ以外の場合は何もありません。
  • _qおよび_v-これらを無視します。それらは内部で後方参照に使用されます。

正規表現エンジンが名前付きキャプチャの繰り返しをサポートしていない場合は、各属性を取得するために使用できるセクションがあります。 attributesグループでその正規表現を実行して、それぞれのattributeattribute_nameおよびattribute_valueを取得します。

デモはこちら: https://regex101.com/r/mH8jSu/11

2
Hounshell

正規表現は、HTMLのような言語には十分強力ではありません。もちろん、正規表現を使用できる例がいくつかあります。ただし、一般的には構文解析には適していません。

1
Gumbo

あなた、知っている...あなたの多くのメンタリティがありますCA N'Tそれをし、フェンスの両側の誰もが正しいと思います違う。あなたは[〜#〜] can [〜#〜]できますが、正規表現を1つ実行するよりも少し処理がかかります。例として this (私はこれを1時間以内に書いた)を取る。 HTMLは完全に有効であると想定していますが、前述の正規表現を適用するために使用している言語に応じて、HTMLを修正して、成功することを確認できます。たとえば、存在しないはずの終了タグを削除する場合:_</img>_など。次に、閉じている単一のHTMLスラッシュを、欠落している要素などに追加します。

たとえば、JavaScriptの[x].getElementsByTagName()のようなHTML要素の取得を実行できるライブラリを作成するコンテキストでこれを使用します。正規表現のDEFINEセクションで記述した機能を接合し、それを使用して、1つずつ要素のツリー内をステップ実行します。

だから、これはHTMLを検証するための最後の100%の答えでしょうか?いいえ。しかし、それは始まりであり、もう少し作業があれば、それを行うことができます。ただし、1つの正規表現の実行内で実行しようとすると、実用的でも効率的でもありません。

0
Erutan409