Utiliser des expressions régulières pour analyser HTML: pourquoi pas?

il semble que chaque question sur stackoverflow où l'asker utilise regex pour saisir des informations de HTML aura inévitablement une" réponse " qui dit de ne pas utiliser regex pour analyser HTML.

pourquoi pas? Je suis conscient qu'Il ya citation-unquote" real "parsers HTML là-bas comme belle soupe , et je suis sûr qu'ils sont puissants et utiles, mais si vous faites juste quelque chose de simple, rapide, ou sale, alors pourquoi se soucier d'utiliser quelque chose de si compliqué quand quelques déclarations regex fonctionneront très bien?

de plus, y a-t-il quelque chose de fondamental que je ne comprends pas à propos de regex qui fait d'eux un mauvais choix pour l'analyse en général?

185
demandé sur Andy Lester 2009-02-26 17:24:18

18 réponses

le parsing HTML entier n'est pas possible avec les expressions régulières, car il dépend de la correspondance entre l'ouverture et la balise de fermeture, ce qui n'est pas possible avec regexps.

les expressions régulières ne peuvent correspondre qu'à les langues régulières mais HTML est une langue sans contexte et pas une langue régulière (comme @StefanPochmann l'a fait remarquer, les langues régulières sont aussi sans contexte, donc sans contexte nécessairement pas régulier). La seule chose que vous pouvez faire avec regexps sur HTML est heuristique, mais cela ne fonctionnera pas sur toutes les conditions. Il devrait être possible de présenter un fichier HTML qui sera apparié à tort par n'importe quelle expression régulière.

195
répondu Johannes Weiss 2017-12-09 16:16:27

pour quickndirty regexp fera l'affaire. Mais la chose fondamentale à savoir est qu'il est impossible de construire un regexp qui sera correctement parse HTML.

la raison est que regexps ne peut pas gérer les expressions imbriquées. Voir peut-on utiliser des expressions régulières pour correspondre à des motifs imbriqués?

32
répondu kmkaplan 2017-05-23 12:26:15

(de http://htmlparsing.com/regexes )

dites que vous avez un fichier HTML où vous essayez d'extraire des URLs à partir de les balises .

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

donc vous écrivez un regex comme celui-ci en Perl:

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

dans ce cas, $url contiendra effectivement http://example.com/whatever.jpg . Mais ce qui arrive quand vous commencez à obtenir HTML comme ceci:

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

ou

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

ou

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

ou

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

ou vous commencez à recevoir des faux positifs à partir de

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

il semble si simple, et il pourrait être simple pour un simple, fichier immuable, mais pour tout ce que vous allez faire sur les données HTML arbitraires, regexes sont juste une recette pour le futur chagrin d'amour.

18
répondu Andy Lester 2013-09-10 17:07:31

en ce qui concerne l'analyse, les expressions régulières peuvent être utiles à l'étape de l '"analyse lexicale" (lexer), où l'entrée est décomposée en jetons. C'est moins utile à l'étape "construire un arbre d'analyse".

pour un analyseur HTML, Je m'attendrais à ce QU'il n'accepte que du HTML bien formé et qui nécessite des capacités en dehors de ce qu'une expression régulière peut faire (ils ne peuvent pas "compter" et s'assurer qu'un nombre donné d'éléments d'ouverture est équilibré par le même nombre de fermeture élément.)

16
répondu Vatine 2009-02-26 14:34:11

deux raisons rapides:

  • écrire un regex qui peut résister à une entrée malveillante est difficile; bien plus difficile que d'utiliser un outil préconstruit
  • écrire un regex qui peut fonctionner avec le markup ridicule que vous serez inévitablement coincé avec est difficile; beaucoup plus difficile que d'utiliser un outil prébuilt

concernant l'aptitude des regexes à l'analyse en général: ils ne sont pas adaptés. Avez-vous déjà vu les sortes de regexes vous devez analyser la plupart des langues?

15
répondu Hank Gay 2009-02-26 14:29:02

parce qu'il y a plusieurs façons de "faire foirer" HTML que les navigateurs traiteront d'une manière plutôt libérale, mais il faudrait tout de même un certain effort pour reproduire le comportement libéral du navigateur pour couvrir tous les cas avec des expressions régulières, de sorte que votre regex échouera inévitablement sur certains cas spéciaux, et cela pourrait introduire de graves lacunes de sécurité dans votre système.

8
répondu Tamas Czinege 2009-02-26 14:29:35

le problème est que la plupart des utilisateurs qui posent une question qui a à voir avec HTML et regex le font parce qu'ils ne peuvent pas trouver leur propre regex qui fonctionne. Il faut alors se demander si tout serait plus facile en utilisant un DOM ou un analyseur SAX ou quelque chose de similaire. Ils sont optimisés et construits dans le but de travailler avec des structures de documents de type XML.

bien sûr, il y a des problèmes qui peuvent être résolus facilement avec des expressions régulières. Mais l'accent est mis sur facilement .

si vous voulez juste trouver toutes les URLs qui ressemblent à http://.../ vous êtes d'accord avec regexps. Mais si vous voulez trouver toutes les URL qui sont dans un élément a qui a la classe 'mylink' vous devriez probablement utiliser un analyseur approprié.

7
répondu okoman 2013-09-10 21:09:31

les expressions régulières n'ont pas été conçues pour gérer une structure d'étiquette imbriquée, et c'est au mieux compliqué (au pire, impossible) de gérer tous les cas de bord possibles que vous obtenez avec du HTML réel.

6
répondu Peter Boughton 2009-02-26 14:35:50

je crois que la réponse réside dans la théorie du calcul. Pour qu'une langue soit analysée à l'aide de regex, elle doit être par définition "régulière" ( link ). HTML n'est pas un langage régulier car il ne répond pas à un certain nombre de critères pour un langage régulier (beaucoup à faire avec les nombreux niveaux d'imbrication inhérents au code html). Si vous êtes intéressé par la théorie du calcul, je recommande ce Livre.

5
répondu taggers 2009-02-26 14:45:39

"cela dépend" cependant. Il est vrai que regexes ne peut et ne peut pas analyser HTML avec une vraie précision, pour toutes les raisons indiquées ici. Si, cependant, les conséquences de se tromper (comme ne pas manipuler les étiquettes imbriquées) sont mineures, et si les regexes sont super-commodes dans votre environnement (comme quand vous hacking Perl), allez-y.

supposez que vous êtes, Oh, peut-être parsing pages web qui pointent vers votre site--peut-être vous les avez trouvées avec une recherche de lien Google--et vous voulez un moyen rapide d'obtenir une idée générale du contexte entourent votre lien. Vous tentez d'exécuter un petit rapport qui pourrait vous alerter lien spam, quelque chose comme ça.

dans ce cas, se tromper sur certains documents ne sera pas une grosse affaire. Personne, mais vous verrez des erreurs, et si vous êtes très chanceux, il y aura peu assez que vous pouvez suivre individuellement.

je suppose que je dis que c'est un compromis. Parfois mettre en œuvre ou utiliser un correctif analyseur--aussi facile que cela puisse être, peut-être pas la peine si la précision n'est pas critique.

faites attention à vos suppositions. Je peux penser à quelques façons le raccourci de regexp peut se retourner contre vous si vous essayez d'analyser quelque chose qui sera montré en public, par exemple.

3
répondu catfood 2009-02-26 15:26:20

il y a certainement des cas où l'utilisation d'une expression régulière pour analyser certaines informations du HTML est la bonne façon d'aller - cela dépend beaucoup de la situation spécifique.

Le consensus ci-dessus est que, en général c'est une mauvaise idée. Cependant, si la structure HTML est connue (et peu susceptible de changer), alors c'est toujours une approche valide.

3
répondu Jason 2011-04-29 06:45:17

cette expression récupère les attributs des éléments HTML. Il supporte:

  • attributs non cotés / Cités,
  • single / double guillemets,
  • echappé citations à l'intérieur des attributs,
  • espaces autour de signes égaux,
  • nombre d'attributs,
  • vérifier uniquement les attributs à l'intérieur des balises,
  • escape comments, et
  • gère différentes citations à l'intérieur d'une valeur d'attribut.

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

Check it out . Il fonctionne mieux avec les drapeaux" gisx", comme dans la démo.

3
répondu Ivan Chaer 2017-03-11 02:46:13

gardez à l'esprit que bien que HTML lui-même N'est pas régulier, les parties d'une page que vous regardez pourrait être régulier.

par exemple, c'est une erreur pour les balises <form> d'être imbriquées; si la page Web fonctionne correctement, alors utiliser une expression régulière pour saisir un <form> serait tout à fait raisonnable.

j'ai récemment fait du grattage de toile en utilisant seulement du sélénium et des expressions régulières. Je suis parti avec elle parce que les données que je voulais ont été mis dans un <form> , et mis dans un format de table simple (donc je pouvais même compter sur <table> , <tr> et <td> pour être non-emboîté-ce qui est en fait très inhabituel). Dans une certaine mesure, des expressions régulières étaient même presque nécessaires, parce qu'une partie de la structure à laquelle j'avais besoin d'accéder était délimitée par des commentaires. (Belle soupe peut vous donner des commentaires, mais il aurait été difficile de saisir <!-- BEGIN --> et <!-- END --> blocs en utilisant belle soupe.)

si je devais m'inquiéter des tables emboîtées, cependant, mon approche n'aurait tout simplement pas fonctionné! J'aurais dû retomber sur Belle Soupe. Même alors, cependant, parfois, vous pouvez utiliser une expression régulière pour saisir le morceau dont vous avez besoin, et puis percer à partir de là.

2
répondu alpheus 2013-09-10 21:01:51

en fait, le parsing HTML avec regex est parfaitement possible en PHP. Vous avez juste à analyser la chaîne entière à l'envers en utilisant strrpos pour trouver < et de répéter le regex à partir de là en utilisant des spécificateurs ungreedy à chaque fois pour obtenir plus de tags imbriqués. Pas fantaisiste et terriblement lent sur les grandes choses, mais je l'ai utilisé pour mon propre éditeur de modèle personnel pour mon site web. Je ne parsais pas réellement HTML, mais quelques étiquettes personnalisées j'ai fait pour interroger des entrées de base de données pour afficher des tables de données (Mon L'étiquette <#if()> pourrait mettre en évidence les entrées spéciales de cette façon). Je n'étais pas prêt à faire appel à un analyseur XML sur seulement quelques balises auto-créées (avec des données non-XML) ici et là.

Donc, même si cette question est considérablement morts, il apparaît toujours dans une recherche Google. Je l'ai lu et j'ai pensé "challenge accepted" et j'ai fini de corriger mon code simple sans avoir à tout remplacer. Décidé d'offrir une opinion différente à toute personne à la recherche d'un similaire raison. Aussi la dernière réponse a été posté il y a 4 heures donc c'est toujours un sujet chaud.

2
répondu Deji 2013-09-10 21:05:45

j'ai essayé ma main à un regex pour cela aussi. Il est surtout utile pour trouver des morceaux de contenu appariés avec la prochaine étiquette HTML, et il ne cherche pas correspondance fermer les étiquettes, mais il va ramasser les étiquettes de fermeture. Roulez une pile dans votre propre langue pour les vérifier.

utiliser avec les options 'sx'. 'g' si vous vous sentez chanceux:

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

celui-ci est conçu pour Python (il pourrait fonctionner pour les autres langues, ne l'ont pas essayé, il utilise des lookheads positifs, lookbehinds négatifs, et nommé backreferences). Prend en charge:

  • Balise D'Ouverture - <div ...>
  • Fermer Les Balises </div>
  • Commentaire - <!-- ... -->
  • CDATA - <![CDATA[ ... ]]>
  • Étiquette À Fermeture Automatique - <div .../>
  • Valeurs D'Attribut Facultatives - <input checked>
  • Valeurs D'Attribut Non Cotées / Cotées - <div style='...'>
  • Single / Double Quotes - <div style="...">
  • Échappé Citations - <a title='John\'s Story'>

    (ce n'est pas vraiment valide en HTML, mais je suis un mec sympa)
  • Espaces Autour Égale Signes - <a href = '...'>
  • Captures Nommées Pour Bits Intéressants

c'est aussi assez bon de ne pas se déclencher sur des tags malformés, comme quand on oublie un < ou > .

si votre saveur regex supporte des captures répétées du nom, alors vous êtes doré, mais pas le Python re (je sais que regex le fait, mais j'ai besoin d'utiliser le python vanille). Voici ce que vous obtenez:

  • content - Tout le contenu jusqu'à la prochaine balise. Vous pourriez le laisser.
  • markup - l'étiquette entière avec tout cela.
  • comment - si c'est un commentaire, le contenu du commentaire.
  • cdata - si c'est un <![CDATA[...]]> , le contenu CDATA.
  • close_tag - si c'est une étiquette fermée ( </div> ), le nom de l'étiquette.
  • tag - si c'est une étiquette ouverte ( <div> ), le nom de l'étiquette.
  • attributes - tous les attributs se trouvent à l'intérieur de l'étiquette. Utilisez ceci pour obtenir tout attributs si vous n'obtenez pas de groupes répétés.
  • attribute - répété, chaque attribut.
  • attribute_name - répété, chaque nom d'attribut.
  • attribute_value - répété, chaque valeur d'attribut. Cela inclut les citations si elles ont été citées.
  • is_self_closing -c'est / si c'est une étiquette à fermeture automatique, sinon rien.
  • _q et _v - ignorer ceux-ci, ils sont utilisés en interne pour des références arrières.

si votre moteur regex ne supporte pas les captures nommées répétées, il y a une section appelée que vous pouvez utiliser pour obtenir chaque attribut. Il suffit d'exécuter ce regex sur le attributes groupe pour obtenir chaque attribute , attribute_name et attribute_value hors de lui.

démo ici: https://regex101.com/r/mH8jSu/11

2
répondu Hounshell 2016-12-28 21:48:55

HTML / XML est divisé en balisage et contenu.

Regex n'est utile qu'en faisant une analyse lexicale des étiquettes.

Je pense que l'on peut en déduire le contenu.

Ce serait un bon choix pour un analyseur de Saxo.

Les étiquettes et le contenu pourraient être livrés à un utilisateur

fonction définie où nidation / fermeture des éléments

peut-être gardé la trace de.

As pour ce qui est de l'analyse des étiquettes, on peut le faire avec

regex et utilisé pour rayer les étiquettes d'un document.

après des années de tests, j'ai trouvé le secret du

la façon dont les navigateurs analysent les étiquettes, à la fois bien et mal formées.

les éléments normaux sont analysés avec cette forme:

le noyau de ces étiquettes utilisent ce regex

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

vous remarquerez ceci [^>]? comme l'une des alternations.

Cela correspondra à des citations non équilibrées d'étiquettes mal formées.

il est aussi, le seul le plus racine de tout mal à des expressions régulières.

La façon dont il est utilisé va déclencher un bump-along pour satisfaire il est gourmand, must-match

quantifiée conteneur.

s'il est utilisé passivement, il n'y a jamais de problème.

Mais, si vous force quelque chose à faire correspondre en l'intercalant avec

une paire attribut/valeur, et ne fournissent pas une protection adéquate

de retour en arrière, c'est un hors de contrôle cauchemar.

C'est la forme générale pour tout simplement vieilles étiquettes.

Vous remarquez le [\w:] représentant le nom de l'étiquette ?

En réalité, le juridique caractères représentant l'étiquette nom

sont une liste incroyable de caractères Unicode.

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

nous voyons aussi que vous ne pouvez pas rechercher une étiquette spécifique

sans parsing tous tags.

Je veux dire que vous pourriez, mais il faudrait utiliser une combinaison de

des verbes comme (*SKIP) (*FAIL) mais tout de même tous les balises doivent être analysées.

la raison en est que la syntaxe des étiquettes peut être cachée dans d'autres étiquettes, etc..

ainsi, pour analyser passivement toutes les étiquettes, un regex est nécessaire comme celui ci-dessous.

Celui-ci correspond aussi à invisible content .

comme nouveau HTML ou xml ou tout autre développer de nouvelles constructions, il suffit de l'ajouter comme

l'un des alternances.


note de la page Web-Je n'ai jamais vu une page web (ou XHTML/xml) que ce

avait des problèmes avec. Si vous en trouvez un, faites le moi savoir.

Performance note - C'est rapide. C'est l'analyseur d'étiquettes le plus rapide que j'ai vu

(il peut être plus rapide, qui sait).

J'ai plusieurs versions spécifiques. Il est également excellent comme racleur

(si vous êtes le type de main-sur).


raw Complet regex

<(?:(?:(?:(script|style|object|embed|applet|noframes|noscript|noembed)(?:\s+(?>"[\S\s]*?"|'[\S\s]*?'|(?:(?!/>)[^>])?)+)?\s*>)[\S\s]*?</\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]*?))))>

Formaté look

 <
 (?:
      (?:
           (?:
                # 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]*? </  \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]*? )
           )
      )
 )
 >
2
répondu sln 2017-06-15 22:26:23

les expressions régulières ne sont pas assez puissantes pour un langage tel que HTML. Bien sûr, il y a quelques exemples où vous pouvez utiliser des expressions régulières. Mais en général, il n'est pas approprié pour l'analyse.

1
répondu Gumbo 2009-02-26 14:33:51

vous savez...Il ya beaucoup de mentalité de vous ne peut pas le faire et je pense que tout le monde des deux côtés de la barrière sont bons et mauvais. Vous pouvez le faire, mais il faut un peu plus de traitement que juste exécuter un regex contre lui. Prenez ce (j'ai écrit cela en moins d'une heure) comme exemple. Il suppose que le HTML est tout à fait VALIDE, mais en fonction de la langue que vous utilisez pour appliquer le regex susmentionné, vous pouvez faire quelques fixations du HTML pour vous assurer qu'il réussira. Par exemple, enlever les étiquettes de fermeture qui ne sont pas censées être là: </img> par exemple. Ensuite, ajoutez la barre oblique vers L'avant unique HTML aux éléments qui leur manquent, etc.

Je l'utiliserais dans le contexte de l'écriture d'une bibliothèque qui me permettrait d'effectuer une récupération D'élément HTML similaire à celle de JavaScript [x].getElementsByTagName() , par exemple. J'épisserais juste le fonctionnalité que j'ai écrite dans la section Définir du regex et l'utiliser pour marcher à l'intérieur d'un arbre d'éléments, un à la fois.

alors, est-ce que ce sera la réponse finale de 100% pour valider HTML? Aucun. Mais c'est un début et avec un peu plus de travail, il peut être fait. Cependant, essayer de le faire à l'intérieur d'une exécution regex n'est ni pratique ni efficace.

0
répondu Erutan409 2015-11-22 15:03:21