A primeira linha de uma tabela HTML é a linha de cabeçalho.
Exceto quando não é.
Tabelas da Wikipedia frequentemente têm uma linha de título que abrange todas as colunas antes dos cabeçalhos reais. Sites de estatísticas esportivas têm cabeçalhos agrupados onde "Tempo de Jogo" abrange múltiplas sub-colunas como "PJ", "Titular", "Min". Tabelas financeiras têm linhas de unidade ("em milhões USD") que parecem cabeçalhos mas não são.
Se você assume que a linha 0 é sempre o cabeçalho, suas exportações estarão quebradas para uma parte significativa das tabelas do mundo real.
Veja como detectar a linha de cabeçalho real programaticamente.
O Problema: Três Tipos de "Primeiras Linhas"
Considere estes padrões comuns:
Padrão 1: Linha de Título
<table>
<tr>
<th colspan="4">População Mundial por País</th> <!-- Título, não cabeçalho -->
</tr>
<tr>
<th>Posição</th>
<th>País</th>
<th>População</th>
<th>% do Mundo</th>
</tr>
<tr>
<td>1</td>
<td>Índia</td>
<td>1.428.627.663</td>
<td>17,85%</td>
</tr>
</table>
A linha 0 é um título. A linha 1 é o cabeçalho. A linha 2+ são dados.
Padrão 2: Cabeçalhos Agrupados (Dois Níveis)
<table>
<tr>
<th></th>
<th></th>
<th colspan="3">Tempo de Jogo</th>
<th colspan="2">Desempenho</th>
</tr>
<tr>
<th>Jogador</th>
<th>Nação</th>
<th>PJ</th>
<th>Titular</th>
<th>Min</th>
<th>Gols</th>
<th>Ast</th>
</tr>
<tr>
<td>João Silva</td>
<td>BRA</td>
<td>34</td>
<td>30</td>
<td>2700</td>
<td>12</td>
<td>8</td>
</tr>
</table>
A linha 0 são cabeçalhos de grupo. A linha 1 são os cabeçalhos de coluna reais. A linha 2+ são dados.
Padrão 3: Prefixo de Navegação da Wikipedia
<tr>
<th colspan="3">v t e Patrimônios Mundiais da UNESCO</th>
</tr>
Os links "v t e" (ver/discutir/editar) são a navegação de template da Wikipedia. Eles precisam ser removidos, e a linha ainda pode ser um título em vez de cabeçalho.
Heurística 1: Detectando Linhas de Título
Uma linha de título tipicamente tem:
- Uma única célula (ou muito poucas)
- Colspan grande abrangendo a maioria/todas as colunas
- Conteúdo de texto que parece um título, não nomes de colunas
function isTitleRow(row, totalColumns) {
if (!row || row.length === 0) return false;
// Contar células não vazias
const nonEmptyCells = row.filter(cell => cell && cell.trim()).length;
// Linhas de título geralmente têm 1-2 células não vazias
if (nonEmptyCells > 2) return false;
// Verificar se primeira célula abrange a maioria das colunas (indica colspan)
// Em uma matriz normalizada, isso aparece como valores repetidos
const firstValue = row[0];
const repeatedCount = row.filter(cell => cell === firstValue).length;
// Se o primeiro valor se repete em >50% das colunas, provavelmente é um título colspan
if (repeatedCount > totalColumns * 0.5) {
return true;
}
return false;
}
Heurística 2: O Que Faz Uma Linha "Parecer Cabeçalhos"
Linhas de cabeçalho têm características que as distinguem de linhas de dados:
function rowLooksLikeHeaders(row) {
if (!row || row.length === 0) return false;
// Se dominada por números puros, não é cabeçalho
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 ou porcentagem
numericCells++;
} else {
textCells++;
}
}
const totalNonEmpty = numericCells + textCells;
if (totalNonEmpty === 0) return false;
// Cabeçalhos são principalmente texto, não números
// Se >70% das células não vazias são numéricas, provavelmente são dados
if (numericCells / totalNonEmpty > 0.7) {
return false;
}
// Cabeçalhos não devem ser principalmente vazios
if (emptyCells / row.length > 0.7) {
return false;
}
return true;
}
Heurística 3: O Que Faz Uma Linha "Parecer Dados"
A verificação inversa ajuda a confirmar que encontramos a fronteira correta:
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 padrões numéricos
if (/^-?\d+([.,]\d+)?%?$/.test(value)) {
numericCells++;
}
// Verificar padrões de data
if (/^\d{1,4}[-/\.]\d{1,2}[-/\.]\d{1,4}$/.test(value)) {
dateCells++;
}
}
if (totalNonEmpty === 0) return false;
// Linhas de dados tipicamente têm conteúdo numérico ou de data
const dataLikeCells = numericCells + dateCells;
return dataLikeCells / totalNonEmpty > 0.3;
}
Heurística 4: Detectando Cabeçalhos de Coluna Agrupados
Tabelas estilo FBREF têm uma linha de cabeçalho de grupo seguida por uma linha de sub-cabeçalho. A linha de grupo tem:
- Células vazias no início (colunas sem grupos)
- Valores repetidos da expansão de colspan
- Múltiplos valores únicos não vazios (não apenas um como um título)
function detectGroupHeaderRow(row, nextRow) {
if (!row || !nextRow || row.length < 4) return false;
// Linhas de cabeçalho de grupo DEVEM ter células vazias no início
// Isso distingue de tabelas duplicadas horizontalmente
const firstCellEmpty = !(row[0] || "").trim();
if (!firstCellEmpty) return false;
// Contar valores únicos não vazios
const uniqueValues = new Set(
row.filter(v => v && v.trim()).map(v => v.trim().toLowerCase())
);
// Uma linha de título tem exatamente UM valor único
// Uma linha de cabeçalho de grupo deve ter MÚLTIPLOS valores únicos
if (uniqueValues.size <= 1) return false;
// Contar repetições consecutivas (indica expansão 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);
// Alta taxa de repetição (>30%) sugere expansão de colspan
// A próxima linha deve ter mais valores únicos (os sub-cabeçalhos reais)
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: Limpando Prefixos de Navegação da Wikipedia
Templates da Wikipedia frequentemente prefixam conteúdo com "v t e" (links para ver/discutir/editar o template):
function cleanWikipediaNavPrefix(text) {
if (!text) return text;
// Padrão 1: "v t e " no início (separado por espaço)
// Padrão 2: "v | t | e " (separado por pipe)
// Padrão 3: "[v] [t] [e] " (separado por colchetes)
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();
}
Juntando Tudo: O Algoritmo de Detecção
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];
// Pular linhas de título
if (isTitleRow(currentRow, totalColumns)) {
continue;
}
// Verificar cabeçalhos agrupados (dois níveis)
if (detectGroupHeaderRow(currentRow, nextRow)) {
// A linha de sub-cabeçalho (i+1) é o cabeçalho real
return i + 1;
}
// Verificar se esta linha parece cabeçalho e a próxima parece dados
if (rowLooksLikeHeaders(currentRow) && rowLooksLikeData(nextRow)) {
return i;
}
}
// Fallback: assumir linha 0 como cabeçalho
return 0;
}
Testes no Mundo Real
Essas heurísticas foram desenvolvidas testando com:
- Tabelas de países/população da Wikipedia (linhas de título + prefixos "v t e")
- Estatísticas de jogadores do FBREF (cabeçalhos agrupados)
- Tabelas financeiras com linhas de unidade
- Tabelas de dados governamentais com múltiplos níveis de cabeçalho
Nenhuma heurística é perfeita. O objetivo é lidar corretamente com os padrões comuns e falhar graciosamente em tabelas incomuns.
Quando a Detecção Falha
Para tabelas que não se encaixam nos padrões comuns, forneça uma opção 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 };
}
Usuários que conhecem seus dados podem especificar a linha de cabeçalho explicitamente.
Resumo
| Padrão | Método de Detecção |
|---|---|
| Linha de título | Célula única com colspan grande |
| Cabeçalho padrão | Linha com mais texto, seguida por linha com números |
| Cabeçalhos agrupados | Primeiras células vazias + valores repetidos + mais valores únicos na próxima linha |
| Navegação Wikipedia | Padrão de prefixo "v t e" |
O insight-chave: cabeçalhos e dados têm características diferentes. Cabeçalhos são pesados em texto com rótulos descritivos. Dados são pesados em números com valores reais. A fronteira entre eles geralmente é detectável.
Para saber mais sobre os desafios específicos das tabelas da Wikipedia, veja nosso guia sobre scraper de tabelas HTML para Chrome.
Precisa de detecção automática de cabeçalho sem escrever código? Saiba mais em gauchogrid.com/pt-br/html-table-exporter ou experimente gratuitamente na Chrome Web Store.