2009-02-26 26 views
178

Parece que todas las preguntas en stackoverflow donde el asker está usando regex para obtener algo de información de HTML inevitablemente tendrá una "respuesta" que dice que no se debe usar regex para analizar HTML.Usar expresiones regulares para analizar HTML: ¿por qué no?

¿Por qué no? Soy consciente de que hay analizadores HTML "reales" de comillas entre comillas, como Beautiful Soup, y estoy seguro de que son potentes y útiles, pero si solo estás haciendo algo simple, rápido o sucio, ¿por qué molestarse? usando algo tan complicado cuando algunas declaraciones de expresiones regulares funcionarán bien?

Por otra parte, ¿hay algo fundamental que no entiendo acerca de la expresión regular que los hace una mala elección para el análisis en general?

+3

creo que esto es una víctima de http://stackoverflow.com/questions/133601 – jcrossley3

+19

Debido a que sólo Chuck Norris * puede * analizar HTML con expresiones regulares (como se explica en esta famosa frase de Zalgo: http://stackoverflow.com/questions/1732348/regex-match-open-tags-ex cept-xhtml-self-contained-tags). – takeshin

+1

Esta pregunta me llevó a preguntar otra que de alguna manera está relacionada. En caso de que esté interesado: [Por qué no es posible usar expresiones regulares para analizar HTML/XML: una explicación formal en términos simples] (http://stackoverflow.com/q/6751105/146792) – mac

Respuesta

187

El análisis completo de HTML no es posible con expresiones regulares, ya que depende de que coincidan las etiquetas de apertura y cierre que no es posible con expresiones regulares.

Las expresiones regulares sólo se ajustan a regular languages pero HTML es un context-free language y no un lenguaje regular (Como @StefanPochmann señaló, lenguajes regulares son también libre de contexto, por lo que no necesariamente significa que no es regular independiente del contexto).Lo único que puede hacer con las expresiones regulares en HTML es la heurística, pero eso no funcionará en todas las condiciones. Debería ser posible presentar un archivo HTML que se emparejará incorrectamente con cualquier expresión regular.

+24

La mejor respuesta hasta el momento. Si solo puede coincidir con las gramáticas regulares, entonces necesitaríamos una expresión regular infinitamente grande para analizar una gramática libre de contexto como HTML. Me encanta cuando estas cosas tienen respuestas teóricas claras. – ntownsend

+1

Supuse que estábamos debatiendo expresiones regulares de tipo Perl en las que no son realmente expresiones regulares. –

+0

¿Qué es lo que hace que las expresiones regulares tipo Perl no sean expresiones regulares reales? – ntownsend

14

Dos razones rápidas:

  • escribir una expresión regular que puede hacer frente a una entrada maliciosa que es difícil; mucho más difícil que usar una herramienta preconstruida
  • escribir una expresión regular que puede funcionar con el marcado ridículo que inevitablemente se verá afectado es difícil; mucho más difícil que usar una herramienta preconstruida

En cuanto a la idoneidad de las expresiones regulares para el análisis en general: no son adecuadas. ¿Alguna vez has visto el tipo de expresiones regulares que necesitarías para analizar la mayoría de los idiomas?

+0

Wow? ¿Una votación negativa después de más de 2 años? En caso de que alguien se esté preguntando, no dije "Porque es teóricamente imposible" porque la pregunta claramente se hacía sobre "rápido y sucio", no "correcto". El OP claramente ya leyó respuestas que cubrían el territorio teóricamente imposible y aún no estaba satisfecho. –

+0

Tener un voto popular después de más de 5 años.:) En cuanto a por qué podría haber recibido el voto a favor, no estoy calificado para decirlo, pero personalmente, me hubiera gustado ver algunos ejemplos o explicaciones en lugar de la pregunta retórica de cierre. –

+2

Esencialmente, todos los análisis html rápidos y sucios que se realizan en el envío de productos o herramientas internas terminan siendo un gran agujero de seguridad o un error que está por ocurrir. Debe ser desalentado con gusto. Si uno puede usar un regex, uno puede usar un analizador html apropiado. –

7

Porque hay muchas formas de "arruinar" el HTML que los navegadores tratarán de una manera bastante liberal pero requerirá bastante esfuerzo reproducir el comportamiento liberal del navegador para cubrir todos los casos con expresiones regulares, por lo que su expresión inevitable será inevitable fallar en algunos casos especiales, y eso posiblemente introduciría serias brechas de seguridad en su sistema.

+0

Muy cierto, la mayoría de HTML parece ser horrible. No entiendo cómo una expresión regular defectuosa puede presentar brechas de seguridad serias. ¿Puede dar un ejemplo? – ntownsend

+3

ntownsend: Por ejemplo, cree que ha eliminado todas las etiquetas de secuencia de comandos del HTML pero su expresión regular no cubre un caso especial (que, digamos, solo funciona en IE6): boom, ¡tiene una vulerabilidad de XSS! –

+0

Este fue un ejemplo estrictamente hipotético, ya que la mayoría de los ejemplos del mundo real son demasiado complicados para encajar en estos comentarios, pero puede encontrar algunos haciendo búsquedas rápidas en Google sobre el tema. –

6

El problema es que la mayoría de los usuarios que hacen una pregunta que tiene que ver con HTML y expresiones regulares lo hacen porque no pueden encontrar una expresión propia que funcione. Entonces uno tiene que pensar si todo sería más fácil cuando se usa un analizador DOM o SAX o algo similar. Están optimizados y construidos con el propósito de trabajar con estructuras de documentos similares a XML.

Claro, hay problemas que pueden resolverse fácilmente con expresiones regulares. Pero el énfasis se encuentra en fácilmente.

Si solo quiere encontrar todas las URL que se parecen a http://.../, está bien con las expresiones regulares. Pero si quiere encontrar todas las URL que están en un elemento a que tiene la clase 'mylink', probablemente sea mejor que use un analizador apropiado.

31

Para quick'n'dirty regexp va a estar bien. Pero lo fundamental es saber que es imposible para construir una expresión regular que correctamente analizar HTML.

La razón es que las expresiones regulares no pueden manejar expresiones anidadas arbitrariamente. Ver Can regular expressions be used to match nested patterns?

+0

@j_random_hacker: se agregó una referencia a otra respuesta de Stackoverflow. – kmkaplan

+1

Algunas bibliotecas de expresiones regulares pueden hacer expresiones regulares recursivas (lo que las convierte efectivamente en expresiones no regulares :) –

0

Las expresiones regulares no son lo suficientemente potentes para un lenguaje como HTML. Claro, hay algunos ejemplos donde puedes usar expresiones regulares. Pero, en general, no es apropiado para el análisis sintáctico.

15

En cuanto al análisis, las expresiones regulares pueden ser útiles en la etapa de "análisis léxico" (lexer), donde la entrada se divide en tokens. Es menos útil en la etapa real de "construir un árbol de análisis sintáctico".

Para un analizador HTML, esperaría que solo acepte HTML bien formado y que requiera capacidades fuera de lo que puede hacer una expresión regular (no pueden "contar" y asegurarse de que un número determinado de elementos de apertura están equilibrados por el mismo número de elementos de cierre).

5

Las expresiones regulares no se diseñaron para manejar una estructura de etiquetas anidadas, y en el mejor de los casos es complicado (en el peor, imposible) manejar todos los casos de borde posibles que se obtienen con HTML real.

4

Creo que la respuesta está en la teoría de cálculo. Para que un lenguaje sea analizado usando regex, debe ser por definición "regular" (link). HTML no es un lenguaje normal, ya que no cumple con una serie de criterios para un idioma normal (mucho que ver con los muchos niveles de anidamiento inherentes al código html). Si está interesado en la teoría de la computación, le recomendaría el libro this.

+0

De hecho, he leído ese libro. Simplemente no se me ocurrió que HTML es un lenguaje sin contexto. – ntownsend

2

"Depende" sin embargo. Es cierto que las expresiones regulares no pueden analizar HTML con verdadera precisión, por todos los motivos que se dan aquí. Sin embargo, si las consecuencias de equivocarse (como no manejar etiquetas anidadas) son menores, y si las expresiones regulares son muy convenientes en su entorno (como cuando está pirateando Perl), continúe.

Supongamos que está, quizás, analizando las páginas web que enlazan con su sitio, tal vez las encontró con una búsqueda de enlaces de Google, y desea una forma rápida de obtener una idea general del contexto que rodea su enlace . Está intentando ejecutar un pequeño informe que podría alertarlo para que vincule el correo no deseado, algo así.

En ese caso, el error de análisis de algunos de los documentos no será un gran problema. Nadie más que tú verá los errores, y si tienes mucha suerte habrá pocos que puedas seguir individualmente.

Supongo que estoy diciendo que es una compensación. A veces, la implementación o el uso de un analizador correcto, por más fácil que sea, puede no valer la pena si la precisión no es crítica.

Solo tenga cuidado con sus suposiciones. Puedo pensar en algunas formas en que el atajo de expresiones regulares puede ser contraproducente si estás tratando de analizar algo que se mostrará en público, por ejemplo.

2

Definitivamente, hay casos en los que usar una expresión regular para analizar información de HTML es la forma correcta de hacerlo; depende en gran medida de la situación específica.

El consenso anterior es que, en general, es una mala idea. Sin embargo, si la estructura HTML es conocida (y es poco probable que cambie), sigue siendo un enfoque válido.

1

Ten en cuenta que, si bien el HTML en sí no es regular, partes de una página que estás mirando pueden ser ser regular.

Por ejemplo, es un error para anidar las etiquetas <form>; si la página web funciona correctamente, usar una expresión regular para tomar un <form> sería completamente razonable.

Recientemente realicé algunos raspados de la web usando solo selenio y expresiones regulares. Me salí con la suya porque los datos que quería se pusieron en un <form>, y los coloqué en un formato de tabla simple (para que incluso pudiera contar con <table>, <tr> y <td> para no anidar, lo cual es realmente inusual). En cierto grado, las expresiones regulares eran casi necesarias, porque parte de la estructura a la que tenía que acceder estaba delimitada por comentarios. (Hermosa sopa le puede dar comentarios, pero habría sido difícil de agarrar y <!-- BEGIN --><!-- END --> bloques usando sopa Hermosa.)

Si tuviera que preocuparse acerca de las tablas anidadas, sin embargo, mi enfoque, simplemente no habría funcionado! Hubiera tenido que recurrir a Beautiful Soup. Incluso entonces, sin embargo, a veces puede usar una expresión regular para tomar el trozo que necesita y luego profundizar desde allí.

1

En realidad, el análisis de HTML con expresiones regulares es perfectamente posible en PHP. Solo tiene que analizar toda la cadena al revés usando strrpos para encontrar < y repetir la expresión regular desde allí utilizando especificadores sin codificación cada vez para superar las etiquetas anidadas. No es lujoso y terriblemente lento en cosas grandes, pero lo usé para mi propio editor personal de plantillas para mi sitio web. En realidad no estaba analizando HTML, pero algunas etiquetas personalizadas que hice para consultar las entradas de la base de datos muestran tablas de datos (mi etiqueta <#if()> podría resaltar entradas especiales de esta manera). No estaba preparado para buscar un analizador XML en solo un par de etiquetas creadas por uno mismo (con datos muy no XML dentro de ellas) aquí y allá.

Por lo tanto, a pesar de que esta pregunta está considerablemente muerta, todavía aparece en una búsqueda en Google. Lo leí y pensé "desafío aceptado" y terminé de arreglar mi código simple sin tener que reemplazar todo. Decidió ofrecer una opinión diferente a cualquiera que busque una razón similar. Además, la última respuesta se publicó hace 4 horas, por lo que este sigue siendo un tema candente.

+1

-1 por sugerir una idea TERRIBLE. ¿Consideró los espacios en blanco entre la etiqueta y el soporte del ángulo de cierre? (Por ejemplo, '') ¿Consideró las etiquetas de cierre comentadas? (Por ejemplo, '

+3

Parece ser la respuesta real, aunque probablemente sea posible analizar HTML arbitrario con expresiones regulares, ya que las expresiones actuales son más que un autómatas finito, para analizar un html arbitrario y no solo una página concreta, debe volver a implementar un analizador HTML en expresiones regulares y expresiones regulares seguramente se vuelven 1000 veces ilegibles. –

+0

Hola Andy, me tomé el tiempo para encontrar una expresión que respalde tus casos mencionados. http://stackoverflow.com/a/40095824/1204332 ¡Déjame saber lo que piensas! :) –

+0

El razonamiento de esta respuesta es * way * obsoleto, y se aplica incluso menos hoy de lo que originalmente (lo que creo que no fue así). (Citando OP: "si solo estás haciendo algo simple, rápido o sucio ...") –

0

Usted, sabe ... hay mucha mentalidad de usted NO PUEDE hacerlo y creo que todos en ambos lados de la valla son correctos y equivocados. Usted CAN, pero requiere un poco más de procesamiento que solo ejecutar una expresión regular en su contra. Tome this (escribí esto dentro de una hora) como ejemplo. Asume que el HTML es completamente válido, pero dependiendo del idioma que utilice para aplicar la expresión regular antes mencionada, puede corregir el código HTML para asegurarse de que tenga éxito. Por ejemplo, eliminar etiquetas de cierre que no se supone que estén allí: </img> por ejemplo. A continuación, agregue la barra única de cierre de HTML hacia los elementos que les faltan, etc.

Usaría esto en el contexto de escribir una biblioteca que me permitiera realizar la recuperación de elementos HTML similar a la de JavaScript [x].getElementsByTagName(), por ejemplo. Acabo de empalmar la funcionalidad que escribí en la sección DEFINE de la expresión regular y la uso para entrar dentro de un árbol de elementos, uno a la vez.

¿Esta será la respuesta final del 100% para validar HTML? No. Pero es un comienzo y con un poco más de trabajo, se puede hacer. Sin embargo, tratar de hacerlo dentro de una ejecución de expresiones regulares no es práctico ni eficiente.

2

Esta expresión recupera atributos de elementos HTML. Es compatible con:

  • /atributos sin comillas citados,
  • comillas simples/dobles,
  • comillas escapadas atributos dentro,
  • espacios alrededor de signos de igualdad,
  • cualquier número de atributos,
  • cheque solo para los atributos dentro de las etiquetas,
  • comentarios de escape, y
  • manage dif comillas diferentes dentro de un valor de atributo.

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

Check it out. Funciona mejor con las banderas "gisx", como en la demostración.

+0

Eso es muy interesante. No legible, probablemente difícil de depurar, pero aún así: ¡trabajo impresionante! –

1

Probé mi mano en una expresión regular para esto también.Es sobre todo útil para encontrar trozos de contenido emparejados con la próxima etiqueta HTML, y no busca que coincidan con etiquetas de cierre, pero recogerá etiquetas de cierre. Tira una pila en tu propio idioma para verificarlos.

Usar con las opciones 'sx'. 'G' también si te sientes con suerte:

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

Éste está diseñado para Python (que podría funcionar para otros idiomas, no lo he probado, que utiliza los símbolos de anticipación positivos, negativos, lookbehinds y referencias hacia atrás con nombre) . Soporta:

  • abierto Tag - <div ...>
  • Cerrar Tag - </div>
  • Comentario - <!-- ... -->
  • CDATA - <![CDATA[ ... ]]>
  • cierre automático Tag - <div .../>
  • valores de atributos opcionales - <input checked>
  • Atributo sin cita/cotizado Valores TE - <div style='...'>
  • comillas dobles/individuales - <div style="...">
  • comillas escapadas - <a title='John\'s Story'>
    (esto no es realmente HTML válido, pero yo soy un buen tipo)
  • espacios equivale aproximadamente al Signos - <a href = '...'>
  • Captura llamado así por cosas interesantes

también es bastante buena acerca de no activación de las etiquetas mal formados, como cuando se le olvida una < o >.

Si su sabor regex admite repetidas capturas nombradas, entonces está dorado, pero Python re no (sé que la expresión regular sí, pero necesito usar Python vainilla). Esto es lo que obtienes:

  • content - Todo el contenido hasta la próxima etiqueta. Podrías dejar esto fuera.
  • markup - Toda la etiqueta con todo lo que contiene.
  • comment - Si es un comentario, el contenido del comentario.
  • cdata - Si es un <![CDATA[...]]>, el contenido CDATA.
  • close_tag - Si es una etiqueta cerrada (</div>), el nombre de la etiqueta.
  • tag - Si es una etiqueta abierta (<div>), el nombre de la etiqueta.
  • attributes - Todos los atributos dentro de la etiqueta. Use esto para obtener todos los atributos si no obtiene grupos repetidos.
  • attribute - Repetido, cada atributo.
  • attribute_name - Se repite, cada nombre de atributo.
  • attribute_value - Repite, cada valor de atributo. Esto incluye las cotizaciones si fue cotizado.
  • is_self_closing - Esto es / si es una etiqueta de cierre automático, de lo contrario nada.
  • _q y _v - Ignore estos; se usan internamente para backreferences.

Si su motor de expresiones regulares no admite repetidas capturas con nombre, hay una sección llamada que puede usar para obtener cada atributo. Simplemente ejecute esa expresión regular en el grupo attributes para obtener cada attribute, attribute_name y attribute_value.

demo aquí: https://regex101.com/r/mH8jSu/11

0

HTML/XML se divide en marcas y contenido.
Regex solo es útil para realizar un análisis de etiquetas léxicas.
Supongo que se puede deducir el contenido.
Sería una buena opción para un analizador SAX.
Las etiquetas y el contenido se pueden entregar a un usuario
función definida donde se puede seguir el anidamiento/cierre de los elementos
.

En cuanto a solo analizar las etiquetas, se puede hacer con
regex y se utiliza para quitar etiquetas de un documento.

Durante años de pruebas, he encontrado el secreto para el
navegadores de manera analizan las etiquetas, tanto bien como mal formadas.

Los elementos normales son analizados con esta forma:

El núcleo de estas etiquetas utilizan esta expresión regular

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

Se dará cuenta de este [^>]? como una de las alternancias.
Esto coincidirá con las comillas no balanceadas de las etiquetas mal formadas.

También es, la raíz más de todos los malvados para expresiones regulares.
La forma en que se usa activará un bache para satisfacer su codicioso, debe coincidir
contenedor cuantificado.

Si se usa pasivamente, nunca hay un problema.
Pero, si la fuerza algo para que coincida con intercalando con
un atributo/valor deseado par, y no proporcionan una protección adecuada
de dar marcha atrás, es una pesadilla de control.

Esta es la forma general de las etiquetas antiguas.
¿Observe el [\w:] que representa el nombre de la etiqueta?
En realidad, los caracteres legales que representan el nombre de la etiqueta
son una increíble lista de caracteres Unicode.

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

Cambiando de tema, también vemos que simplemente no se puede buscar una etiqueta específica
sin analizar TODAS las etiquetas.
Quiero decir que podría, pero tendría que usar una combinación de
verbos como (* SKIP) (* FAIL) pero aún así todas las etiquetas tienen que ser analizadas.

La razón es que la sintaxis de la etiqueta puede estar oculto dentro de otras etiquetas, etc ..

Por lo tanto, para analizar todas las etiquetas de forma pasiva, es necesaria una expresión regular como la de abajo.
Este en particular coincide con contenido invisible también.

Como nuevo HTML o xml o cualquier otro desarrollo de construcciones nuevas, solo agréguelo como
una de las alternancias.


página web nota - nunca he visto una página web (o XHTML/XML) que esta
tenían problemas con el. Si encuentras uno, házmelo saber.

Nota de rendimiento - Es rápido. Este es el analizador de etiquetas más rápido que he visto
(puede ser más rápido, quién sabe).
Tengo varias versiones específicas. También es excelente como raspador
(si eres del tipo práctico).


completa de expresiones regulares prima

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

con formato mirar

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