Você encontrou dados em um site. Precisa deles no seu banco de dados. O fluxo geralmente envolve: exportar para CSV, importar na ferramenta SQL, lidar com incompatibilidades de tipo, corrigir problemas de encoding.
E se você pudesse ir direto da tabela HTML para SQL válido?
Este guia mostra como gerar instruções CREATE TABLE e INSERT INTO a partir de qualquer tabela web, lidando com inferência de tipos, sanitização de identificadores e escape adequado. Esta é a abordagem que uso no HTML Table Exporter.
O Resultado Final
De uma tabela como esta:
| Nome do Produto | Preço | Em Estoque |
|-----------------|--------|------------|
| Widget Pro | 29.99 | Sim |
| Gadget Basic | 14.50 | Não |
Gerar:
-- Exportado pelo HTML Table Exporter PRO
CREATE TABLE produtos (
nome_do_produto TEXT,
preco REAL,
em_estoque TEXT
);
INSERT INTO produtos (nome_do_produto, preco, em_estoque) VALUES
('Widget Pro', 29.99, 'Sim'),
('Gadget Basic', 14.50, 'Não');
Vamos construir isso passo a passo.
Passo 1: Extrair a Matriz da Tabela
Primeiro, extrair os dados brutos da tabela HTML:
function extractTableMatrix(table) {
const rows = Array.from(table.rows);
const grid = [];
rows.forEach((rowEl, rowIndex) => {
if (!grid[rowIndex]) grid[rowIndex] = [];
let colIndex = 0;
Array.from(rowEl.cells).forEach(cell => {
while (grid[rowIndex][colIndex] !== undefined) colIndex++;
const text = cell.textContent.trim();
const rowSpan = parseInt(cell.rowSpan) || 1;
const colSpan = parseInt(cell.colSpan) || 1;
for (let r = 0; r < rowSpan; r++) {
if (!grid[rowIndex + r]) grid[rowIndex + r] = [];
for (let c = 0; c < colSpan; c++) {
grid[rowIndex + r][colIndex + c] = text;
}
}
colIndex += colSpan;
});
});
return grid;
}
Saída: Um array 2D onde rows[0] são cabeçalhos e rows[1+] são dados.
Passo 2: Sanitizar Nomes de Colunas
Identificadores SQL têm regras estritas. Cabeçalhos como "Nome do Produto" ou "Preço (R$)" precisam de sanitização:
function sanitizeSqlIdentifier(header, fallbackIndex) {
let id = (header || "").toString().trim();
if (!id) {
return `col_${fallbackIndex + 1}`;
}
// Normalizar unicode (remover acentos)
id = id.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
// Converter para snake_case minúsculo
id = id
.toLowerCase()
.replace(/[^a-z0-9]+/g, "_")
.replace(/^_+|_+$/g, "");
// Garantir que não começa com número
if (/^\d/.test(id)) {
id = `col_${id}`;
}
// Fallback se vazio após sanitização
if (!id) {
return `col_${fallbackIndex + 1}`;
}
return id;
}
// Exemplos:
// "Nome do Produto" → "nome_do_produto"
// "Preço (R$)" → "preco_r"
// "2024 Receita" → "col_2024_receita"
// "" → "col_1"
Passo 3: Tratar Nomes de Colunas Duplicados
Tabelas às vezes têm cabeçalhos duplicados. SQL exige nomes de colunas únicos:
function makeUniqueColumnNames(headers) {
const used = new Set();
return headers.map((header, idx) => {
let name = sanitizeSqlIdentifier(header, idx);
let candidate = name;
let counter = 1;
while (used.has(candidate)) {
candidate = `${name}_${counter}`;
counter++;
}
used.add(candidate);
return candidate;
});
}
// ["Nome", "Nome", "Valor"] → ["nome", "nome_1", "valor"]
Passo 4: Inferir Tipos SQL
Analisar dados para determinar os tipos SQL apropriados:
function inferSqlColumnTypes(rows, headerRowIndex = 0) {
const headerRow = rows[headerRowIndex] || [];
const dataRows = rows.slice(headerRowIndex + 1);
const types = new Array(headerRow.length).fill("TEXT");
for (let col = 0; col < headerRow.length; col++) {
// Amostrar até 50 valores por coluna
const values = [];
for (let r = 0; r < Math.min(dataRows.length, 50); r++) {
const cell = dataRows[r][col];
const v = cell != null ? String(cell).trim() : "";
if (v !== "") values.push(v);
}
if (values.length === 0) {
types[col] = "TEXT";
continue;
}
// Verificar se todos os valores correspondem a um tipo
let allInt = true;
let allNumeric = true;
for (const v of values) {
if (!/^[-+]?\d+$/.test(v)) {
allInt = false;
}
if (!/^[-+]?\d+([.,]\d+)?$/.test(v)) {
allNumeric = false;
}
}
if (allInt) {
types[col] = "INTEGER";
} else if (allNumeric) {
types[col] = "REAL";
} else {
types[col] = "TEXT";
}
}
return types;
}
Prioridade de tipos: INTEGER > REAL > TEXT
Se 100% dos valores não-vazios são inteiros, usa INTEGER. Se são todos numéricos (incluindo decimais), usa REAL. Caso contrário, TEXT.
Passo 5: Escapar Valores Corretamente
Injeção SQL não é apenas uma preocupação de segurança — corrompe seus dados. Escape adequado:
function sqlEscapeValue(raw, type) {
// Tratamento de NULL
if (raw == null) return "NULL";
const v = String(raw).trim();
if (v === "") return "NULL";
// Tipos numéricos
if (type === "INTEGER" || type === "REAL") {
// Normalizar separador decimal
const normalized = v.replace(",", ".");
const num = Number(normalized);
if (!Number.isNaN(num) && Number.isFinite(num)) {
return normalized;
}
// Cair para TEXT se não for um número válido
}
// TEXT: escapar aspas simples duplicando-as
const escaped = v.replace(/'/g, "''");
return `'${escaped}'`;
}
// Exemplos:
// sqlEscapeValue("Olá", "TEXT") → "'Olá'"
// sqlEscapeValue("It's good", "TEXT") → "'It''s good'"
// sqlEscapeValue("29.99", "REAL") → "29.99"
// sqlEscapeValue("", "TEXT") → "NULL"
// sqlEscapeValue(null, "TEXT") → "NULL"
Passo 6: Gerar o SQL
Juntando tudo:
function tableToSql(tableInfo) {
const rows = tableInfo.rows || [];
if (rows.length < 2) return "";
const headerRowIndex = tableInfo.headerRowIndex || 0;
const headerRow = rows[headerRowIndex];
const dataRows = rows.slice(headerRowIndex + 1);
if (!headerRow || headerRow.length === 0) return "";
// Gerar nomes de colunas
const columnNames = makeUniqueColumnNames(headerRow);
// Inferir tipos
const types = inferSqlColumnTypes(rows, headerRowIndex);
// Gerar nome da tabela
const rawTableName = tableInfo.name || tableInfo.slug || "tabela";
const tableName = sanitizeSqlIdentifier(rawTableName, 0) || "tabela_exportada";
// Instrução CREATE TABLE
const createLines = columnNames.map((col, i) =>
` ${col} ${types[i] || "TEXT"}`
);
const createStmt = `CREATE TABLE ${tableName} (\n${createLines.join(",\n")}\n);`;
// Instruções INSERT
const insertHeader = `INSERT INTO ${tableName} (${columnNames.join(", ")}) VALUES`;
const valueLines = dataRows.map(row => {
const values = columnNames.map((_, i) => {
const cell = row[i];
const type = types[i] || "TEXT";
return sqlEscapeValue(cell, type);
});
return ` (${values.join(", ")})`;
});
// Combinar
let sql = `-- Exportado pelo HTML Table Exporter\n\n${createStmt}\n\n`;
if (valueLines.length > 0) {
sql += `${insertHeader}\n${valueLines.join(",\n")};\n`;
}
return sql;
}
Exemplo Completo
Tabela de entrada:
const tableInfo = {
name: "Vendas Q1",
rows: [
["Produto", "Unid. Vendidas", "Receita (R$)", "Lucrativo"],
["Widget A", "1.234", "45.678,90", "Sim"],
["Widget B", "567", "12.345,67", "Não"],
["Gadget X", "890", "23.456,78", "Sim"]
]
};
Saída:
-- Exportado pelo HTML Table Exporter
CREATE TABLE vendas_q1 (
produto TEXT,
unid_vendidas INTEGER,
receita_r REAL,
lucrativo TEXT
);
INSERT INTO vendas_q1 (produto, unid_vendidas, receita_r, lucrativo) VALUES
('Widget A', 1234, 45678.90, 'Sim'),
('Widget B', 567, 12345.67, 'Não'),
('Gadget X', 890, 23456.78, 'Sim');
Observe:
- Nomes de colunas sanitizados (
Receita (R$)→receita_r) - Números detectados e sem aspas
- Vírgulas nos números tratadas
- Nome da tabela vindo dos metadados
Tratando Casos Extremos
Valores NULL
Muitas tabelas web representam dados ausentes de formas diferentes:
const NULL_PATTERNS = ["N/A", "n/a", "N/D", "-", "--", "null", "none", "nenhum", "."];
function sqlEscapeValue(raw, type, nullPatterns = NULL_PATTERNS) {
if (raw == null) return "NULL";
const v = String(raw).trim();
if (v === "" || nullPatterns.includes(v.toLowerCase())) {
return "NULL";
}
// ... resto da função
}
Grandes Conjuntos de Dados
Para tabelas com milhares de linhas, divida os INSERTs em lotes:
function tableToSqlBatched(tableInfo, batchSize = 1000) {
// ... mesma configuração ...
const batches = [];
for (let i = 0; i < valueLines.length; i += batchSize) {
const batch = valueLines.slice(i, i + batchSize);
batches.push(`${insertHeader}\n${batch.join(",\n")};`);
}
return `${createStmt}\n\n${batches.join("\n\n")}`;
}
Sintaxe Específica por Banco de Dados
SQLite, PostgreSQL e MySQL têm diferenças sutis de sintaxe:
function tableToSql(tableInfo, dialect = "sqlite") {
// ... mesma lógica de colunas/tipos ...
// Mapeamento de tipos específico por dialeto
const typeMap = {
sqlite: { INTEGER: "INTEGER", REAL: "REAL", TEXT: "TEXT" },
postgresql: { INTEGER: "INTEGER", REAL: "NUMERIC", TEXT: "TEXT" },
mysql: { INTEGER: "INT", REAL: "DECIMAL(10,2)", TEXT: "VARCHAR(255)" }
};
const dialectTypes = typeMap[dialect] || typeMap.sqlite;
const createLines = columnNames.map((col, i) =>
` ${col} ${dialectTypes[types[i]] || dialectTypes.TEXT}`
);
// ... resto da função ...
}
Bookmarklet para o Navegador
Solução rápida para uso ocasional:
javascript:(function(){
const table = document.querySelector("table");
if (!table) { alert("Nenhuma tabela encontrada"); return; }
const rows = Array.from(table.rows).map(r =>
Array.from(r.cells).map(c => c.textContent.trim())
);
const headers = rows[0].map((h, i) =>
(h || `col_${i+1}`).toLowerCase().replace(/[^a-z0-9]+/g, "_")
);
const values = rows.slice(1).map(row =>
"(" + row.map(v => `'${v.replace(/'/g, "''")}'`).join(", ") + ")"
).join(",\n");
const sql = `INSERT INTO table_data (${headers.join(", ")}) VALUES\n${values};`;
navigator.clipboard.writeText(sql);
alert("SQL copiado para a área de transferência!");
})();
Quando Usar Isso
Bom para:
- Importações rápidas de dados para SQLite/PostgreSQL
- Popular bancos de dados de teste
- Migrações de dados pontuais
- Aprender SQL por exemplos
Não ideal para:
- ETL em larga escala (use ferramentas próprias)
- Schemas complexos (chaves estrangeiras, constraints)
- Dados binários
Para pipelines de dados em produção, ferramentas ETL com validação são melhores. Para "preciso desta tabela no meu banco agora", geração direta de SQL é rápida.
Experimente Sem Código
Se não quer construir isso você mesmo, o HTML Table Exporter PRO gera SQL com um clique. A versão gratuita exporta para CSV/JSON/Excel; a PRO adiciona SQL, NDJSON e limpeza de dados.
Para saber qual é a melhor extensão Chrome para copiar tabelas para planilhas, confira nossa análise comparativa.
Saiba mais em gauchogrid.com/pt-br/html-table-exporter ou experimente na Chrome Web Store.
Para qual banco de dados você está importando dados web? Tenho curiosidade sobre os casos de uso.
Top comments (0)