Python /

Python: Conversor HTML a PMWiki

Descripción

El siguiente programa toma una URL como entrada, descarga su contenido en HTML y lo transforma automáticamente al formato utilizado por PmWiki, generando un fichero listo para integrarse en el wiki sin necesidad de ediciones manuales. El proceso consiste en obtener la página web, limpiar y adaptar su estructura al marcado propio de PmWiki y guardar el resultado en un archivo local, lo que facilita incorporar información externa de forma rápida y ordenada dentro del sistema.

  1. import re
  2. import sys
  3. from pathlib import Path
  4. from urllib.parse import urlparse
  5. from requests_html import HTMLSession, HTML
  6. import asyncio
  7.  
  8. # Variable global modificable o sustituible por parámetros
  9. URL_GLOBAL = "https://misutmeeple.com/2026/05/resena-light-speed-arena/"
  10.  
  11.  
  12. class ExtractorWeb:
  13.     """Responsable de descargar el contenido HTML renderizado de una URL."""
  14.  
  15.     def __init__(self):
  16.         self.sesion = HTMLSession()
  17.  
  18.     def descargar_html_renderizado(self, url: str) -> str:
  19.         """Descarga la URL y fuerza el renderizado de JavaScript configurando el event loop."""
  20.         try:
  21.             try:
  22.                 bucle_eventos = asyncio.get_event_loop()
  23.             except RuntimeError:
  24.                 bucle_eventos = asyncio.new_event_loop()
  25.                 asyncio.set_event_loop(bucle_eventos)
  26.  
  27.             respuesta = self.sesion.get(url)
  28.             respuesta.raise_for_status()
  29.             respuesta.html.render(timeout=20)
  30.             return respuesta.html.html
  31.         except Exception as error:
  32.             raise RuntimeWarning(f"Error al descargar o renderizar la URL: {error}")
  33.  
  34.  
  35. class TransformadorPmWiki:
  36.     """Responsable de transformar código HTML a la sintaxis oficial de PmWiki."""
  37.  
  38.     def transformar(self, contenido_html_completo: str) -> str:
  39.         """
  40.        Aísla el contenido de la etiqueta <body>, elimina scripts y lo transforma
  41.        aplicando exhaustivamente las reglas de PmWiki.
  42.        """
  43.         # 1. Aislar el contenido del <body>
  44.         objeto_html = HTML(html=contenido_html_completo)
  45.         nodo_body = objeto_html.find("body", first=True)
  46.  
  47.         html_a_procesar = nodo_body.html if nodo_body else contenido_html_completo
  48.  
  49.         # --- MEJORA: Eliminar bloques <script> antes de procesar el texto ---
  50.         # Se remueven tanto las etiquetas de apertura/cierre como todo el código JS de su interior
  51.         html_a_procesar = re.sub(r"<script\b[^>]*>([\s\S]*?)</script>", "", html_a_procesar, flags=re.IGNORECASE)
  52.  
  53.         # 2. Procesar elementos complejos (Tablas)
  54.         texto = self._procesar_tablas(html_a_procesar)
  55.  
  56.         # --- 3. Encabezados (Headings) ---
  57.         texto = re.sub(r"<h1>(.*?)</h1>", r"! \1\n", texto, flags=re.IGNORECASE)
  58.         texto = re.sub(r"<h2>(.*?)</h2>", r"!! \1\n", texto, flags=re.IGNORECASE)
  59.         texto = re.sub(r"<h3>(.*?)</h3>", r"!!! \1\n", texto, flags=re.IGNORECASE)
  60.         texto = re.sub(r"<h4>(.*?)</h4>", r"!!!! \1\n", texto, flags=re.IGNORECASE)
  61.  
  62.         # --- 4. Estilos de Carácter / Énfasis ---
  63.         texto = re.sub(r"<strong><em>(.*?)</em></strong>", r"'''''\1'''''", texto, flags=re.IGNORECASE)
  64.         texto = re.sub(r"<b><i>(.*?)</i></b>", r"'''''\1'''''", texto, flags=re.IGNORECASE)
  65.         texto = re.sub(r"<strong>(.*?)</strong>", r"'''\1'''", texto, flags=re.IGNORECASE)
  66.         texto = re.sub(r"<b>(.*?)</b>", r"'''\1'''", texto, flags=re.IGNORECASE)
  67.         texto = re.sub(r"<em>(.*?)</em>", r"''\1''", texto, flags=re.IGNORECASE)
  68.         texto = re.sub(r"<i>(.*?)</i>", r"''\1''", texto, flags=re.IGNORECASE)
  69.  
  70.         # Monospaciado, tachado, subrayado
  71.         texto = re.sub(r"<code>(.*?)</code>", r"@@\1@@", texto, flags=re.IGNORECASE)
  72.         texto = re.sub(r"<tt>(.*?)</tt>", r"@@\1@@", texto, flags=re.IGNORECASE)
  73.         texto = re.sub(r"<del>(.*?)</del>", r"{-\1-}", texto, flags=re.IGNORECASE)
  74.         texto = re.sub(r"<s>(.*?)</s>", r"{-\1-}", texto, flags=re.IGNORECASE)
  75.         texto = re.sub(r"<ins>(.*?)</ins>", r"{+\1+}", texto, flags=re.IGNORECASE)
  76.         texto = re.sub(r"<u>(.*?)</u>", r"{+\1+}", texto, flags=re.IGNORECASE)
  77.         texto = re.sub(r"<sup>(.*?)</sup>", r"^\1^", texto, flags=re.IGNORECASE)
  78.         texto = re.sub(r"<sub>(.*?)</sub>", r"_\1_", texto, flags=re.IGNORECASE)
  79.  
  80.         # Enlaces
  81.         texto = re.sub(r'<a\s+(?:[^>]*?\s+)?href="([^"]*)"[^>]*>(.*?)</a>', r"[[\1 | \2]]", texto, flags=re.IGNORECASE)
  82.  
  83.         # Estructuras (Líneas y Listas)
  84.         texto = re.sub(r"<hr\s*/?>", r"\n----\n", texto, flags=re.IGNORECASE)
  85.         texto = re.sub(r"<ul>\s*<li>", r"\n* ", texto, flags=re.IGNORECASE)
  86.         texto = re.sub(r"</ul>", r"\n", texto, flags=re.IGNORECASE)
  87.         texto = re.sub(r"</li>\s*<li>\s*<ul>\s*<li>", r"\n** ", texto, flags=re.IGNORECASE)
  88.         texto = re.sub(r"</li>\s*<li>", r"\n* ", texto, flags=re.IGNORECASE)
  89.         texto = re.sub(r"</li>", r"", texto, flags=re.IGNORECASE)
  90.  
  91.         # Listas ordenadas
  92.         texto = re.sub(r"<ol>\s*<li>", r"\n# ", texto, flags=re.IGNORECASE)
  93.         texto = re.sub(r"</ol>", r"\n", texto, flags=re.IGNORECASE)
  94.         texto = re.sub(r"</li>\s*<li>", r"\n# ", texto, flags=re.IGNORECASE)
  95.  
  96.         # Párrafos y Saltos
  97.         texto = re.sub(r"<br\s*/?>", r"\n", texto, flags=re.IGNORECASE)
  98.         texto = re.sub(r"<p>(.*?)</p>", r"\n\1\n", texto, flags=re.IGNORECASE)
  99.  
  100.         # --- 5. Limpieza Final ---
  101.         texto = re.sub(r"<[^>]+>", "", texto)
  102.         texto = re.sub(r"\n{3,}", "\n\n", texto)
  103.  
  104.         return texto.strip()
  105.  
  106.     def _procesar_tablas(self, html_puro: str) -> str:
  107.         objeto_html = HTML(html=html_puro)
  108.         tablas_html = objeto_html.find("table")
  109.  
  110.         for tabla in tablas_html:
  111.             bloque_pmwiki = []
  112.             atributos_tabla = []
  113.             if "border" in tabla.attrs:
  114.                 atributos_tabla.append(f"border={tabla.attrs['border']}")
  115.             if "bgcolor" in tabla.attrs:
  116.                 atributos_tabla.append(f"bgcolor={tabla.attrs['bgcolor']}")
  117.  
  118.             config_tabla = f"||{' '.join(atributos_tabla)}" if atributos_tabla else ""
  119.             if config_tabla:
  120.                 bloque_pmwiki.append(config_tabla)
  121.  
  122.             filas = tabla.find("tr")
  123.             for fila in filas:
  124.                 linea_fila = ""
  125.                 celdas = fila.find("th, td")
  126.  
  127.                 for celda in celdas:
  128.                     es_encabezado = celda.tag == "th"
  129.                     separador = "||!" if es_encabezado else "||"
  130.                     contenido_celda = celda.text.strip() if celda.text else ""
  131.  
  132.                     estilo = celda.attrs.get("style", "").lower()
  133.                     align = celda.attrs.get("align", "").lower()
  134.  
  135.                     if "text-align: right" in estilo or align == "right":
  136.                         linea_fila += f"{separador} {contenido_celda}"
  137.                     elif "text-align: center" in estilo or align == "center":
  138.                         linea_fila += f"{separador} {contenido_celda} "
  139.                     else:
  140.                         linea_fila += f"{separador}{contenido_celda} "
  141.  
  142.                 if linea_fila:
  143.                     linea_fila += "||"
  144.                     bloque_pmwiki.append(linea_fila)
  145.  
  146.             tabla_html_original = tabla.html
  147.             tabla_pmwiki_string = "\n" + "\n".join(bloque_pmwiki) + "\n"
  148.             html_puro = html_puro.replace(tabla_html_original, tabla_pmwiki_string)
  149.  
  150.         return html_puro
  151.  
  152.  
  153. class GeneradorNombreArchivo:
  154.     """Responsable de la lógica de negocio para generar nombres y rutas válidas."""
  155.  
  156.     @staticmethod
  157.     def obtener_ruta_en_directorio_del_script(url: str) -> Path:
  158.         """Genera la ruta absoluta final apuntando siempre al mismo directorio del script."""
  159.         url_analizada = urlparse(url)
  160.         ruta_completa = f"{url_analizada.netloc}{url_analizada.path}"
  161.  
  162.         limpio = re.sub(r"[^a-zA-Z0-9\.\-]", "", ruta_completa)
  163.         nombre_fichero = f"{limpio}.pmwiki" if limpio else "resultado.pmwiki"
  164.  
  165.         directorio_del_script = Path(__file__).resolve().parent
  166.         return directorio_del_script / nombre_fichero
  167.  
  168.  
  169. class AplicacionWebAPmWiki:
  170.     """Orquestador de la aplicación (Fachada / Controlador)."""
  171.  
  172.     def __init__(self):
  173.         self.extractor = ExtractorWeb()
  174.         self.transformador = TransformadorPmWiki()
  175.  
  176.     def ejecutar(self, url: str) -> None:
  177.         """Ejecuta el flujo completo de descarga, transformación y guardado."""
  178.         print(f"Iniciando proceso para: {url}")
  179.  
  180.         contenido_html_completo = self.extractor.descargar_html_renderizado(url)
  181.         contenido_pmwiki = self.transformador.transformar(contenido_html_completo)
  182.  
  183.         ruta_guardado = GeneradorNombreArchivo.obtener_ruta_en_directorio_del_script(url)
  184.         ruta_guardado.write_text(contenido_pmwiki, encoding="utf-8")
  185.  
  186.         print(f"Archivo guardado exitosamente en: {ruta_guardado}")
  187.  
  188.  
  189. if __name__ == "__main__":
  190.     aplicacion = AplicacionWebAPmWiki()
  191.     aplicacion.ejecutar(URL_GLOBAL)

Python : PyDev : py Programación

Última modificación de la página el 24 May 2026 a las 18h30
Powered by PmWiki