La primera fila de una tabla HTML es la fila de encabezado.
Excepto cuando no lo es.
Las tablas de Wikipedia suelen tener una fila de título que abarca todas las columnas antes de los encabezados reales. Los sitios de estadísticas deportivas tienen encabezados agrupados donde "Tiempo de Juego" abarca múltiples sub-columnas como "PJ", "Titular", "Min". Las tablas financieras tienen filas de unidad ("en millones USD") que parecen encabezados pero no lo son.
Si asumes que la fila 0 siempre es el encabezado, tus exportaciones van a estar rotas para una porción significativa de tablas del mundo real.
Aquí te muestro cómo detectar la fila de encabezado real programáticamente.
El Problema: Tres Tipos de "Primeras Filas"
Considerá estos patrones comunes:
Patrón 1: Fila de Título
<table>
<tr>
<th colspan="4">Población Mundial por País</th> <!-- Título, no encabezado -->
</tr>
<tr>
<th>Ranking</th>
<th>País</th>
<th>Población</th>
<th>% del Mundo</th>
</tr>
<tr>
<td>1</td>
<td>India</td>
<td>1.428.627.663</td>
<td>17,85%</td>
</tr>
</table>
La fila 0 es un título. La fila 1 es el encabezado. La fila 2+ son datos.
Patrón 2: Encabezados Agrupados (Dos Niveles)
<table>
<tr>
<th></th>
<th></th>
<th colspan="3">Tiempo de Juego</th>
<th colspan="2">Rendimiento</th>
</tr>
<tr>
<th>Jugador</th>
<th>Nación</th>
<th>PJ</th>
<th>Titular</th>
<th>Min</th>
<th>Goles</th>
<th>Asist</th>
</tr>
<tr>
<td>Juan Pérez</td>
<td>ARG</td>
<td>34</td>
<td>30</td>
<td>2700</td>
<td>12</td>
<td>8</td>
</tr>
</table>
La fila 0 son encabezados de grupo. La fila 1 son los encabezados de columna reales. La fila 2+ son datos.
Patrón 3: Prefijo de Navegación de Wikipedia
<tr>
<th colspan="3">v t e Sitios del Patrimonio Mundial</th>
</tr>
Los enlaces "v t e" (ver/discusión/editar) son la navegación de plantillas de Wikipedia. Necesitan ser eliminados, y la fila podría ser un título en lugar de un encabezado.
Heurística 1: Detectar Filas de Título
Una fila de título típicamente tiene:
- Una sola celda (o muy pocas celdas)
- colspan grande abarcando la mayoría/todas las columnas
- Contenido de texto que parece un título, no nombres de columnas
function isTitleRow(row, totalColumns) {
if (!row || row.length === 0) return false;
// Contar celdas no vacías
const nonEmptyCells = row.filter(cell => cell && cell.trim()).length;
// Las filas de título suelen tener 1-2 celdas no vacías
if (nonEmptyCells > 2) return false;
// Verificar si la primera celda abarca la mayoría de columnas (indica colspan)
// En una matriz normalizada, esto se muestra como valores repetidos
const firstValue = row[0];
const repeatedCount = row.filter(cell => cell === firstValue).length;
// Si el primer valor se repite en >50% de las columnas, probablemente es un título con colspan
if (repeatedCount > totalColumns * 0.5) {
return true;
}
return false;
}
Heurística 2: Qué Hace Que una Fila "Parezca Encabezados"
Las filas de encabezado tienen características que las distinguen de las filas de datos:
function rowLooksLikeHeaders(row) {
if (!row || row.length === 0) return false;
// Si está dominada por números puros, no son encabezados
let numericCells = 0;
let textCells = 0;
let emptyCells = 0;
for (const cell of row) {
const value = (cell || "").trim();
if (!value) {
emptyCells++;
} else if (/^-?\d+([.,]\d+)?%?$/.test(value)) {
// Número puro o porcentaje
numericCells++;
} else {
textCells++;
}
}
const totalNonEmpty = numericCells + textCells;
if (totalNonEmpty === 0) return false;
// Los encabezados son mayormente texto, no números
// Si >70% de las celdas no vacías son numéricas, probablemente son datos
if (numericCells / totalNonEmpty > 0.7) {
return false;
}
// Los encabezados no deberían estar mayormente vacíos
if (emptyCells / row.length > 0.7) {
return false;
}
return true;
}
Heurística 3: Qué Hace Que una Fila "Parezca Datos"
La verificación inversa ayuda a confirmar que encontramos el límite correcto:
function rowLooksLikeData(row) {
if (!row || row.length === 0) return false;
let numericCells = 0;
let dateCells = 0;
let totalNonEmpty = 0;
for (const cell of row) {
const value = (cell || "").trim();
if (!value) continue;
totalNonEmpty++;
// Verificar patrones numéricos
if (/^-?\d+([.,]\d+)?%?$/.test(value)) {
numericCells++;
}
// Verificar patrones de fecha
if (/^\d{1,4}[-/\.]\d{1,2}[-/\.]\d{1,4}$/.test(value)) {
dateCells++;
}
}
if (totalNonEmpty === 0) return false;
// Las filas de datos típicamente tienen contenido numérico o de fecha
const dataLikeCells = numericCells + dateCells;
return dataLikeCells / totalNonEmpty > 0.3;
}
Heurística 4: Detectar Encabezados de Columna Agrupados
Las tablas estilo FBREF tienen una fila de encabezado de grupo seguida por una fila de sub-encabezados. La fila de grupo tiene:
- Celdas vacías al principio (columnas sin grupos)
- Valores repetidos por expansión de colspan
- Múltiples valores únicos no vacíos (no solo uno como un título)
function detectGroupHeaderRow(row, nextRow) {
if (!row || !nextRow || row.length < 4) return false;
// Las filas de encabezado de grupo DEBEN tener celdas vacías al inicio
const firstCellEmpty = !(row[0] || "").trim();
if (!firstCellEmpty) return false;
// Contar valores únicos no vacíos
const uniqueValues = new Set(
row.filter(v => v && v.trim()).map(v => v.trim().toLowerCase())
);
// Una fila de título tiene exactamente UN valor único
// Una fila de encabezado de grupo debe tener MÚLTIPLES valores únicos
if (uniqueValues.size <= 1) return false;
// Contar repeticiones consecutivas (indica expansión de colspan)
let consecutiveRepeats = 0;
for (let i = 1; i < row.length; i++) {
const curr = (row[i] || "").trim();
const prev = (row[i - 1] || "").trim();
if (curr === prev) consecutiveRepeats++;
}
const repeatRatio = consecutiveRepeats / (row.length - 1);
// Ratio alto de repetición (>30%) sugiere expansión de colspan
// La siguiente fila debería tener más valores únicos (los sub-encabezados reales)
const nextUniqueValues = new Set(
nextRow.filter(v => v && v.trim()).map(v => v.trim().toLowerCase())
);
return repeatRatio > 0.3 && nextUniqueValues.size > uniqueValues.size;
}
Heurística 5: Limpiar Prefijos de Navegación de Wikipedia
Las plantillas de Wikipedia suelen tener prefijo con "v t e" (enlaces para ver/discusión/editar la plantilla):
function cleanWikipediaNavPrefix(text) {
if (!text) return text;
// Patrón 1: "v t e " al inicio (separados por espacios)
// Patrón 2: "v | t | e " (separados por pipes)
// Patrón 3: "[v] [t] [e] " (separados por corchetes)
return text
.replace(/^\s*v\s+t\s+e\s+/i, "")
.replace(/^\s*v\s*\|\s*t\s*\|\s*e\s+/i, "")
.replace(/^\s*\[v\]\s*\[t\]\s*\[e\]\s+/i, "")
.trim();
}
Uniendo Todo: El Algoritmo de Detección
function detectHeaderRowIndex(matrix) {
if (!matrix || matrix.length < 2) return 0;
const totalColumns = matrix[0]?.length || 0;
for (let i = 0; i < Math.min(matrix.length - 1, 5); i++) {
const currentRow = matrix[i];
const nextRow = matrix[i + 1];
// Saltar filas de título
if (isTitleRow(currentRow, totalColumns)) {
continue;
}
// Verificar encabezados agrupados (dos niveles)
if (detectGroupHeaderRow(currentRow, nextRow)) {
// La fila de sub-encabezados (i+1) es el encabezado real
return i + 1;
}
// Verificar si esta fila parece encabezados y la siguiente parece datos
if (rowLooksLikeHeaders(currentRow) && rowLooksLikeData(nextRow)) {
return i;
}
}
// Fallback: asumir que la fila 0 es el encabezado
return 0;
}
Testing en el Mundo Real
Estas heurísticas se desarrollaron probando contra:
- Tablas de países/población de Wikipedia (filas de título + prefijos "v t e")
- Estadísticas de jugadores de FBREF (encabezados agrupados)
- Tablas financieras con filas de unidad
- Tablas de datos gubernamentales con múltiples niveles de encabezado
Ninguna heurística es perfecta. El objetivo es manejar los patrones comunes correctamente y fallar graciosamente en tablas inusuales.
Cuando la Detección Falla
Para tablas que no encajan en patrones comunes, proporciona una opción de override manual:
function extractTable(matrix, options = {}) {
const headerRowIndex = options.headerRowIndex ?? detectHeaderRowIndex(matrix);
const headerRow = matrix[headerRowIndex];
const dataRows = matrix.slice(headerRowIndex + 1);
return { headerRow, dataRows };
}
Los usuarios que conocen sus datos pueden especificar la fila de encabezado explícitamente.
Resumen
| Patrón | Método de Detección |
|---|---|
| Fila de título | Celda única con colspan grande |
| Encabezado estándar | Fila con mayormente texto, seguida de fila con números |
| Encabezados agrupados | Primeras celdas vacías + valores repetidos + más valores únicos en la siguiente fila |
| Navegación Wikipedia | Patrón de prefijo "v t e" |
La idea clave: los encabezados y los datos tienen características diferentes. Los encabezados son ricos en texto con etiquetas descriptivas. Los datos son ricos en números con valores reales. El límite entre ellos generalmente es detectable.
Para más sobre los desafíos específicos de las tablas de Wikipedia, consulta nuestra guía sobre exportar tablas de Wikipedia a Excel.
¿Necesitas detección automática de encabezados sin escribir código? Conoce más en gauchogrid.com/es/html-table-exporter o pruébalo gratis en la Chrome Web Store.
Top comments (0)