web-dev-qa-db-fra.com

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

Il semble que chaque question sur stackoverflow où le demandeur utilise regex pour récupérer des informations HTML aura inévitablement une "réponse" qui dit de ne pas utiliser regex pour analyser HTML.

Pourquoi pas? Je suis conscient qu'il existe de "vrais" analyseurs HTML, tels que Beautiful Soup , et je suis sûr qu'ils sont puissants et utiles, mais si vous faites quelque chose simple, rapide ou sale, alors pourquoi se donner la peine d’utiliser quelque chose de si compliqué lorsque quelques déclarations regex fonctionnent parfaitement?

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

200
ntownsend

L'analyse HTML complète n'est pas possible avec les expressions régulières, car elle dépend de la correspondance entre les balises d'ouverture et de fermeture, ce qui n'est pas possible avec les expressions rationnelles.

Les expressions régulières ne peuvent correspondre que langages normaux mais HTML est un langage sans contexte et pas a langage standard (comme @StefanPochmann l'a souligné, les langages normaux sont également dépourvus de contexte, ce qui signifie que contexte ne signifie pas nécessairement non régulier). La seule chose que vous pouvez faire avec les expressions rationnelles sur HTML est l'heuristique, mais cela ne fonctionnera pas à toutes les conditions. Il devrait être possible de présenter un fichier HTML qui ne sera pas mis en correspondance par une expression régulière.

202
Johannes Weiss

Pour quick´n´dirty, l'expression rationnelle fera l'affaire. Mais la chose fondamentale à savoir est qu’il est impossible de construire une expression rationnelle qui va correctement analyser HTML.

La raison en est que les expressions rationnelles ne peuvent pas gérer les expressions imbriquées de manière arbitraire. Voir Peut-on utiliser des expressions régulières pour faire correspondre des modèles imbriqués?

34
kmkaplan

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

Supposons que vous essayez d'extraire les URL de balises <img> dans un fichier HTML.

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

Donc, vous écrivez une expression régulière comme celle-ci en Perl:

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

Dans ce cas, $url contiendra en effet http://example.com/whatever.jpg. Mais que se passe-t-il lorsque vous commencez à obtenir du 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 de

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

Cela a l'air si simple, et cela peut être simple pour un fichier unique et immuable, mais pour tout ce que vous allez faire sur des données HTML arbitraires, les expressions rationnelles ne sont que la recette d'un futur chagrin d'amour.

19
Andy Lester

Deux raisons rapides:

  • écrire une expression régulière qui peut résister à une entrée malveillante est difficile; beaucoup plus difficile que d'utiliser un outil prédéfini
  • écrire une expression rationnelle qui fonctionne avec le balisage ridicule avec lequel vous serez inévitablement coincé est difficile; beaucoup plus difficile que d'utiliser un outil préconfiguré

En ce qui concerne la pertinence des expressions rationnelles pour l'analyse syntaxique en général: elles ne conviennent pas. Avez-vous déjà vu le genre de regex dont vous auriez besoin pour analyser la plupart des langues?

16
Hank Gay

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

Pour un analyseur HTML, je m'attendrais à ce qu'il accepte uniquement du code HTML bien formé, ce qui nécessite des fonctionnalités extérieures à celles d'une expression régulière (elles ne peuvent pas "compter" et s'assurer qu'un nombre donné d'éléments d'ouverture sont équilibrés par le même nombre des éléments de fermeture).

16
Vatine

Comme il existe de nombreuses façons de "foirer" le code HTML que les navigateurs traiteront de manière assez libérale, il vous faudra toutefois un certain effort pour reproduire le comportement libéral du navigateur afin de couvrir tous les cas avec des expressions régulières, de sorte que votre expression rationnelle échouera inévitablement cas, et cela pourrait éventuellement introduire de graves failles de sécurité dans votre système.

8
Tamas Czinege

Le problème est que la plupart des utilisateurs qui posent une question concernant HTML et regex le font parce qu’ils ne peuvent pas trouver une regex propre qui fonctionne. Ensuite, il faut penser si tout serait plus facile avec un analyseur DOM ou SAX ou quelque chose de similaire. Ils sont optimisés et construits dans le but de travailler avec des structures de document de type XML.

Bien sûr, il existe 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 allez bien avec les expressions rationnelles. Mais si vous voulez trouver toutes les URL qui se trouvent dans un a-Element qui a la classe 'mylink', vous feriez probablement mieux d'utiliser un analyseur syntaxique approprié.

7
okoman

Les expressions régulières n'ont pas été conçues pour gérer une structure de balise imbriquée, et il est au mieux compliqué (au pire, impossible) de gérer tous les cas Edge possibles obtenus avec du code HTML réel.

6
Peter Boughton

Je crois que la réponse réside dans la théorie du calcul. Pour qu'une langue soit analysée avec regex, elle doit être par définition "régulière" ( link ). Le langage HTML n’est pas un langage standard, car il ne répond pas à un certain nombre de critères d’un langage standard (en grande partie à cause des nombreux niveaux d’imbrication inhérents au code HTML). Si vous êtes intéressé par la théorie du calcul, je recommanderais this book.

6
taggers

Cette expression récupère les attributs des éléments HTML. Ça supporte:

  • attributs non cités/cités,
  • guillemets simples/doubles,
  • citations échappées à l'intérieur des attributs,
  • des espaces autour des signes égaux,
  • un nombre quelconque d'attributs,
  • vérifier uniquement les attributs à l'intérieur des balises,
  • échapper des commentaires, et
  • gérer différentes citations dans une valeur d'attribut.

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

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

4
Ivan Chaer

"Cela dépend" cependant. Il est vrai que les expressions rationnelles n'analysent pas et ne peuvent pas analyser le code HTML avec une exactitude exacte, pour toutes les raisons données ici. Si, toutefois, les conséquences d'une erreur (par exemple, ne pas gérer les balises imbriquées) sont mineures, et si les expressions rationnelles sont très pratiques dans votre environnement (comme lorsque vous piratez Perl), continuez.

Supposons que vous, oh, analysez peut-être les pages Web qui pointent vers votre site - peut-être les avez-vous trouvées avec une recherche de lien Google - et que vous souhaitiez un moyen rapide d'avoir une idée générale du contexte entourant votre lien. Vous essayez de générer un petit rapport qui pourrait vous avertir de lier un spam, quelque chose comme ça.

Dans ce cas, la mauvaise présentation de certains documents ne sera pas un problème. Personne d'autre que vous ne verrez les erreurs, et si vous êtes très chanceux, vous en aurez assez pour pouvoir effectuer un suivi individuel.

Je suppose que je dis que c'est un compromis. Parfois, implémenter ou utiliser un analyseur correct - aussi simple que cela puisse être - ne vaut pas la peine si l’exactitude n’est pas critique.

Soyez juste prudent avec vos hypothèses. Je peux imaginer différentes manières dont le raccourci d’expression régulière peut se retourner contre vous si vous essayez d’analyser quelque chose qui sera diffusé en public, par exemple.

3
catfood

Il existe certainement des cas où utiliser une expression régulière pour analyser certaines informations à partir de HTML est la bonne façon de procéder - 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), l'approche reste valide.

3
Jason

HTML/XML est divisé en balisage et contenu.
Regex n’est utile que lors d’une analyse syntaxique des balises lexicales.
Je suppose que vous pouvez en déduire le contenu.
Ce serait un bon choix pour un analyseur SAX.
Les balises et le contenu pourraient être remis à un utilisateur
fonction définie où imbriquer/fermer des éléments
peut être suivi.

En ce qui concerne l'analyse des balises, cela peut être fait avec
regex et utilisé pour enlever les balises d’un document.

Au fil des années de tests, j'ai trouvé le secret de la
Les navigateurs analysent les balises, qu'elles soient bien ou mal formées.

Les éléments normaux sont analysés avec ce formulaire:

Le noyau de ces tags utilise cette regex

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

Vous remarquerez que [^>]? Fait partie des alternatives.
Ceci fera correspondre les citations non équilibrées de balises mal formées.

C'est aussi, le plus simple racine de tout mal aux expressions régulières.
La façon dont il est utilisé déclenchera une bosse le long de satisfaire ce qui est gourmand, doit correspondre
contenant quantifié.

Si utilisé passivement, il n'y a jamais de problème.
Mais, si vous force quelque chose à faire correspondre en l’intercalant avec
un couple attribut recherché/valeur, et n'assure pas une protection adéquate
de revenir en arrière, c'est un cauchemar incontrôlable.

C'est la forme générale pour les anciennes balises tout simplement.
Notez que [\w:] Représente le nom de la balise?
En réalité, les légaux caractères représentant le nom de la balise
sont une liste incroyable de caractères Unicode.

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

En passant, nous constatons également que vous ne pouvez pas rechercher une balise spécifique.
sans analyser TOUS balises.
Je veux dire, vous pourriez, mais il faudrait utiliser une combinaison de
verbes comme (* SKIP) (* FAIL) mais toutes les balises doivent être analysées.

La raison en est que la syntaxe des balises peut être cachée à l'intérieur d'autres balises, etc.

Ainsi, pour analyser passivement toutes les balises, une expression régulière est nécessaire, comme celle ci-dessous.
Celui-ci correspond notamment à contenu invisible.

Au fur et à mesure que HTML ou XML ou tout autre développement de nouvelles constructions, ajoutez-le simplement
une des alternances.


Note de page Web - Je n’ai jamais vu de page Web (ou xhtml/xml) que cette
a eu des problèmes avec. Si vous en trouvez un, faites le moi savoir.

Note de performance - C'est rapide. C'est l'analyseur de tag le plus rapide que j'ai vu
(il peut y avoir plus rapide, qui sait).
J'ai plusieurs versions spécifiques. C'est aussi excellent comme grattoir
(si vous êtes du type pratique).


Regex brut complet

<(?:(?:(?:(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]*?))))>

Look formaté

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

En fait, l’analyse HTML avec regex est parfaitement possible en PHP. Il vous suffit d'analyser la chaîne entière à l'envers en utilisant strrpos pour trouver <, Puis répétez la regex à l'aide de spécificateurs ungreedy à chaque fois pour récupérer les balises imbriquées. Pas très sophistiqué et très lent pour les gros projets, mais je l’ai utilisé pour mon propre éditeur de modèles personnel pour mon site Web. Je n'étais pas en train d'analyser HTML, mais quelques balises personnalisées que j'ai créées pour interroger les entrées de base de données afin d'afficher des tableaux de données (ma balise <#if()> pouvait mettre en évidence les entrées spéciales de cette façon). Je n'étais pas prêt à utiliser un analyseur XML uniquement avec quelques balises auto-créées (contenant des données très non XML) ici et là.

Ainsi, même si cette question est pratiquement morte, elle apparaît toujours dans une recherche Google. Je l'ai lu et j'ai pensé "défi accepté" et fini de corriger mon code simple sans avoir à tout remplacer. Décidé de donner un avis différent à ceux qui recherchent une raison similaire. De plus, la dernière réponse a été postée il y a 4 heures, donc c'est toujours un sujet d'actualité.

2
Deji

N'oubliez pas que, même si le langage HTML n'est pas régulier, les parties d'une page que vous consultez pourraient être régulières.

Par exemple, il s’agit d’une erreur pour <form> balises à imbriquer; si la page Web fonctionne correctement, utilisez une expression régulière pour saisir un <form> serait complètement raisonnable.

J'ai récemment fait des recherches sur le Web en utilisant uniquement du sélénium et des expressions régulières. Je m'en suis tiré parce que les données que je voulais ont été mises dans un <form>, et mis dans un format de tableau simple (afin que je puisse même compter sur <table>, <tr> et <td> être non-imbriqué - ce qui est très inhabituel). Dans une certaine mesure, les expressions régulières étaient même presque nécessaires, car une partie de la structure à laquelle je devais accéder était délimitée par des commentaires. (Beautiful Soup peut vous donner des commentaires, mais il aurait été difficile à saisir <!-- BEGIN --> et <!-- END --> blocs avec Belle Soupe.)

Si je devais me soucier des tables imbriquées, mon approche n'aurait tout simplement pas fonctionné! J'aurais dû me rabattre sur Beautiful Soup. Même dans ce cas, toutefois, vous pouvez parfois utiliser une expression régulière pour saisir le bloc dont vous avez besoin, puis effectuer une exploration à partir de là.

2
alpheus

Je me suis essayé à une regex pour cela aussi. Il est surtout utile pour rechercher des morceaux de contenu associés à la balise HTML suivante, mais ne recherche pas correspondance balises fermées , mais il va ramasser les balises proches. Rouler une pile dans votre propre langue pour vérifier ceux-ci.

Utiliser avec les options 'sx'. 'g' aussi 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 (cela pourrait fonctionner pour d'autres langages, je ne l'ai pas essayé, il utilise des indicateurs positifs, des indicateurs négatifs et des références inverses nommées).

  • Ouvrir la balise - <div ...>
  • Fermer la balise - </div>
  • Commentaire - <!-- ... -->
  • CDATA - <![CDATA[ ... ]]>
  • Étiquette à fermeture automatique - <div .../>
  • Valeurs d'attribut facultatives - <input checked>
  • Valeurs d'attribut non cotées/entre guillemets - <div style='...'>
  • Citations simples/doubles - <div style="...">
  • Citations échappées - <a title='John\'s Story'>
    ((ce n'est pas vraiment du HTML valide, mais je suis un gars sympa)]
  • Espaces autour des signes équivalents - <a href = '...'>
  • Captures nommées pour des bits intéressants

Il est également intéressant de ne pas déclencher sur des balises malformées, comme lorsque vous oubliez un < ou >.

Si votre style regex prend en charge les captures répétées nommées, alors vous êtes en or, mais Python re ne le fait pas (je sais que regex le fait, mais je dois utiliser Vanilla Python). Voici ce que vous obtenez:

  • content - Tout le contenu jusqu'à la prochaine balise. Vous pouvez laisser ceci dehors.
  • markup - La balise entière avec tout ce qu'elle contient.
  • comment - S'il s'agit d'un commentaire, le contenu du commentaire.
  • cdata - Si c'est un <![CDATA[...]]>, le contenu de CDATA.
  • close_tag - S'il s'agit d'une balise fermée (</div>), le nom de la balise.
  • tag - S'il s'agit d'une balise ouverte (<div>), le nom de la balise.
  • attributes - Tous les attributs à l'intérieur de la balise. Utilisez ceci pour obtenir tous les attributs si vous ne recevez 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 elle a été citée.
  • is_self_closing - C'est / s'il s'agit d'une balise à fermeture automatique, sinon rien.
  • _q et _v - Ignore ceux-ci; ils sont utilisés en interne pour les références arrières.

Si votre moteur regex ne prend pas en charge les captures nommées répétées, une section vous permettant de récupérer chaque attribut est appelée. Il suffit d’exécuter cette expression rationnelle sur le groupe attributes pour obtenir chaque attribute, attribute_name et attribute_value en dehors de ça.

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

2
Hounshell

Les expressions régulières ne sont pas assez puissantes pour un langage tel que HTML. Bien sûr, il existe 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
Gumbo

Tu sais ... Il y a beaucoup de mentalité de ta part NE PEUX PAS le faire et je pense que tout le monde des deux côtés de la clôture a raison et faux. Vous [~ # ~] pouvez [~ # ~] le faire, mais cela prend un peu plus de traitement que de simplement lancer une regex contre elle. Prenons this (je l’ai écrit en une heure) à titre d’exemple. Cela suppose que le code HTML est complètement valide, mais en fonction du langage que vous utilisez pour appliquer la regex susmentionnée, vous pouvez effectuer quelques corrections du code HTML pour vous assurer qu'il réussira. Par exemple, en supprimant les balises de fermeture qui ne sont pas supposées être présentes: </img> par exemple. Ajoutez ensuite la barre oblique HTML de fermeture aux éléments manquants, etc.

J'utiliserais cela 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 la fonction [x].getElementsByTagName() de JavaScript, par exemple. Je combinerais simplement les fonctionnalités que j'avais écrites dans la section DEFINE de l'expression régulière et les utiliserais pour entrer dans un arbre d'éléments, un à la fois.

Alors, sera-ce la réponse finale à 100% pour la validation HTML? Non, mais c'est un début et avec un peu plus de travail, cela peut être fait. Cependant, essayer de le faire dans une exécution regex n’est ni pratique, ni efficace.

0
Erutan409