-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10048 from MPMG-DCC-UFMG/dev
Integra sistema distribuído à Master
- Loading branch information
Showing
276 changed files
with
12,988 additions
and
12,745 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
.git | ||
venv/ | ||
env/ | ||
mpmg*/ | ||
mpmg*/ | ||
data/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,96 +1,200 @@ | ||
# C01 | ||
Desenvolvimento de ferramentas para construção e manutenção de coletores de páginas da Web. | ||
|
||
Existem coletores bases, que podem ser personalizados através da interface feita em django. Eles são capazes de coletar: | ||
|
||
- Páginas estáticas | ||
- Páginas dinâmicas ou onde é necessário interagir com formulários. | ||
- Arquivos | ||
- Conjunto de arquivos | ||
## Status atual | ||
|
||
Os coletores são desenvolvidos em Scrapy em conjunto com Pyppeteer para o caso de páginas dinâmicas. O gerenciamento dos coletores é feito com o Scrapy-cluster. | ||
- Distribuição de spiders (funcionam cooperativamente em máquinas distintas) | ||
- Problemas de escalabilidade no módulo de salvamento de coletas/arquivos | ||
- Suporte a páginas estáticas e downloads de arquivos | ||
- Suporte parcial a páginas dinâmicas (com bugs) | ||
- Suporte a download de arquivos em páginas dinâmicas. Mas caso haja mais de uma instância de `spider_manager` executando em máquinas distintas, os arquivos serão salvos em locais diferentes. | ||
- Logs são transmitidos distribuidamente via Kafka | ||
- Monitoramento parcial de coletas/coletores | ||
- É possível acompanhar o andamento da coleta de páginas/arquivos | ||
- Não é possível acompanhar a "saúde" dos `workers`/`spiders` | ||
|
||
## TODOs | ||
|
||
Dentre as funcionalidades disponíveis para os coletores, pode-se se citar, por exemplo: | ||
A lista atualizada pode ser vista [aqui](https://github.com/MPMG-DCC-UFMG/C01/issues?q=is%3Aissue+is%3Aopen+label%3A%22sistema+distribu%C3%ADdo%22): | ||
|
||
- Mecanismos para camuflagem dos coletores, como rotação de endereço de IP e gerenciamento de cookies. | ||
- Ferramentas para gerar endereços automaticamente através de templates | ||
- Os coletores também podem ser gerenciados através de uma API RESTful. | ||
- [ ] Suporte total a páginas dinâmicas (há certos bugs na execução de coletas) | ||
- [ ] Download centralizado de arquivos em páginas dinâmicas | ||
- Provavelmente será necessário suporte a sistema de arquivos distribuídos | ||
- [x] Suporte a mecânismos de passos (páginas dinâmicas) | ||
- [ ] Resolver problemas de escalabilidade (módulos `writer` e `link_generator`) | ||
- Será necessário suporte à sistema de arquivos distribuídos para o módulo `writer` | ||
- [ ] Melhorias no sistema de acompanhamento de andamento de coletas (o atual não esté "calibrado") | ||
- [ ] Tela de monitoramento de coletas, como saúde dos `workers`/`spiders` | ||
- [ ] Realização de testes, inclusive de robustez | ||
|
||
Para que seja possível utilizar o sistema, e consequentemente configurar e executar coletores, é necessário inicialmente instalar a aplicação. Essa página se refere a essa etapa inicial. Preferencialmente, a instalação deve ser feita nativamente em sistemas baseados em Linux, contudo, através do Docker, é possível instalar o sistema em outros SO, como Windows. Portanto, se esse for o seu caso, foque nas instruções de instalação no final da página, na seção "Execução com Docker (standalone)". Futuramente, será possível realizar uma instalação distribuída do sistema, que ainda está sendo desenvolvida. | ||
## Como executar | ||
|
||
Temos 4 módulos que devem funcionar de maneira independente: `Crawler Manager (CM)` (que inicia junto com o servidor), `Link Generator (LG)`, `Spider Manager (SM)` e `Writer (W)`. | ||
|
||
## Instalação | ||
A primeira etapa para poder instalar o sistema é realizar o donwload de seu código-fonte. Para isso, utilize as ferramentas do GitHub para baixar o repositório localmente. | ||
Esses módulos, devidademente configurado os `hosts` do `Zookeeper`, `Kafka` e `Redis`, em um arquivo `settings.py` na respectiva pasta deles, podem ser executados em máquinas diferentes com as seguintes condições: | ||
|
||
Em seguida, é importante notar que para usar o programa é necessário um _virtualenv_ ou uma máquina apenas com **Python 3.7+**, de maneira que os comandos _"python"_ referencie o Python 3.7+, e _"pip"_ procure a instalação de pacotes também do Python 3.7+. Assim, configure esse ambiente. Para mais informações de como criar e manter _virtualenvs_, o [seguinte material](https://docs.python.org/pt-br/3/library/venv.html) pode ajudar. | ||
- Cada um devem ser iniciados manualmente, ainda não foram dockerizados. | ||
- Deve haver apenas uma instância em execução dos seguintes módulos: | ||
- `Crawler Manager`: Naturalmente terá apenas uma instância em execução, pois é um processo filho do servidor. | ||
- `Link Generator`: Mais de uma instância em execução resultará em duplicação de requisições de coletas. Isso porque cada instância irá ler o comando de geração de requisições, então **é necessário adaptar a leitura de mensagens do Kafka para que apenas um consumer leia cada mensagem**. | ||
- `Writer`: Esse módulo tem o problema de escalabilidade de `Link Generator` (Kafka consumers lendo a mesma mensagem, portanto, gerando processamento duplicado), mas mais que isso, ter mais de uma instância em execução resultará em `fragmentação da coleta`. Isso pois cada módulo escreve dados no sistema de arquivos local de cada máquina. Portando, é `necessário suporte a sistema de arquivos distribuídos`. | ||
- Podem haver várias instâncias de `Spider Managers` em execução, inclusive na mesma máquina. Ao contrário dos problemas de escalabilidade relatado nos outros módulos que se comunicam via `Kafka`, a fila de coletas é gerenciado via `Redis`, de modo que não há processamento de uma mesma mensagem por consumidores diferentes. | ||
- No momento, um `Spider Manager` por gerir apenas um spider de uma mesma coleta. | ||
|
||
Além disso, alguns serviços necessitam que o Java esteja rodando no sistema, que pode ser instalado por | ||
> **Obs.**: Zookeeper, Kafka e Redis devem estar em execução e seus hosts e portas configuradas nos arquivos settings.py dos módulos. | ||
### Iniciando Kafka e Redis | ||
|
||
Ao executar `python install.py` na raiz do projeto, tanto o `Kafka` quanto o `Redis` são baixados automaticamente. Para executá-los, execute os seguintes comandos (considerando que a pasta corrente é a raiz do projeto): | ||
|
||
Inicie o `Zookeeper` para que o `Kafka` funcione corretamente: | ||
```bash | ||
cd kafka_2.13-2.4.0/ | ||
bin/zookeeper-server-start.sh config/zoo.properties | ||
``` | ||
sudo apt install default-jre | ||
|
||
Abra um outro terminal e inicie o Kafka: | ||
|
||
```bash | ||
cd kafka_2.13-2.4.0/ | ||
bin/kafka-server-start.sh config/server.properties | ||
``` | ||
Caso seu sistema não seja Linux, siga as instruções de instalação da Java Runtime para o seu sistema, as quais podem ser encontradas nesse [link](https://www.java.com/en/download/manual.jsp). | ||
|
||
Para instalar todos os programas e suas dependências execute o script install.py. | ||
``` | ||
python install.py | ||
Abra outro terminal e inicie o `Redis`: | ||
|
||
```bash | ||
cd redis-5.0.10/ | ||
./src/redis-server | ||
``` | ||
Esse script deve ser executado a partir da raiz do repositório. No final da execução, logs semelhantes a esses devem aparecer no seu terminal: | ||
|
||
<img src="https://drive.google.com/uc?export=view&id=1DXXS-CQyXThC4xlPYp-zquUctdDhphac" > | ||
### Interface/servidor Django | ||
|
||
Se deseja instalar apenas algum dos módulos implementados, por exemplo, o módulo de extração de parâmetros de formulários, navegue até a pasta do módulo e execute pip install: | ||
``` | ||
cd src/form-parser | ||
pip install . | ||
``` | ||
Instale as dependências: | ||
|
||
Caso pretenda utilizar o Tor para rotacionar IPs, é necessário configurá-lo por meio dos seguintes comandos: | ||
```bash | ||
|
||
python3.7 -m venv venv | ||
source venv/bin/activate | ||
python install.py | ||
``` | ||
chmod 744 src/tor_setup/setup.sh | ||
./src/tor_setup/setup.sh | ||
|
||
Coloque-o para rodar: | ||
|
||
```bash | ||
python manage.py runserver --noreload | ||
``` | ||
|
||
Um script de instalação diferente foi necessário pois para instalar o Tor é necessário ser superusuário. | ||
> Note a flag `--noreload`, ela é importante para que o servidor não reinicie durante sua execução e crie mais consumidores Kafka que o necessário (gerando duplicação de `logs`, por exemplo) | ||
## Execução | ||
### Link Generator | ||
|
||
Para execução da interface basta executar o seguinte comando: | ||
``` | ||
python run.py | ||
``` | ||
E em seguida acessar _http://localhost:8000/_ | ||
Vá para a pasta do módulo e instale suas dependências: | ||
|
||
Se quiser acessar o programa através da rede, execute: | ||
```bash | ||
cd link_generator/ | ||
python3.7 -m venv venv | ||
source venv/bin/activate | ||
pip install -r requirements.txt | ||
``` | ||
python run.py 0.0.0.0:8000 | ||
Execute-o: | ||
|
||
```bash | ||
cd src/ | ||
python main.py | ||
``` | ||
E então use o IP da máquina onde a interface está sendo executada para acessá-la. Por exemplo, se a máquina onde você rodou o comando acima tem endereço de IP _1.2.3.4_, e esse endereço é visível para sua máquina através da rede, você pode acessar _http://1.2.3.4:8000/_. Essa execução só dará certo se a máquina estiver com o acesso externo desse IP liberado, caso contrário, não será possível acessar o sistema remotamente. | ||
|
||
Assim que tiver executado e acesso pelo navegador, a seguinte página aparecerá: | ||
### Spider Manager | ||
|
||
Vá para a pasta do módulo e instale suas dependências: | ||
|
||
<img src="https://drive.google.com/uc?export=view&id=1pfvTCtLBCk7SIu8SprEL8cD5FQcojlZl" > | ||
```bash | ||
cd spider_manager/ | ||
python3.6 -m venv venv | ||
source venv/bin/activate | ||
pip install -r requirements.txt | ||
|
||
Mais informações de como utilizar a interface pode ser encontrado nas próximas páginas dessa Wiki. | ||
``` | ||
|
||
## Execução com Docker (standalone) | ||
> Note que é usado python3.6. Isso é um requisito necessário, pois o módulo utiliza recursos do Scrapy-Cluster que só a suporte a essa versão do python. | ||
Antes de tudo, assegure-se de que o Docker está devidamente instalado no seu computador. Caso precise de instruções de como fazer isso, o seguinte link pode auxiliar nesse processo: https://docs.docker.com/get-docker/ | ||
Crie quandos processos desejar de Spider Managers, repetindo o comando abaixo: | ||
|
||
Para instalação do sistema é necessário montar a imagem a partir do Dockerfile, para isso execute o seguinte comando a partir da raiz do repositório: | ||
``` | ||
sudo docker build -t c01 . | ||
cd src/ | ||
python comand_listener.py | ||
``` | ||
### Writer | ||
|
||
Vá para a pasta do módulo e instale suas dependências: | ||
|
||
Para conseguir executar a imagem, é preciso criar o diretório "data" a partir da raiz do repositório, para isso, execute o comando: | ||
``` | ||
mkdir data | ||
cd writer/ | ||
python3.7 -m venv venv | ||
source venv/bin/activate | ||
``` | ||
Obs: Caso seu sistema não seja Linux, utilize o comando equivalente para criar diretório. | ||
|
||
Em seguida, é necessário executar a imagem. Ainda na raiz do respositório execute o comando responsável por isso: | ||
``` | ||
sudo docker run --mount type=bind,source="$(pwd)/data",target=/data -p 8000:8000 -t c01 | ||
Execute-o: | ||
|
||
```bash | ||
cd src/ | ||
python writer.py | ||
``` | ||
## Arquitetura atual da solução distribuída | ||
|
||
![Arquitetura](sist_dist_diagram.png) | ||
|
||
O sistema possui 4 módulos principais: | ||
|
||
- **Gerenciador de coletas** (`crawler_manager`) | ||
- Módulo acoplado ao servidor. | ||
|
||
- É responsável por realizar a interface entre a aplicação Django e os módulos de processamento de coletas. | ||
|
||
- Recebe status de spiders (criação e encerramento) vindos de gerenciador de spiders. Com isso, é capaz de saber e informar ao servidor quando uma coleta terminou. | ||
|
||
- Também repassa os logs das coletas ao servidor, bem como o atualiza sobre o andamento das coletas, em geral. | ||
|
||
- **Gerador de requisições de coletas** (`link_generator`, *um melhor nome pode ser atribuído) | ||
- Responsável por gerar as requisições iniciais de coletas. | ||
|
||
- Útil para o caso de URLs parametrizadas não sobrecarreguem o servidor. | ||
|
||
- **Funcionamento atual** | ||
|
||
- O módulo recebe a configuração de um coletor e gera todas requisições de coletas possíveis. Um dos possíveis impactos disso é "inundar" o Redis. | ||
|
||
- **Funcionamento desejado/futuro** | ||
|
||
- Geração requisições de coletas sob demanda. | ||
|
||
- **Gerenciador de spiders** (`spider_manager`) | ||
- Responsável por gerenciar o ciclo de vida dos spiders, bem como informar ao gerenciador de coletas sobre o status dos mesmos e das coletas. | ||
|
||
- **Funcionamento atual** | ||
|
||
- Há suporte há apenas um spider por coleta para cada gerenciador de spiders. Mas podem haver múltiplos spiders gerenciados, desde que sejam de coletas diferentes. | ||
|
||
- Spiders são criados no início do processo de coleta, e, caso um spider fique ocioso por determinado tempo, ele é automaticamente encerrado. | ||
|
||
- **Funcionamento desejado/futuro** | ||
|
||
- Suporte a múltiplos spiders de um mesmo coletor. | ||
|
||
- Balanceamento de carga. Criação e encerramento de spiders a qualquer momento, de acordo com a necessidade. | ||
|
||
- **Escritor** (`writer`) | ||
- Responsável por persistir as coletas, bem como baixar e salvar os possíveis arquivos encontrados nela. | ||
|
||
- **Funcionamento atual** | ||
|
||
- Só é suportado que apenas um deste módulo execute ao mesmo tempo. Pois os dados coletados são salvos diretamente no sistema de arquivo da máquina hospedeira. | ||
|
||
- Mais de um deste módulo em execução implicaria em dados salvos em máquinas diferentes ou possíveis conflitos de arquivos. | ||
|
||
- Isso reflete em um possível gargalo ao salvar os dados coletados, como os dados coletados são transmitidos por um único tópico Kafka e o processamento por um única instância deste módulo. | ||
|
||
- **Funcionamento desejado/futuro** | ||
|
||
O comando acima garante que o container terá acesso ao disco da máquina, e esse aceso foi feito através da ligação da raiz do respositório com a raiz da imagem. Ou seja, ao configurar coletores com o seguinte caminho "/data/nome_coletor", os dados estarão sendo salvos na verdade no seguinte diretório da máquina: "caminho_da_raiz_repositório>/data/nome_coletor". É possível alterar o diretório na máquina hospedeira, para isso, basta alterar o trecho "$(pwd)" do comando para o diretório desejado. | ||
- Suporte a sistema de arquivos distribuídos e/ou salvamento dos dados coletados em banco de dados (para o caso das páginas), possibilitando múltiplas instâncias do módulo executando ao mesmo tempo. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import json | ||
import os | ||
|
||
from kafka import KafkaConsumer | ||
|
||
class FileDescriptionConsumer(): | ||
TOPIC_NAME = 'file_description' | ||
|
||
def __init__(self, kafka_host: str, kafka_port: str): | ||
""" | ||
FileDescriptionConsumerConsumer constructor. | ||
:param kafka_host: location of the host machine running kafka | ||
:param kafka_port: port used to interact with kafka in the host machine | ||
""" | ||
|
||
self.kafka_consumer = KafkaConsumer( | ||
FileDescriptionConsumer.TOPIC_NAME, | ||
bootstrap_servers=f'{kafka_host}:{kafka_port}' | ||
) | ||
|
||
def description_consumer(self): | ||
""" | ||
Writes the description of the items stored in Kafka to the correct | ||
files. Items must be utf-8 encoded json strings in the format described | ||
in feed_description. | ||
""" | ||
try: | ||
for item_json in self.kafka_consumer: | ||
item = json.loads(item_json.value.decode('utf-8')) | ||
self.write_description(item) | ||
finally: | ||
self.kafka_consumer.close() | ||
|
||
def write_description(self, item: dict): | ||
""" | ||
Writes the description of items loaded from the Kafka topic to the | ||
correct file. | ||
:param item: A dictionary in the format described in feed_description | ||
""" | ||
|
||
file_address = os.path.join(item["destination"], | ||
"file_description.jsonl") | ||
|
||
with open(file_address, "a+") as f: | ||
f.write(json.dumps(item["description"])) | ||
f.write("\n") |
Oops, something went wrong.