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

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

Pourquoi pas? Je sais qu’il ya des “vrais” parsingurs HTML comme Beautiful Soup , et je suis sûr qu’ils sont puissants et utiles, mais si vous faites simplement quelque chose de simple, de rapide ou de sale, alors pourquoi? se soucier d’utiliser quelque chose de si compliqué quand quelques déclarations de regex fonctionneront très bien?

De plus, y a-t-il juste quelque chose de fondamental que je ne comprends pas au sujet des regex qui en fait un mauvais choix pour l’parsing en général?

    L’parsing HTML complète n’est pas possible avec les expressions régulières, car cela dépend de la correspondance entre la balise d’ouverture et la balise de fermeture, ce qui n’est pas possible avec les expressions rationnelles.

    Les expressions régulières ne peuvent correspondre qu’à des langages réguliers, mais HTML est un langage dépourvu de contexte et non un langage courant (comme l’a souligné @StefanPochmann, les langages normaux sont également dépourvus de contexte, ce qui signifie qu’ils ne sont pas forcément réguliers). La seule chose que vous pouvez faire avec les expressions rationnelles sur HTML est l’heuristique, mais cela ne fonctionnera pas dans toutes les conditions. Il devrait être possible de présenter un fichier HTML qui sera mal comparé par une expression régulière.

    Pour une demande de remboursement rapide, regexp fera bien. Mais la chose fondamentale à savoir est qu’il est impossible de construire une expression rationnelle qui parsingra correctement le HTML.

    La raison en est que les expressions rationnelles ne peuvent pas gérer des expressions nestedes de manière arbitraire. Voir Les expressions régulières peuvent-elles être utilisées pour correspondre à des modèles nesteds?

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

    Supposons que vous ayez un fichier HTML où vous tentez d’extraire des URL à partir de balises .

     

    Donc, vous écrivez une regex comme ceci dans Perl:

     if ( $html =~ / 

    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:

      

    ou

      

    ou

      

    ou

      

    ou vous commencez à obtenir des faux positifs de

      

    Cela semble 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 qu'une recette pour de futurs chagrins.

    En ce qui concerne l’parsing syntaxique, les expressions régulières peuvent être utiles dans l’étape «parsing lexicale» (lexer), où l’entrée est divisée en jetons. C’est moins utile dans la phase “construire un arbre d’parsing”.

    Pour un parsingur HTML, je m’attendrais à ce qu’il accepte uniquement 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 sont équilibrés par le même nombre) des éléments de fermeture).

    Deux raisons rapides:

    • écrire une regex capable de résister à des entrées malveillantes est difficile; beaucoup plus difficile que d’utiliser un outil pré-construit
    • écrire une regex qui peut fonctionner avec le balisage ridicule avec lequel vous serez inévitablement coincé est difficile; beaucoup plus difficile que d’utiliser un outil pré-construit

    Concernant l’adéquation des expressions rationnelles pour l’parsing syntaxique en général: elles ne conviennent pas. Avez-vous déjà vu les types de regexes dont vous auriez besoin pour parsingr la plupart des langues?

    Comme il y a beaucoup de façons de “bousiller” le HTML que les navigateurs traiteront de manière plutôt libérale, il faudra un certain effort pour reproduire le comportement libéral du navigateur afin de couvrir tous les cas avec des expressions régulières. cas, 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 liée à HTML et à regex le font car ils ne trouvent pas de regex qui fonctionne. Il faut ensuite se demander si tout serait plus facile avec un parsingur 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 facilement .

    Si vous voulez juste trouver toutes les URL qui ressemblent à http://.../ vous êtes bien avec les regexps. Mais si vous voulez trouver toutes les URL qui se trouvent dans un a-Element ayant la classe ‘mylink’, vous devriez probablement utiliser un parsingur approprié.

    Les expressions régulières ne sont pas conçues pour gérer une structure de balise nestede, et il est au mieux compliqué (au pire, impossible) de gérer tous les cas limites possibles obtenus avec du vrai HTML.

    Je pense que la réponse réside dans la théorie des calculs. Pour qu’une langue soit analysée en utilisant regex, elle doit être par définition “normal” ( lien ). Le langage HTML n’est pas un langage standard car il ne répond pas à un certain nombre de critères pour un langage standard (beaucoup à voir avec les nombreux niveaux d’imbrication inhérents au code HTML). Si vous êtes intéressé par la théorie du calcul, je recommanderais ce livre.

    “Ca dépend” cependant. Il est vrai que les expressions rationnelles ne peuvent pas et ne peuvent pas parsingr le code HTML avec une réelle précision, pour toutes les raisons données ici. Si, toutefois, les conséquences d’une erreur (par exemple, ne pas gérer les tags nesteds) sont mineures et si les expressions rationnelles sont super pratiques dans votre environnement (par exemple, lorsque vous piratez Perl), allez-y.

    Supposons que vous parsingz peut-être des pages Web liées à votre site – peut-être que vous les avez trouvées avec une recherche de liens Google – et que vous souhaitiez obtenir rapidement un aperçu général du contexte entourant votre lien. Vous essayez d’exécuter un petit rapport qui pourrait vous alerter de lier le spam, par exemple.

    Dans ce cas, la mauvaise gestion de certains documents ne sera pas un gros problème. Personne d’autre que vous ne verrez les erreurs, et si vous êtes très chanceux, il y en aura assez que vous pourrez suivre individuellement.

    Je suppose que je dis que c’est un compromis. Parfois, l’implémentation ou l’utilisation d’un parsingur correct, aussi simple que cela puisse être, ne vaut peut-être pas la peine si la précision n’est pas critique.

    Faites attention à vos hypothèses. Je peux penser à quelques façons dont le raccourci regexp peut se retourner si vous essayez d’parsingr quelque chose qui sera affiché en public, par exemple.

    Il y a certainement des cas où l’utilisation d’une expression régulière pour parsingr 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 qu’en général c’est une mauvaise idée. Cependant, si la structure HTML est connue (et peu susceptible de changer), il s’agit toujours d’une approche valide.

    Cette expression récupère les atsortingbuts des éléments HTML. Elle supporte:

    • atsortingbuts non cotés / cotés,
    • guillemets simples / doubles,
    • échappé à des guillemets à l’intérieur d’atsortingbuts,
    • les espaces autour des signes égaux,
    • n’importe quel nombre d’atsortingbuts,
    • vérifier uniquement les atsortingbuts à l’intérieur des balises,
    • échapper des commentaires, et
    • gérer différentes citations dans une valeur d’atsortingbut.

    (?:\<\!\-\-(?:(?!\-\-\>)\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.

    Gardez à l’esprit que, bien que HTML ne soit pas régulier, certaines parties d’une page que vous consultez peuvent être régulières.

    Par exemple, il s’agit d’une erreur pour les balises

    à imbriquer; Si la page Web fonctionne correctement, utiliser une expression régulière pour saisir un

    serait tout à fait raisonnable.

    J’ai récemment fait un peu de web scraping en utilisant seulement Selenium et des expressions régulières. Je me suis débrouillé parce que les données que je voulais étaient placées dans un

    et placées dans un format de tableau simple (donc je pouvais même compter sur

    ,

    et

    pour ne pas être nestedes– ce qui est en fait 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 de saisir les blocs et utilisant Beautiful Soup.)

    Si je devais m’inquiéter des tables nestedes, mon approche n’aurait tout simplement pas fonctionné! J’aurais dû me rabattre sur Beautiful Soup. Même dans ce cas, cependant, vous pouvez parfois utiliser une expression régulière pour saisir le morceau dont vous avez besoin, puis explorer à partir de là.

    En fait, l’parsing HTML avec regex est parfaitement possible en PHP. Vous devez simplement parsingr la chaîne entière en arrière en utilisant strrpos pour rechercher < et répéter le regex à partir de là en utilisant des spécificateurs non-concordants à chaque fois pour passer les balises nestedes. Pas très compliqué 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 n'étais pas en train d'parsingr HTML, mais quelques balises personnalisées que j'ai créées pour interroger des entrées de firebase database afin d'afficher des tables de données (ma <#if()> pouvait mettre en évidence des entrées spéciales). Je n'étais pas prêt à choisir un parsingur XML sur quelques balises créées par moi-même (avec des données très non XML en leur sein) ici et là.

    Donc, même si cette question est considérablement morte, elle apparaît toujours dans une recherche Google. Je l'ai lu et j'ai pensé "challenge accepté" et j'ai fini de fixer mon code simple sans avoir à tout remplacer. Décidé d'offrir un avis différent à toute personne cherchant une raison similaire. La dernière réponse a également été publiée il y a 4 heures. Il s'agit donc toujours d'un sujet d'actualité.

    J’ai essayé ma main à une regex pour cela aussi. C’est surtout utile pour trouver des morceaux de contenu appariés avec la balise HTML suivante, et il ne cherche pas à faire correspondre les balises fermées, mais il récupérera les balises proches. Lancez une stack dans votre propre langue pour les vérifier.

    Utiliser avec les options ‘sx’. ‘g’ aussi si vous vous sentez chanceux:

     (?P.*?) # Content up to next tag (?P # Entire tag .+?)]]>| #  | #  \w+)\s*>| #  <(?P\w+) #  (?P\s+ # : Use this part to get the atsortingbutes out of 'atsortingbutes' group. (?P\w+) (?:\s*=\s* (?P [\w:/.\-]+| # Unquoted (?=(?P<_v> # Quoted (?P<_q>['\"]).*?(? )* )\s* (?P/?) # Self-closing indicator >) # End of tag 

    Celui-ci est conçu pour Python (il pourrait fonctionner pour d’autres langages, ne pas l’avoir essayé, il utilise des “lookaheads” positifs, des lookbehind négatifs et des backreferences nommées). Les soutiens:

    • Open Tag –

    • Fermer la
    • Commentaire –
    • CDATA –
    • Balise à fermeture automatique –

    • Valeurs d’atsortingbut facultatives –
    • Valeurs d’atsortingbuts non cotées / cotées –

    • Single / Double Quotes –

    • Citations
      (ce n’est pas du HTML vraiment valide, mais je suis un gars sympa)
    • Espaces autour de signes égaux –
    • Captures nommées pour des bits intéressants

    Il est également très utile de ne pas déclencher sur des tags mal formés, comme lorsque vous oubliez un < ou > .

    Si votre expression regex prend en charge les captures nommées répété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'au tag suivant. Vous pourriez laisser cela de côté.
    • markup - Le tag entier avec tout ce qu'il contient.
    • comment - Si c'est un commentaire, le contenu du commentaire.
    • cdata - Si c'est un , le contenu du CDATA.
    • close_tag - S'il s'agit d'une balise proche ( ), le nom de la balise.
    • tag - S'il s'agit d'une balise ouverte (

      ), le nom de la balise.

    • atsortingbutes - Tous les atsortingbuts à l'intérieur de la balise. Utilisez ceci pour obtenir tous les atsortingbuts si vous n'obtenez pas de groupes répétés.
    • atsortingbute - Répété, chaque atsortingbut.
    • atsortingbute_name - Répété, chaque nom d'atsortingbut.
    • atsortingbute_value - Répété, chaque valeur d'atsortingbut. Cela inclut les citations si elles ont été citées.
    • is_self_closing - Ceci est / si c'est une balise à fermeture automatique, sinon rien.
    • _q et _v - Ignorez-les; ils sont utilisés en interne pour les backreferences.

    Si votre moteur d'expression régulière ne prend pas en charge les captures nommées répétées, il existe une section appelée pour obtenir chaque atsortingbut. Il suffit de lancer cette expression régulière sur le groupe d' atsortingbutes pour obtenir chaque atsortingbute , atsortingbute_name et atsortingbute_value .

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

    HTML / XML est divisé en un balisage et un contenu.
    Regex n’est utile que pour parsingr un tag lexical.
    Je suppose que vous pourriez en déduire le contenu.
    Ce serait un bon choix pour un parsingur SAX.
    Les balises et le contenu peuvent être livrés à un utilisateur
    fonction définie où nidification / fermeture des éléments
    peut être gardé trace de.

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

    Au fil des années de tests, j’ai trouvé le secret de la
    les navigateurs parsingnt les balises, à la fois bien et mal formées.

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

    Le kernel de ces balises utilise cette regex

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

    Vous remarquerez ceci [^>]? comme l’une des alternances.
    Cela correspondra aux cotations déséquilibrées des tags mal formés.

    C’est aussi la racine la plus commune de tout le mal aux expressions régulières.
    La façon dont il est utilisé déclenchera une bosse pour satisfaire sa gourmandise.
    conteneur quantifié.

    S’il est utilisé passivement, il n’y a jamais de problème.
    Mais si vous forcez quelque chose en l’intervertissant avec
    une paire atsortingbut / valeur recherchée et ne fournit pas une protection adéquate
    de retour en arrière, c’est un cauchemar hors de contrôle.

    C’est la forme générale pour les anciens tags.
    Notez le [\w:] représentant le nom de la balise?
    En réalité, les caractères légaux représentant le nom du tag
    sont une liste incroyable de caractères Unicode.

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

    Passons à autre chose, nous voyons également que vous ne pouvez tout simplement pas rechercher une balise spécifique
    sans parsingr toutes les balises.
    Je veux dire que vous pourriez, mais il faudrait utiliser une combinaison de
    des verbes comme (* SKIP) (* FAIL) mais toutes les balises doivent toujours être analysées.

    La raison en est que la syntaxe de la balise peut être cachée dans d’autres balises, etc.

    Donc, pour parsingr passivement toutes les balises, une regex est nécessaire comme celle ci-dessous.
    Ce particulier correspond également au contenu invisible .

    En tant que nouveau HTML ou XML ou tout autre développement de nouvelles constructions, ajoutez-le simplement comme
    une des alternances.


    Note sur la page Web – Je n’ai jamais vu une page Web (ou xhtml / xml)
    eu des problèmes avec. Si vous en trouvez un, faites le moi savoir.

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


    Regex brute complète

    <(?:(?:(?:(script|style|object|embed|applet|noframes|noscript|noembed)(?:\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]*?))))>

    Aspect 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]*?  ) ) | (?: /? [\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 existe des exemples où vous pouvez utiliser des expressions régulières. Mais en général, ce n’est pas approprié pour l’parsing.

    Vous savez que vous ne pouvez pas le faire et je pense que tout le monde des deux côtés de la barrière a raison et tort. Vous POUVEZ le faire, mais il faut un peu plus de traitement que d’exécuter une seule regex. Prenez ceci (j’ai écrit ceci en moins d’une heure) comme exemple. Cela suppose que le code HTML est complètement valide, mais selon le langage que vous utilisez pour appliquer la regex susmentionnée, vous pouvez corriger le code HTML pour vous assurer qu’il réussira. Par exemple, supprimer les balises de fermeture qui ne sont pas supposées être là: par exemple. Ensuite, ajoutez la barre oblique HTML de fermeture unique aux éléments qui les manquent, etc.

    J’utiliserais ceci dans le contexte de l’écriture d’une bibliothèque qui me permettrait d’effectuer une récupération d’éléments HTML similaire à celle de JavaScript [x].getElementsByTagName() , par exemple. Je voudrais juste append la fonctionnalité que j’ai écrite dans la section DEFINE du regex et l’utiliser pour entrer dans une arborescence d’éléments, un à la fois.

    Donc, sera-ce la dernière réponse à 100% pour valider le HTML? Non, mais c’est un début et avec un peu plus de travail, cela peut être fait. Cependant, essayer de le faire à l’intérieur d’une exécution rationnelle n’est ni pratique ni efficace.