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?
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.
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?
(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.
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.)
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?
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.
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é.
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.
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.
"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.
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.
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.
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à.
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.
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
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]*? )
)
)
)
>
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.
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.