Raspando o parlamento belga

Raspando o parlamento belga


Tornando os dados parlamentares belgas mais acessíveis e transparentes

🗳️ O resultado final pode ser encontrado em zijwerkenvooru.be (a página está em holandês)

A Câmara de Representantes da Belga faz parte do Parlamento Federal Belga. É composto por 150 membros que são eleitos diretamente a cada 5 anos. Eles executam as seguintes tarefas

  1. Faça perguntas aos ministros relevantes
  2. Debate sobre propostas legislativas
  3. Votar em propostas legislativas

Este projeto começou comigo querendo descobrir a resposta para uma pergunta muito simples: Como o membro X vota no tópico Y?

Dado que os cidadãos belgas podem votar diretamente nos membros do Parlamento, só faz sentido saber facilmente como eles votam.

Surpreendentemente – ou talvez sem surpresa, para os leitores belgas – isso acabou sendo muito mais difícil do que o esperado.

Pule para a direção Como o membro X vota no tópico Y?

A Câmara de Representantes tem um site. Então, vamos dar uma olhada.

Pule para a direção Relatórios de reunião

Os membros da Câmara de Representantes se reúnem semanalmente na reunião plenária, onde fazem perguntas e debatem/votam em propostas legislativas. Toda semana, eles publicam um relatório desta reunião.

Os relatórios das reuniões plenárias.
Os relatórios das reuniões plenárias.

Cada relatório está disponível como um .pdf ou .Html Arquive e contém toda a reunião digitada (tanto em holandês quanto em francês). Um relatório típico tem entre 40 e 80 páginas de comprimento.

Você pode dar uma olhada em um relatório aqui: https://www.dekamer.be/doc/pcri/html/56/ip033x.html.

Pule para a direção Resultados da votação

Cada relatório contém os diferentes votos que ocorreram durante a reunião. Para cada voto, há uma seção que mostra o seguinte:

  • O tópico da votação (em holandês e francês)
  • Uma tabela com os resultados dos votos como números (quantas pessoas votaram em sim/não/absterning)

Para descobrir qual membro votou o quê, temos que rolar até o final do relatório onde para cada voto (indexado por número), há uma lista de nomes agrupados por sim/não/absterna. Não é ótimo!

Um voto resulta no relatório.
Um voto resulta no relatório.
Um voto resulta no relatório.
Um voto resulta no relatório.

Além disso, o voto contém um id como 656/1-4 que vincula o voto a um dossiê 656 e um subdocument 1-4ambos não podem ser encontrados no relatório, mas em uma página separada do site da Câmara de Representantes.

Portanto, a única maneira de ver os resultados dos votos é este relatório de mais de 50 páginas. Não há como

  • Veja todos os votos para um determinado membro
  • Veja todos os votos para um determinado partido
  • Veja todos os votos para um determinado tópico

Vamos melhorar isso! O projeto que decidi construir consiste nas três etapas seguintes:

  1. Baixar o Relatórios de reunião plenária (arquivos HTML)
  2. Extraia os dados dos arquivos HTML e escreva os dados para .Parquet Arquivos que são mais facilmente consultáveis
  3. Leia os dados dos arquivos parquet e gerar arquivos HTML deles para obter um site

Primeiro, precisamos extrair os dados do site da Câmara de Representantes e colocá -los no formato certo.

Pule para a direção Baixando os relatórios de reunião plenária

Este foi o passo mais fácil. Usando Rust e o Reqwest Crate, eu poderia simplesmente baixar os relatórios HTML. Ao parametrizar o URL, eu posso apenas atravessar os IDs da reunião e fazer o download de cada um.

let url = format!(
    "https://www.dekamer.be/doc/PCRI/HTML/{}/ip{:03}x.HTML",
    session_id, meeting_id
);

let response = client.get(&url).await?;
let raw_bytes = response.bytes().await?;
let (decoded_str, _, _) = WINDOWS_1252.decode(&raw_bytes);
let content = decoded_str.to_string();

O único problema que encontrei foi que os arquivos HTML contêm a seguinte meta tag, que nos diz que o arquivo HTML é codificado usando a codificação do Windows-1252 Chracter.

<meta charset=windows-1252>

Eu usei a caixa de codificação-rs para lidar com a decodificação dos arquivos HTML que lidaram perfeitamente com esses arquivos codificados pelo Windows-1252.

💡 Etiqueta de raspagem

Ao raspar dados de um site como se eu estivesse usando um bot/script, verifique o seguinte:

  • Leia os termos de serviço para garantir que você tenha permissão para raspar o site
  • Respeite robots.txt e siga as instruções
  • Solicita solicitações de aceleração para evitar muitos pedidos de uma só vez
  • Identifique seu bot usando um cabeçalho de agente de usuário apropriado
  • Evite raspagem desnecessária (raspe apenas o que é necessário e verifique se você já possui o arquivo antes de baixá -lo novamente)

Agora que temos os arquivos HTML baixados, precisamos extrair dados de votação deles. Para isso, usei a caixa de raspador que permite analisar o HTML usando seletores. Os seletores permitem que você segmente um elemento específico + seletores CSS dentro do arquivo HTML. Por exemplo, o seguinte seletor procura tr tags que contêm uma criança um tag que tem um Href atribuir com o texto Mailto: nele. Eu uso este seletor para encontrar endereços de email.

Selector::parse("tr a(href*='mailto:')")

O script de extração passa pelo arquivo HTML baixado e, usando esse tipo de seletores e outras regras lógicas, ele cria uma estrutura de dados que representa o relatório. Ele captura as seguintes informações:

  • Perguntas (Quesionário, Reclamado, Tópico, Discussão, Documentos Vinculados)
  • Votos (tópico, resultados, documentos vinculados)
  • proposições (tópico, documentos vinculados)

Esses dados então são escritos para um .parquet Arquivo usando a caixa de parquet.

let questions_batch = RecordBatch::try_new(
    questions_schema.clone(),
    vec!(
        Arc::new(StringArray::from(question_ids)),
        Arc::new(StringArray::from(question_questioners)),
        Arc::new(StringArray::from(question_respondents)),
        Arc::new(StringArray::from(question_topics_nl)),
        
    ),
)?;

let mut questions = ArrowWriter::try_new(questions_file, questions_schema, None)?;
questions.write(&questions_batch)?;
questions.close().unwrap();

Acabamos com um questões.parquet Arquivo que contém uma linha para cada pergunta, legal!

Dados de perguntas estruturadas em um arquivo parquet.
Dados de perguntas estruturadas em um arquivo parquet.

Agora que geramos todos esses arquivos parquet, podemos usá -los para gerar um site que possa visualizar todos os dados que extraímos dos relatórios.

Pule para a direção Estrutura e técnicas de raspagem

Para gerar o site, eu uso o Eleventy, que é um gerador de sites estáticos. No momento da construção, todas as páginas do site são geradas. Para consultar os arquivos parquet, eu uso a API do nó DuckDB. O DuckDB é um sistema de gerenciamento de banco de dados SQL em processo que no meu caso é usado para consultar e transformar os dados dos arquivos parquet, em estruturas de dados que podem ser alimentadas no meu gerador de sites estáticos.

import { DuckDBInstance } from '@duckdb/node-api';

const questionsFilePath = 'src/data/questions.parquet';
const instance = await DuckDBInstance.create(':memory:');
const connection = await instance.connect();

const readParquet = async (filePath) => {
    const result = await connection.runAndReadAll(`SELECT * FROM read_parquet('${filePath}')`);
    return result.getRows();
};

readParquet(questionsFilePath);

Por exemplo, podemos consultar ambos membros.parquet e questões.parquet para relacionar perguntas com os membros. Em seguida, podemos gerar uma página para cada membro, mostrando todas as perguntas que eles fizeram nas sessões plenárias.

A imagem abaixo mostra uma lista de perguntas que um membro fez. Cada pergunta mostra o título da pergunta e pela qual ministro foi respondido.

Perguntas feitas por um membro.
Perguntas feitas por um membro.

Ao clicar em uma pergunta, você vá para a página de detalhes, que inclui a discussão subsequente em um estilo de bate -papo/diálogo.

Uma discussão após uma pergunta.
Uma discussão após uma pergunta.

A coisa legal de usar elente e DuckDB é que todas essas transformações e gerações de página ocorrem no momento da construção. Isso significa que não há chamadas de banco de dados ou consultas caras acontecendo quando um usuário visita uma página no site.

Pule para a direção Visualização de voto

Vinculando membrosAssim, festas e votospodemos exibir os resultados dos votos de maneira organizada. A página de detalhes da votação mostra o título do voto, o resultado e os resultados detalhados da votação.

Votos no relatório.
Votos no relatório.
Votos no site.
Votos no site.

Cada voto tem 3 pontos de vista:

  • pelo partido (geralmente o mais interessante, já que muitas vezes todos os membros de um partido votam da mesma forma)
  • meu membro
  • por opção de voto

Pule para a direção Rotulagem de tópicos

Cada pergunta, voto ou proposição recebe uma lista de tópicos. Dentro desses tópicos, existem 8 tópicos principais

  • 🚆 Mobilidade e transporte
  • 🩺 Saúde e bem -estar
  • 🌍 Clima, energia e agricultura
  • 💼 Economia e trabalho
  • 🔒 Segurança, aplicação da lei e defesa
  • 🌍 Política internacional e migração
  • 💻 Tecnologia, comunicação e mídia
  • 🛐 Educação, cultura e religião

Ao atribuir tópicos para cada item de reunião, posso então para um determinado membro gerar um gráfico que mostra seu principal interesse.

Os tópicos favoritos de um membro como um gráfico estelar.
Os tópicos favoritos de um membro como um gráfico estelar.
Visualização de detalhes do tópico.
Visualização de detalhes do tópico.

Também podemos seguir o contrário: dado um tópico, consulte todos os itens de reunião (perguntas/votos/proposições) relacionados a esse tópico, juntamente com os quais os membros estão mais envolvidos com esses tópicos.

O mapeamento entre um item de reunião e um tópico é feito usando uma lista simples de palavras -chave, contra a qual o título da pergunta/voto/proposição é correspondido.

"smoking and vapes": ("vapes", "cigarette", "smoking"),
"games of chance": ("lotto", "gambling", "lottery"),

No final de cada um dos 8 principais tópicos, adicionei 2-5 subtópicos com algumas palavras-chave para cada um. Esta lista será estendida e ajustada à medida que mais perguntas são ingeridas ao longo do tempo.

Pule para a direção Resumo do título

Freqüentemente, as perguntas relacionadas são agrupadas para que elas só precisem ser respondidas uma vez pelo ministro responsável. Esses títulos de perguntas geralmente significam a mesma coisa e então eu decidi que esse seria um bom caso de uso para usar um Llm para resumir esses tópicos.

4 tópicos de perguntas semelhantes.
4 tópicos de perguntas semelhantes.

Ao ter uma alternância de ‘versão resumida’ na parte superior da página, o usuário pode optar por ver a versão resumida de cada pergunta. Isso torna a página inteira um pouco mais digestível de se olhar. Quando um usuário vê um tópico em que está interessado, ele pode ir à página de detalhes para ver a discussão completa.

1 Tópico de pergunta resumida.
1 Tópico de pergunta resumida.

Mistral foi usado para gerar esses resumos. Eu usei o seguinte prompt:

The assistant will receive a comma-separated list of topics and generate a single, concise topic (no more than 20 words) that encompasses all the given topics.
- The result must match the style of the input topics.
- The result must be in Dutch.
- Do not add explanations, clarifications, or extra words such as 'including' or 'such as'.
- The output should fit naturally within the provided list.
- Only return the summarized topic without any additional text.

Pule para a direção Rastreamento de renda

No processo de construção do local, encontrei outro local do governo public.regimand.be, que contém renda declarada de políticos belgas. Ao vincular esses dados à lista existente de membros, eu poderia gerar um pequeno gráfico de renda + uma lista de suas funções para cada ano.

A renda anual um membro do Parlamento.
A renda anual um membro do Parlamento.

Pule para a direção Gráficos dinâmicos

Como eu tinha todos esses dados raspados (membros, partidos, votos, perguntas, proposições, dossiers, rendimentos), achei divertido permitir que o usuário gerasse seus próprios gráficos dinâmicos.

Por exemplo, eles podem plotar número de perguntas contra festa ver qual partido faz o maior número de perguntas por pessoa no Parlamento.

Gráficos dinâmicos.
Gráficos dinâmicos.

Dado o ponto de partida (um longo relatório de cada reunião), acho que o resultado final acabou como uma grande melhoria. O site atual zijwerkenvooru.be permite visualizar perguntas/votos/proposições de vários pontos de vista (por membro, por parte, por reunião, por tópico) que um relatório estático toda semana simplesmente nunca poderia fazer.

O site é configurado para que, a cada semana, possa baixar o novo relatório da reunião, raspar os dados e atualizar o site.

A pilha final usada de tecnologias/ferramentas é a seguinte:

🗳️ Pilha de tecnologia

  • Ferrugem (raspando e analisando)
  • Parquet (armazenamento de dados)
  • DuckDB + Node.js (consulta + transformação de dados)
  • Eleventy (geração estática do local)
  • Mistral (LLM Summarization)
  • d3.js (gráficos)

Obrigado pela leitura!

Publicado



Source link

Postagens Similares

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *