Canarim Dataset: do Common Crawl a 342 milhões de documentos em português
A escassez de dados web em português, abertos e imediatamente consumíveis, motivou a criação do Canarim. Publiquei um corpus com 342.818.651 registros, distribuídos em 192 shards Apache Arrow e cerca de 1,09 TB. Sua principal contribuição é oferecer uma camada de aquisição e extração em larga escala que preserva mais estrutura documental do que texto plano.[1]
Na primeira versão, apliquei deduplicação por URL e converti HTML para Markdown com Trafilatura; ainda não implementei deduplicação textual nem filtragem abrangente de conteúdo potencialmente nocivo.[1] Por isso, apresento o Canarim como uma camada de aquisição e extração em larga escala, não como um corpus pronto para treinamento irrestrito.
| Propriedade | Valor publicado |
|---|---|
Registros no split train | 342.818.651 |
| Tamanho publicado | aproximadamente 1,09 TB |
| Shards de dados | 192 arquivos .arrow |
| Colunas | 7 |
| Formato | Apache Arrow streaming |
| DOI | 10.57967/hf/1605 |
Motivação e linhagem do corpus
O Common Crawl captura bilhões de recursos da web e publica respostas HTTP brutas em arquivos WARC, metadados derivados em WAT e texto simples em WET. Parti da camada WARC e preservei warc_filename, warc_record_offset e warc_record_length, além da URL e do instante de captura. Assim, mantive a ligação entre o Markdown distribuído e a captura arquivada, algo que um corpus contendo somente texto perderia.[2]
Renderizando diagrama...
Usei múltiplas iterações do Common Crawl, selecionei documentos em português, dedupliquei inicialmente por URL e converti o HTML em Markdown com Trafilatura.[1] Mantive no esquema os campos do URL Index do Common Crawl: content_languages vem da predição do CLD2, enquanto arquivo, offset e comprimento localizam o membro WARC comprimido.[3]
Na publicação inicial, não detalhei a lista completa de crawls, a consulta exata de seleção, os códigos HTTP e MIME aceitos, o limiar de identificação linguística, as regras de normalização de URL, a versão e os parâmetros do Trafilatura, as contagens intermediárias nem a política para escolher uma captura entre URLs repetidas.
Seleção dos crawls e identificação de idioma
No exemplo que publiquei, usei CC-MAIN-2023-06, capturado em fevereiro de 2023. Também informei que reuni múltiplas iterações, mas não preservei no card a lista completa de crawls.[1] O identificador de cada linha pode ser derivado de warc_filename, cujo prefixo segue crawl-data/CC-MAIN-AAAA-NN/, e então contado. Fazer isso sobre um prefixo do stream produz somente uma amostra ordenada; obter um censo exige percorrer os 342 milhões de registros.
Preservei content_languages=por, código ISO 639-3 para português, a partir do índice de origem, cujo campo languages é previsto pelo CLD2.[3] Não registrei a execução de um segundo identificador e não publiquei probabilidades nem matriz de confusão. Identificação automática erra em páginas curtas, multilíngues, boilerplate, português próximo do galego e texto com muito código. Trabalhos como CCNet mostram por que identificação de idioma, deduplicação e avaliação de qualidade precisam ser etapas mensuradas separadamente em corpora monolíngues.[4]
WARC, offsets e recuperação por intervalo
WARC é um formato de contêiner: cada registro concatena cabeçalhos WARC e um bloco arbitrário de octetos. Uma resposta web normalmente inclui cabeçalhos HTTP e o corpo recebido. O padrão exige campos como WARC-Type, WARC-Date, WARC-Record-ID e Content-Length; ele também admite digests, truncamento e registros revisit para conteúdo duplicado.[5]
No índice do Common Crawl, o trio filename + offset + length torna possível recuperar um registro, sem baixar um WARC de aproximadamente 1 GB. O intervalo inclusivo é:
inicio = warc_record_offset
fim = warc_record_offset + warc_record_length - 1
warc_record_length é o comprimento do membro comprimido apontado pelo índice, não o Content-Length do payload HTML. Um pedido HTTP com Range: bytes=inicio-fim deve retornar 206 Partial Content; o fragmento pode então ser descomprimido como gzip.[3]
import gzip
import requests
def recuperar_warc(registro: dict) -> bytes:
inicio = registro["warc_record_offset"]
fim = inicio + registro["warc_record_length"] - 1
url = "https://data.commoncrawl.org/" + registro["warc_filename"]
resposta = requests.get(
url,
headers={"Range": f"bytes={inicio}-{fim}", "Accept-Encoding": "identity"},
timeout=60,
)
if resposta.status_code != 206:
raise RuntimeError(f"Range não atendido: HTTP {resposta.status_code}")
return gzip.decompress(resposta.content)Esse mecanismo permite revisar a extração, verificar o encoding e reprocessar o conteúdo com outro extrator. O conteúdo recuperado é entrada não confiável: não se deve executar JavaScript, carregar recursos remotos ou renderizar HTML sem sandbox. Uma captura também pode estar truncada pelo crawler; o próprio padrão prevê WARC-Truncated, mas o esquema Canarim não expõe esse campo diretamente.[5]
Deduplicação por URL e por conteúdo
Descrevi a deduplicação publicada apenas como “using URLs”.[1] Não registrei se a chave era a string literal, uma URL normalizada ou uma forma SURT, nem se a operação ocorreu dentro de cada crawl ou globalmente. Também não registrei qual captura preservei quando a mesma URL apareceu em datas diferentes. Essas escolhas importam: preservar a captura mais recente favorece atualidade; preservar a primeira favorece corte temporal; manter todas preserva evolução, mas aumenta repetição.
Mesmo uma deduplicação perfeita por URL não detecta o mesmo artigo em domínios espelho, parâmetros de rastreamento, caminhos alternativos, versões para impressão ou páginas diferentes preenchidas por um template comum. Para uma futura deduplicação textual, a base matemática clássica é transformar cada documento normalizado em um conjunto de shingles de k tokens. Para documentos A e B, a similaridade de Jaccard é:
J(A, B) = |A ∩ B| / |A ∪ B|.
Calcular todas as comparações custa ordem quadrática e é inviável em 342 milhões de documentos. MinHash cria uma assinatura compacta cuja propriedade central é P[minhash(A) = minhash(B)] = J(A, B); várias permutações aproximam Jaccard, e LSH organiza as assinaturas em bandas para gerar apenas pares candidatos.[6] Um pipeline prudente faria hash exato primeiro, MinHash/LSH depois e registraria limiar, tamanho de shingle, normalização e regra de retenção. Deduplicação deve ocorrer antes da separação entre treino e avaliação para evitar vazamento. Lee et al. associaram near-duplicates a memorização, sobreposição com validação e treinamento menos eficiente; RefinedWeb demonstrou a relevância prática de filtragem e deduplicação em Common Crawl.[7][8]
Isso é uma recomendação para uma próxima versão, não uma alegação de que MinHash foi aplicado ao Canarim publicado.
Extração de HTML para Markdown
Trafilatura procura o conteúdo textual principal e separa-o de navegação, anúncios e outros elementos periféricos. Sua API oferece saídas TXT, Markdown, JSON, HTML e XML, controles de precisão/recall, inclusão de comentários, tabelas, links e formatação, além de deduplicação interna opcional. O artigo da ferramenta reporta avaliação comparativa em páginas reais; ainda assim, desempenho médio não garante acerto em todo gênero ou idioma.[9]
Configurei a extração para preservar títulos, subtítulos, negrito e itálico em Markdown e descartar comentários e informação não essencial.[10] No exemplo assets/extracted_text.md, extraído de uma reportagem do G1, preservei parágrafos, citações, legendas e cabeçalhos como ## Homenagens e ## Atraso; no preview, coloquei a página original ao lado do resultado. Esse é um caso selecionado, não uma amostra aleatória nem uma métrica de qualidade. Como não registrei versão, argumentos de chamada e conjunto de avaliação, ainda não consigo reproduzir exatamente toda a coluna text.
Uma medida útil para futuras versões é o rendimento de extração:
rendimento_documental = N(textos não vazios publicados) / N(candidatos WARC selecionados).
Também convém medir quanto conteúdo útil permanece depois da extração e quais tipos de página falham. Nenhum denominador intermediário foi publicado, portanto o rendimento não pode ser calculado para a versão atual.
Esquema e escala
| Campo | Tipo | Semântica e cautela |
|---|---|---|
url | string | URL capturada; não implica disponibilidade atual nem licença uniforme. |
content_languages | string | Código linguístico, demonstrado como por; classificação automática pode errar. |
warc_filename | string | Caminho relativo do WARC no Common Crawl e fonte do identificador do crawl. |
warc_record_offset | int64 | Primeiro byte do membro WARC comprimido. |
warc_record_length | int64 | Número de bytes a solicitar no range HTTP. |
text | string | Conteúdo principal convertido para Markdown. |
crawl_timestamp | string | Instante de captura em UTC; é string, não timestamp tipado no schema. |
O tamanho publicado de aproximadamente 1,09 TB descreve a tabela Arrow completa, não o volume do texto UTF-8 nem uma contagem de tokens. Documentos e shards variam de tamanho, e o streaming evita tratar esse total como requisito de memória.[1]
Armazenamento, streaming e Parquet
Os dados publicados são Arrow, formato colunar usado internamente pela biblioteca datasets. A documentação do Hugging Face distingue o streaming Arrow do formato IPC/Feather e explica que streaming=True produz um IterableDataset: registros chegam durante a iteração, sem materializar o terabyte inteiro localmente. Não há acesso aleatório barato; filter() é preguiçoso e, em Arrow, um predicado Python pode precisar ler todos os registros anteriores até encontrar resultados.[11]
Parquet seria uma alternativa interessante para uma republicação, não uma descrição do estado atual. Estatísticas por row group permitem predicate pushdown, e projeção de colunas pode evitar ler text quando a consulta quer apenas URLs ou crawls. A documentação de streaming do Hugging Face expõe columns e filters especificamente para Parquet.[11] A conversão, porém, exigiria armazenamento adicional, dimensionamento de row groups e uma versão documentada; simplesmente chamar Arrow de Parquet apagaria uma diferença operacional importante.
Carregamento e filtros reproduzíveis
Para reprodutibilidade, registre a versão da biblioteca e a data de acesso ao dataset. O exemplo abaixo limita a leitura a um shard. Ele ainda pode transferir blocos substanciais antes de produzir a primeira linha.
python -m pip install "datasets==4.8.4"from datasets import load_dataset
amostra = load_dataset(
"dominguesm/canarim",
split="train",
data_files="train/data-00000-of-00192.arrow",
streaming=True,
)
for registro in amostra.take(3):
print(registro["crawl_timestamp"], registro["url"])
print((registro["text"] or "")[:300])Para o stream completo, remova data_files. O filtro seguinte é deliberadamente uma política local de exploração, não um filtro atribuído à construção do Canarim. urlsplit() evita aceitar, por engano, hosts como g1.globo.com.exemplo.org.
from urllib.parse import urlsplit
from datasets import load_dataset
dados = load_dataset(
"dominguesm/canarim",
split="train",
streaming=True,
)
def exemplo_util(registro):
texto = (registro["text"] or "").strip()
host = (urlsplit(registro["url"]).hostname or "").lower()
dominio_ok = host == "g1.globo.com" or host.endswith(".g1.globo.com")
return registro["content_languages"] == "por" and dominio_ok and len(texto) >= 500
for registro in dados.filter(exemplo_util).take(10):
print(registro["url"], len(registro["text"]))O limite de 500 caracteres serve somente para tornar o exemplo verificável. Em pesquisa, registre a data de acesso, a versão da biblioteca, o código do filtro, a ordem dos shards, a seed e as contagens de entrada e saída. shuffle(seed=..., buffer_size=...) embaralha shards e uma janela, não cria uma permutação uniforme global; a escolha do buffer altera a amostragem observada.[11]
Controles de qualidade e relação com o Canarim-7B
Antes de usar o corpus em treinamento, recomendo que a pipeline consumidora meça, por domínio e crawl: texto vazio, comprimento em caracteres ou tokens, razão alfabética, repetição de linhas, boilerplate, encoding inválido, idioma por um segundo classificador, URLs dominantes e duplicatas exatas ou aproximadas. Deve ainda separar benchmarks e documentos de avaliação antes da deduplicação final. Esses controles são recomendações reproduzíveis inspiradas na literatura, não filtros inéditos atribuídos ao dataset.[4][7][8]
No Canarim-7B, usei 16 bilhões de tokens do subconjunto português de CC-MAIN-2023-23 para continuar o pré-treinamento do Llama 2 7B.[12] Esse corpus não é a publicação dominguesm/canarim de 342 milhões de registros. Os dois projetos compartilham a origem geral no Common Crawl, mas não apresento o dataset Canarim como corpus direto daquele treinamento.
Privacidade, toxicidade, direitos autorais e licença
Conteúdo acessível na web não é automaticamente livre de direitos nem seguro. Pode conter nomes, emails, telefones, endereços, credenciais acidentais, dados sensíveis, difamação, discurso de ódio, pornografia, desinformação e material envolvendo menores. A política do Common Crawl reconhece que coleta dados pessoais disponíveis publicamente, e seus termos exigem respeito a direitos autorais, privacidade e termos dos proprietários do conteúdo; também proíbem colher informação pessoal para uso separado do conteúdo rastreado.[13] Na primeira versão do Canarim, não registei remoção de PII, classificador de toxicidade, bloqueio de domínios ou mecanismo próprio de exclusão.
Também publiquei uma inconsistência de licença: declarei CC BY 4.0 nos metadados e no card do Hugging Face, mas incluí CC BY-SA 4.0 no arquivo LICENSE do GitHub.[1][10] BY exige atribuição; BY-SA acrescenta compartilhamento pela mesma licença para material adaptado. Nenhuma das duas pode conceder direitos que eu não possua sobre páginas de terceiros, e ambas ressalvam direitos de privacidade, personalidade e outros direitos.[14] Até eu harmonizar essas camadas e esclarecer o alcance sobre estrutura, metadados e conteúdo, recomendo tratar a licença como ambígua, preservar proveniência por URL, consultar assessoria jurídica e não supor autorização universal para redistribuição ou uso comercial.
Decisões, práticas de reúso e próximas versões
Publiquei esquema, escala, shards, um exemplo de extração e a proveniência WARC. Na versão inicial, não registei a lista completa de crawls, população candidata, rendimento, limitações de download/parser, versão e configuração do Trafilatura, normalização e escopo da deduplicação de URL, distribuição de idiomas, domínios e comprimentos, duplicação textual, PII, toxicidade, licenças por documento, remoções ou sobreposição com benchmarks.
Em uma próxima versão, pretendo manter um manifesto versionado por estágio e publicar contagens de aceitação e rejeição, sem ocultar a política aplicada:
Renderizando diagrama...
O valor duradouro do Canarim está na combinação de escala, português, texto estruturado e ponte de volta ao WARC. Transformá-lo em dado de treinamento responsável requer preservar essa rastreabilidade enquanto se acrescentam medições, deduplicação textual, governança e clareza jurídica.
Conclusão
Com o Canarim, materializei uma camada de aquisição e extração de páginas em português em escala de centenas de milhões de registros. Ao publicar esse volume em Arrow, aprendi a separar claramente aquisição, curadoria e os filtros que pretendo ampliar em versões futuras.
Referências
1. Domingues, M. Canarim. Hugging Face Dataset, DOI 10.57967/hf/1605: card, API, arquivos e histórico. Consulta em 24 jul. 2026.
2. Common Crawl Foundation. Get Started: WARC, WAT and WET e Merity, S. Navigating the WARC file format, 2014.
3. Common Crawl Foundation. CDXJ Index, URL Index e Index to WARC Files and URLs in Columnar Format.
4. Wenzek, G. et al. CCNet: Extracting High Quality Monolingual Datasets from Web Crawl Data. LREC 2020, p. 4003-4012.
5. International Internet Preservation Consortium. The WARC Format 1.1, ISO 28500:2017.
6. Broder, A. Z. On the Resemblance and Containment of Documents. Compression and Complexity of SEQUENCES 1997, p. 21-29.
7. Lee, K. et al. Deduplicating Training Data Makes Language Models Better. ACL 2022.
8. Penedo, G. et al. The RefinedWeb Dataset for Falcon LLM, 2023.
9. Barbaresi, A. Trafilatura: A Web Scraping Library and Command-Line Tool for Text Discovery and Extraction. ACL-IJCNLP 2021, DOI 10.18653/v1/2021.acl-demo.15; ver também a documentação de extração.
10. Domingues, M. Repositório Canarim: README, LICENSE, texto extraído e preview.
11. Hugging Face. datasets 4.8.4: Streaming e Loading. A documentação descreve IterableDataset, Arrow, seleção de shards e pushdown em Parquet.
12. Domingues, M. Canarim-7B model card, DOI 10.57967/hf/1356.
13. Common Crawl Foundation. Terms of Use, 7 mar. 2024; Privacy Policy, 3 abr. 2025.
14. Creative Commons. Attribution 4.0 International e Attribution-ShareAlike 4.0 International.
BibTeX
@misc{domingues2024canarim,
author = {Maicon Domingues},
title = {canarim},
year = {2024},
publisher = {Hugging Face},
doi = {10.57967/hf/1605},
url = {https://huggingface.co/datasets/dominguesm/canarim}
}
@inproceedings{barbaresi2021trafilatura,
author = {Adrien Barbaresi},
title = {Trafilatura: A Web Scraping Library and Command-Line Tool for Text Discovery and Extraction},
booktitle = {Proceedings of ACL-IJCNLP 2021: System Demonstrations},
pages = {122--131},
year = {2021},
doi = {10.18653/v1/2021.acl-demo.15},
url = {https://aclanthology.org/2021.acl-demo.15/}
}
@inproceedings{wenzek2020ccnet,
author = {Guillaume Wenzek and Marie-Anne Lachaux and Alexis Conneau and Vishrav Chaudhary and Francisco Guzm\'an and Armand Joulin and Edouard Grave},
title = {{CCN}et: Extracting High Quality Monolingual Datasets from Web Crawl Data},
booktitle = {Proceedings of the Twelfth Language Resources and Evaluation Conference},
pages = {4003--4012},
year = {2020},
url = {https://aclanthology.org/2020.lrec-1.494/}
}
@inproceedings{broder1997resemblance,
author = {Andrei Z. Broder},
title = {On the Resemblance and Containment of Documents},
booktitle = {Compression and Complexity of SEQUENCES 1997},
pages = {21--29},
year = {1997},
doi = {10.1109/SEQUEN.1997.666900}
}
@misc{lee2022deduplicating,
author = {Katherine Lee and Daphne Ippolito and Andrew Nystrom and Chiyuan Zhang and Douglas Eck and Chris Callison-Burch and Nicholas Carlini},
title = {Deduplicating Training Data Makes Language Models Better},
year = {2022},
eprint = {2107.06499},
archivePrefix = {arXiv},
primaryClass = {cs.CL},
url = {https://arxiv.org/abs/2107.06499}
}
@misc{penedo2023refinedweb,
author = {Guilherme Penedo and Quentin Malartic and Daniel Hesslow and Ruxandra Cojocaru and Alessandro Cappelli and Hamza Alobeidli and Baptiste Pannier and Ebtesam Almazrouei and Julien Launay},
title = {The RefinedWeb Dataset for Falcon LLM: Outperforming Curated Corpora with Web Data, and Web Data Only},
year = {2023},
eprint = {2306.01116},
archivePrefix = {arXiv},
primaryClass = {cs.CL},
url = {https://arxiv.org/abs/2306.01116}
}