14 KiB
Esquema físico do banco dfimoveis.sqlite3
1. Escopo e identificação do snapshot
Este documento descreve o esquema observado em 13 de julho de 2026. Ele não altera o modelo conceitual de docs/DATA_MODEL.md e não atribui significado não comprovado às colunas.
| Item | Valor |
|---|---|
| Caminho | dfimoveis_data/dfimoveis.sqlite3 |
| Tamanho | 5.017.600 bytes |
| SHA-256 | 1ce3128b488c50c993d7328ac6e24b5905f083a7e04e3f636407eea1edd02b9d |
| Modificação do arquivo | 2026-07-13 14:20:34.601285975 -03:00 |
| SQLite | 3.45.1 |
| Codificação | UTF-8 |
journal_mode persistido |
WAL |
| Páginas | 1.225 páginas de 4.096 bytes; freelist_count = 0 |
user_version / application_id |
0 / 0 |
| Método de abertura | sqlite3 -readonly e URI mode=ro&immutable=1, sempre com PRAGMA query_only = ON |
Não havia arquivo -wal ou -shm nem processo com o banco aberto na verificação inicial. Isso permitiu tratar o conteúdo como snapshot imutável. PRAGMA quick_check retornou ok e PRAGMA foreign_key_check não retornou violações. Na validação final, o cliente SQLite havia criado os sidecars de runtime dfimoveis.sqlite3-shm (32.768 bytes) e dfimoveis.sqlite3-wal (vazio), comportamento possível ao abrir em leitura um banco configurado em WAL. Eles não foram removidos. O arquivo principal manteve tamanho, mtime e SHA-256 idênticos.
2. Visão geral
O banco possui cinco tabelas da aplicação e a tabela interna sqlite_sequence. Não há views nem triggers. Os quatro índices existentes são autoíndices criados por chaves primárias ou restrições UNIQUE.
erDiagram
listings ||--|{ listing_history : "listing_id"
listings ||--|{ listing_searches : "listing_id"
listings ||--|{ photos : "listing_id"
crawl_runs {
INTEGER id PK
TEXT started_at
TEXT finished_at
TEXT status
TEXT stats_json
}
listings {
TEXT listing_id PK
TEXT url
REAL price_brl
REAL area_m2
TEXT first_seen_at
TEXT last_seen_at
TEXT content_hash
}
listing_history {
INTEGER id PK
TEXT listing_id FK
TEXT captured_at
TEXT content_hash
TEXT data_json
}
listing_searches {
TEXT listing_id PK,FK
TEXT search_url PK
TEXT first_seen_at
TEXT last_seen_at
}
photos {
TEXT listing_id PK,FK
TEXT source_url PK
INTEGER ordinal
TEXT sha256
TEXT local_path
}
crawl_runs não possui relacionamento físico com anúncios ou snapshots. Portanto, a execução que gerou uma linha só pode ser inferida pelos horários, não recuperada por uma chave.
Contagens e cardinalidades observadas
| Tabela | Linhas | Interpretação física |
|---|---|---|
crawl_runs |
4 | Execuções do coletor |
listings |
156 | Identidades de anúncios e seu estado corrente |
listing_history |
156 | Estados distintos dos anúncios por hash |
listing_searches |
156 | Vínculos entre anúncio e URL de busca |
photos |
5.386 | Mídias associadas diretamente aos anúncios |
sqlite_sequence |
2 | Sequências internas de tabelas AUTOINCREMENT |
No snapshot avaliado:
- cada anúncio possui exatamente um registro em
listing_history; - cada anúncio está ligado a exatamente uma URL de busca;
- cada anúncio possui de 5 a 61 linhas em
photos; - não há órfãos nas três relações com
listings; - os 156 hashes de
listing_historycoincidem com o hash corrente emlistings; captured_atcoincide comfirst_seen_atnos 156 anúncios.
3. Dicionário de tabelas
3.1 crawl_runs
Representa execuções técnicas do coletor. Não registra fonte, versão do scraper, configuração, texto de erro ou relacionamento direto com os itens coletados.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação |
|---|---|---|---|---|
id |
INTEGER | Não | PK, AUTOINCREMENT |
Identificador da execução; a PK inteira atribui um valor quando omitido |
started_at |
TEXT | Não | — | Início em ISO 8601, observado em UTC |
finished_at |
TEXT | Sim | — | Término em ISO 8601 |
status |
TEXT | Não | — | Estado textual da execução; somente ok na amostra |
stats_json |
TEXT | Não | '{}' |
Contadores técnicos em JSON |
As quatro estruturas JSON são válidas. As chaves presentes são search_pages, listing_urls, details_ok, details_failed, changed, photos_ok, photos_failed e inactive, todas inteiras.
3.2 listings
Representa a identidade do anúncio no portal e uma cópia mutável de seu estado mais recente. Não representa um imóvel físico deduplicado.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação e ressalvas |
|---|---|---|---|---|
listing_id |
TEXT | Sim no DDL; não observado | PK | Identificador do anúncio no portal; todos os valores atuais têm 6–7 dígitos. Por peculiaridade do SQLite, PK textual sem NOT NULL explícito merece validação na aplicação |
url |
TEXT | Não | — | URL do anúncio |
title |
TEXT | Sim | — | Título extraído |
description |
TEXT | Sim | — | Descrição curta extraída; não é o HTML bruto completo |
transaction_type |
TEXT | Sim | — | Tipo da transação; somente venda no snapshot |
property_type |
TEXT | Sim | — | Tipo anunciado; apartamento ou casa |
price_brl |
REAL | Sim | — | Preço anunciado em reais; sujeito a erros de escala |
condominium_brl |
REAL | Sim | — | Condomínio anunciado; periodicidade não está formalizada |
iptu_brl |
REAL | Sim | — | IPTU anunciado; periodicidade não está formalizada |
area_m2 |
REAL | Sim | — | Uma única área em m², sem indicar se é útil, privativa, construída, total ou terreno |
bedrooms |
INTEGER | Sim | — | Quartos anunciados, não a planta original comprovada |
suites |
INTEGER | Sim | — | Suítes anunciadas |
parking_spaces |
INTEGER | Sim | — | Vagas anunciadas |
address |
TEXT | Sim | — | Texto de endereço; fortemente contaminado pela página na coleta atual |
neighborhood |
TEXT | Sim | — | Bairro; fortemente contaminado pela página na coleta atual |
city |
TEXT | Sim | — | Cidade; 100% nula atualmente |
state |
TEXT | Sim | — | UF; 100% nula atualmente |
advertiser_name |
TEXT | Sim | — | Nome do anunciante; 100% nulo atualmente |
advertiser_code |
TEXT | Sim | — | Código do anunciante; parte relevante dos valores está contaminada |
creci |
TEXT | Sim | — | Registro do anunciante; parte relevante dos valores está contaminada |
latitude |
REAL | Sim | — | Latitude; 100% nula atualmente |
longitude |
REAL | Sim | — | Longitude; 100% nula atualmente |
published_at |
TEXT | Sim | — | Data publicada pelo portal; 100% nula atualmente |
first_seen_at |
TEXT | Não | — | Primeira observação do anúncio |
last_seen_at |
TEXT | Não | — | Última observação do anúncio |
inactive_at |
TEXT | Sim | — | Momento de inativação inferida pelo coletor; 100% nulo atualmente |
content_hash |
TEXT | Sim | — | SHA-256 do conteúdo normalizado usado para detectar mudança |
raw_html_path |
TEXT | Sim | — | Caminho relativo para o HTML bruto do anúncio |
extra_json |
TEXT | Não | '{}' |
JSON adicional; contém apenas json_ld, um array vazio nos 156 anúncios |
Exemplo não sensível de identidade: o anúncio 1026522 aponta para uma URL sob www.dfimoveis.com.br e para um HTML em raw/listing/1026522/...html.gz.
3.3 listing_history
Representa estados distintos de um anúncio, não cada tentativa ou observação do coletor.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação |
|---|---|---|---|---|
id |
INTEGER | Não | PK, AUTOINCREMENT |
Identificador do estado histórico; a PK inteira atribui um valor quando omitido |
listing_id |
TEXT | Não | FK | Anúncio ao qual o estado pertence |
captured_at |
TEXT | Não | — | Momento em que o estado foi capturado |
content_hash |
TEXT | Não | UNIQUE com listing_id |
Hash que impede repetir um estado idêntico para o anúncio |
data_json |
TEXT | Não | — | Snapshot JSON dos principais campos extraídos |
data_json é JSON válido em todas as 156 linhas. Ele contém os campos correntes de conteúdo, mas não contém first_seen_at, last_seen_at, inactive_at, raw_html_path ou uma referência à execução. Como a restrição é UNIQUE(listing_id, content_hash), observações repetidas sem mudança não geram novos snapshots.
3.4 listing_searches
Tabela de associação entre anúncio e consulta de busca que o encontrou.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação |
|---|---|---|---|---|
listing_id |
TEXT | Não | PK parcial, FK | Anúncio |
search_url |
TEXT | Não | PK parcial | URL/configuração da busca |
first_seen_at |
TEXT | Não | — | Primeira observação nessa busca |
last_seen_at |
TEXT | Não | — | Última observação nessa busca |
A chave primária composta é (listing_id, search_url). Há duas URLs distintas no banco, uma para apartamentos de três quartos no Cruzeiro Novo e outra para casas anunciadas com três ou quatro quartos no Jardins Mangueiral.
3.5 photos
Representa arquivos de mídia associados diretamente ao anúncio. A tabela não preserva associação por snapshot.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação |
|---|---|---|---|---|
listing_id |
TEXT | Não | PK parcial, FK | Anúncio |
ordinal |
INTEGER | Não | — | Ordem da imagem no anúncio |
source_url |
TEXT | Não | PK parcial | URL de origem da imagem |
sha256 |
TEXT | Sim | — | Hash do arquivo baixado |
mime_type |
TEXT | Sim | — | Tipo MIME detectado |
bytes |
INTEGER | Sim | — | Tamanho do arquivo |
local_path |
TEXT | Sim | — | Caminho relativo sob dfimoveis_data/ |
first_seen_at |
TEXT | Não | — | Primeira observação da mídia |
last_seen_at |
TEXT | Não | — | Última observação da mídia |
A chave primária é (listing_id, source_url), e não (listing_id, ordinal). Não há ordinais repetidos dentro de um anúncio na amostra atual. Exemplo de caminho: photos/1026522/001_9d7f11205f0a4a3f.jpg.
3.6 sqlite_sequence
Tabela interna do SQLite que mantém as sequências AUTOINCREMENT de crawl_runs e listing_history. Não é entidade do domínio.
4. Chaves, índices e integridade referencial
| Índice | Tabela | Origem | Colunas lógicas |
|---|---|---|---|
sqlite_autoindex_listings_1 |
listings |
PK | listing_id |
sqlite_autoindex_listing_history_1 |
listing_history |
UNIQUE |
listing_id, content_hash |
sqlite_autoindex_listing_searches_1 |
listing_searches |
PK | listing_id, search_url |
sqlite_autoindex_photos_1 |
photos |
PK | listing_id, source_url |
As FKs de listing_history, listing_searches e photos apontam para listings(listing_id), com NO ACTION para atualização e exclusão. Não há índices explícitos por data, preço, região ou hash de foto. Com o volume atual isso não é um problema operacional; se a base crescer, índices analíticos devem ser criados apenas em uma camada derivada ou recomendados para uma migração aprovada, nunca adicionados ao banco original durante análise.
5. Correspondência com o modelo conceitual
| Conceito desejado | Implementação física | Avaliação |
|---|---|---|
| Fonte | Ausente; DF Imóveis é implícito nas URLs | Não permite múltiplas fontes com identidade segura |
| Execução de coleta | crawl_runs |
Parcial; faltam fonte, configuração, versão e relação com itens |
| Anúncio | listings |
Presente; estado corrente e identidade estão na mesma linha |
| Observação histórica | listing_history |
Parcial; guarda somente conteúdo alterado, sem run_id |
| Imóvel físico candidato | Ausente | Nenhuma deduplicação física é persistida |
| Localização normalizada | Colunas em listings |
Insuficiente e atualmente contaminada |
| Mídia | photos |
Parcial; vinculada ao anúncio, não ao snapshot |
| Origem por consulta | listing_searches |
Presente como relação anúncio–URL de busca |
6. Decisões de interpretação e ambiguidades
listing_ididentifica um anúncio no portal, não um imóvel físico.- Uma linha de
listing_historyé um estado de conteúdo distinto, não prova de que o anúncio foi observado somente uma vez. Observações idênticas são condensadas emlast_seen_at. - O banco ainda não contém histórico suficiente para preços ou permanência: há um snapshot por anúncio e todos os anúncios têm
first_seen_at = last_seen_at. - Anúncios diferentes com preço, área ou fotos semelhantes permanecem anúncios distintos. Não há chave de endereço/unidade nem tabela de candidatos físicos.
area_m2não deve ser usada para preço por m² até que o conceito de área seja validado por tipologia e fonte.bedroomsé a quantidade anunciada. Ela não comprova a planta original de três quartos exigida para Jardins Mangueiral.- Datas com sufixo
+00:00são tratadas como UTC. A cobertura observada é de um único dia. - Os campos textuais contaminados devem ser reextraídos do HTML bruto em uma camada derivada; os valores atuais não devem ser silenciosamente normalizados como se fossem corretos.
7. Problemas conhecidos do snapshot
- 146 de 156 endereços têm mais de 300 caracteres; 154 de 156 bairros têm mais de 100 caracteres. Os dois bairros curtos também não formam valores confiáveis.
city,state,latitude,longitude,published_ateadvertiser_nameestão integralmente ausentes.- 98 códigos de anunciante e 153 valores de CRECI são longos demais para o significado esperado, indicando extração contaminada.
- Há um preço de R$ 570.000.000 e uma área de 0,11 m², ambos candidatos fortes a erro de extração/escala.
- O JSON-LD reservado em
extra_jsoné um array vazio em todos os anúncios e não oferece uma fonte estruturada alternativa no snapshot. - Três downloads de foto falharam e deixaram linhas sem hash, MIME, tamanho e caminho local.
- Hashes de foto idênticos aparecem em muitos anúncios, inclusive um hash presente nos 156; isso demonstra a presença de ativos genéricos e impede usar igualdade de hash como prova isolada de imóvel duplicado.
- Não há como ligar formalmente anúncio/snapshot a
crawl_runs, nem distinguir por chave a fonte se uma segunda fonte for incorporada.