Capturando menções de notícias de hackers com Node.js e MongoDB
Avalie esse Tutorial
Se você está no espaço de tecnologia, provavelmente se deparou com o Hacker Novidades em algum momento ou outro. Talvez você esteja interessado em saber o que é popular nesta semana para tecnologia ou talvez tenha algo para compartilhar. É uma plataforma para informações.
O problema é que você encontrará muitas informações no Hacker News sem uma maneira particularmente fácil de filtrá-las para encontrar os tópicos nos quais está interessado. Digamos, por exemplo, que você queira saber informações sobre o Bitcoin assim que ele for compartilhado. Como você faria isso no site Hacker News?
Neste tutorial, aprenderemos como analisar os dados do Hacker News à medida que são criados, filtrando apenas os tópicos nos quais estamos interessados. Faremos uma análise de sentimento sobre as possíveis correspondências para classificá-las. , e então armazenaremos essas informações no MongoDB para que possamos executar relatórios a partir delas. Faremos tudo isso com Node.js e alguns pipelines simples.
Você não precisará de uma conta do Hacker Novidades para este tutorial, mas precisará de algumas coisas para ser bem-sucedido:
- Node.js 12.10 ou mais recente
Armazenaremos todas as nossas correspondências no MongoDB Atlas. Isso facilitará a execução de relatórios e não dependeremos da análise de logs ou dados estruturados de forma semelhante.
O Hacker News não tem uma API que nos permita transmitir dados em tempo real. Em vez disso, usaremos a API de streaming não oficial do Hacker News. Para este exemplo específico, analisaremos o fluxo de comentários, mas suas necessidades podem variar.
Antes de entrarmos no código interessante e em nossa jornada geral para compreender e armazenar os dados do Hacker News à medida que eles chegam, precisamos inicializar nosso projeto.
No seu computador, crie um novo diretório de projeto e execute os seguintes comandos:
1 npm init -y 2 npm install mongodb ndjson request sentiment through2 through2-filter --save
Com os comandos acima, estamos criando um arquivopackage.json e instalando alguns pacotes. Sabemos que omongodb será usado para armazenar nossos dados de notícias do Hacker, mas o restante da lista provavelmente não é familiar para você.
Usaremos o pacote desolicitação para consumir dados brutos da API. À medida que avançamos, você notará que estamos trabalhando com fluxos de dados em vez de solicitações únicas para a API. Isso significa que os dados que obtemos nem sempre estão completos. Para dar sentido a isso, usamos o pacotendjson para obter JSON utilizável do stream. Como estamos trabalhando com fluxos, precisamos ser capazes de usar pipelines, portanto, não podemos simplesmente passar nossos dados JSON pelo pipeline como estão. Em vez disso, precisamos usar 2 e 2-filter para filtrar e manipular nossos dados JSON antes de passá-los para outro estágio no pipeline. Finalmente, temos sentido para fazer uma análise de sentido em nossos dados.
Repetiremos muitos desses pacotes à medida que avançarmos.
Antes de passar para a próxima etapa, certifique-se de criar um arquivomain.js em seu projeto. É nele que adicionaremos nosso código, que, como você verá, não tem muitas linhas.
Vamos começar adicionando nossas dependências baixadas ao nosso arquivo de código e conectando a um cluster ou instância MongoDB.
Abra o arquivo main.js do projeto e adicione o seguinte código:
1 const stream = require("stream"); 2 const ndjson = require("ndjson"); 3 const through2 = require("through2"); 4 const request = require("request"); 5 const filter = require("through2-filter"); 6 const sentiment = require("sentiment"); 7 const util = require("util"); 8 const pipeline = util.promisify(stream.pipeline); 9 const { MongoClient } = require("mongodb"); 10 11 (async () => { 12 const client = new MongoClient(process.env["ATLAS_URI"], { useUnifiedTopology: true }); 13 try { 14 await client.connect(); 15 const collection = client.db("hacker-news").collection("mentions"); 16 console.log("FINISHED"); 17 } catch(error) { 18 console.log(error); 19 } 20 })();
No código acima, adicionamos todas as nossas dependências baixadas, além de algumas. Lembre-se de que estamos trabalhando com um fluxo de dados, portanto, precisamos usar pipelines em Node.js se quisermos trabalhar com esses dados em etapas.
Quando executamos o aplicativo, estamos nos conectando a uma instância ou cluster MongoDB conforme definido em nossas variáveis de ambiente. A variável
ATLAS_URI
teria a seguinte aparência:1 mongodb+srv://<username>:<password>@plummeting-us-east-1.hrrxc.mongodb.net/
Você pode encontrar a string de conexão no seu dashboard do MongoDB Atlas.
Teste se o aplicativo pode se conectar ao banco de dados executando o seguinte comando:
1 node main.js
Se não quiser usar variáveis de ambiente, você poderá codificar o valor em seu projeto ou usar um arquivo de configuração. Pessoalmente, prefiro variáveis de ambiente porque podemos configurá-las externamente na maioria das implantações na cloud para segurança (e não há risco de enviá-las acidentalmente ao Github).
Neste ponto, o código que temos nos conectará ao MongoDB. Agora precisamos nos concentrar em transmitir os dados do Hacker News para o nosso aplicativo e filtrá-los para obter os dados que realmente nos interessam.
Vamos fazer as seguintes alterações em nosso arquivomain.js :
1 (async () => { 2 const client = new MongoClient(process.env["ATLAS_URI"], { useUnifiedTopology: true }); 3 try { 4 await client.connect(); 5 const collection = client.db("hacker-news").collection("mentions"); 6 await pipeline( 7 request("http://api.hnstream.com/comments/stream/"), 8 ndjson.parse({ strict: false }), 9 filter({ objectMode: true }, chunk => { 10 return chunk["body"].toLowerCase().includes("bitcoin") || chunk["article-title"].toLowerCase().includes("bitcoin"); 11 }) 12 ); 13 console.log("FINISHED"); 14 } catch(error) { 15 console.log(error); 16 } 17 })();
No código acima, depois de nos conectarmos, criamos um pipeline de estágios a serem concluídos. O primeiro estágio é uma solicitação GET simples para o ponto de extremidade da API de streaming. Os resultados de nossa solicitação devem ser JSON, mas como estamos trabalhando com um fluxo de dados em vez de esperar uma única resposta, nosso resultado pode ser malformado dependendo de onde estamos no fluxo. Isso é normal.
Para ir além, isso podemos colocar as peças do quebra-cabeça JSON por conta própria à medida que elas vêm do fluxo, ou podemos usar o pacotendjson. Este pacote atua como o segundo estágio e analisa os dados recebidos do estágio anterior, sendo nossa solicitação de streaming.
Quando o estágio
ndjson.parse
for concluído, deveremos ter formado JSON adequadamente para trabalhar. Isso significa que precisamos analisá-los para ver se são dados JSON que queremos manter ou descartar. Lembre-se de que a API de streaming nos fornece todos os dados fornecidos pelo Hacker Novidades, não apenas o que estamos procurando. Para filtrar, podemos usar o pacote2-filter, que nos permite filtrar um fluxo como fariamos em uma array no javaScript.Em nosso estágio
filter
, retornaremos true se o corpo da menção do Hacker News incluir "bitcoin" ou se o título do tópico incluir o termo "bitcoin". Isso significa que essa entrada específica é o que estamos procurando e ela será passada para o próximo estágio do pipeline. Tudo o que não corresponder será ignorado nos estágios futuros.Neste ponto, devemos ter correspondências com os dados do Hacker News que nos interessam. No entanto, o Hacker News tem muitos bots e usuários postando dados potencialmente irrelevantes apenas para classificação nas pesquisas das pessoas. É uma boa ideia analisar o nosso jogo e marcar para saber a qualidade. Mais tarde, podemos optar por ignorar partidas com pontuação baixa, pois provavelmente serão uma perda de tempo.
Então, vamos ajustar um pouco o nosso pipeline no arquivomain.js:
1 (async () => { 2 const client = new MongoClient(process.env["ATLAS_URI"], { useUnifiedTopology: true }); 3 const textRank = new sentiment(); 4 try { 5 await client.connect(); 6 const collection = client.db("hacker-news").collection("mentions"); 7 await pipeline( 8 request("http://api.hnstream.com/comments/stream/"), 9 ndjson.parse({ strict: false }), 10 filter({ objectMode: true }, chunk => { 11 return chunk["body"].toLowerCase().includes("bitcoin") || chunk["article-title"].toLowerCase().includes("bitcoin"); 12 }), 13 through2.obj((row, enc, next) => { 14 let result = textRank.analyze(row.body); 15 row.score = result.score; 16 next(null, row); 17 }) 18 ); 19 console.log("FINISHED"); 20 } catch(error) { 21 console.log(error); 22 } 23 })();
No código acima, adicionamos duas partes relacionadas ao pacotesentiment que havíamos instalado anteriormente.
Primeiro inicializamos o pacote através da seguinte linha:
1 const textRank = new sentiment();
Ao analisar os estágios do pipeline, usamos o pacote2 para manipulação de objetos de streaming. Como se trata de um fluxo, não podemos simplesmente pegar nosso JSON do estágio
ndjson.parse
e esperar poder manipulá-lo como qualquer outro objeto em JavaScript.Quando manipulamos o objeto correspondente, estamos realizando uma análise de sentimento no corpo da menção. Neste momento, não nos importamos com a pontuação, mas planejamos adicioná-la aos dados que eventualmente armazenaremos no MongoDB.
O objeto no momento pode se parecer com algo como isto:
1 { 2 "_id": "5ffcc041b3ffc428f702d483", 3 "body": "<p>this is the body from the streaming API</p>", 4 "author": "nraboy", 5 "article-id": 43543234, 6 "parent-id": 3485345, 7 "article-title": "Bitcoin: Is it worth it?", 8 "type": "comment", 9 "id": 24985379, 10 "score": 3 11 }
A única modificação que fizemos nos dados a partir de agora é a adição de uma pontuação de nossa análise de sentimento.
É importante observar que nossos dados ainda não estão dentro do MongoDB. Estamos apenas no estágio em que fizemos modificações no fluxo de dados que podem corresponder aos nossos interesses.
Com os dados formatados como queremos, podemos nos concentrar em armazená-los no MongoDB e consultá-los sempre que quisermos.
Vamos fazer uma modificação em nosso pipeline:
1 (async () => { 2 const client = new MongoClient(process.env["ATLAS_URI"], { useUnifiedTopology: true }); 3 const textRank = new sentiment(); 4 try { 5 await client.connect(); 6 const collection = client.db("hacker-news").collection("mentions"); 7 await pipeline( 8 request("http://api.hnstream.com/comments/stream/"), 9 ndjson.parse({ strict: false }), 10 filter({ objectMode: true }, chunk => { 11 return chunk["body"].toLowerCase().includes("bitcoin") || chunk["article-title"].toLowerCase().includes("bitcoin"); 12 }), 13 through2.obj((row, enc, next) => { 14 let result = textRank.analyze(row.body); 15 row.score = result.score; 16 next(null, row); 17 }), 18 through2.obj((row, enc, next) => { 19 collection.insertOne({ 20 ...row, 21 "user-url": `https://news.ycombinator.com/user?id=${row["author"]}`, 22 "item-url": `https://news.ycombinator.com/item?id=${row["article-id"]}` 23 }); 24 next(); 25 }) 26 ); 27 console.log("FINISHED"); 28 } catch(error) { 29 console.log(error); 30 } 31 })();
Estamos fazendo outra transformação em nosso objeto. Isso poderia ter sido mesclado com o estágio de transformação anterior, mas para limpeza do código, estamos dividindo-os em dois estágios.
Nesta etapa final, estamos fazendo uma operação
insertOne
com o driver MongoDB Node.js. Estamos pegando o row
dos dados do estágio anterior e estamos adicionando dois novos campos ao objeto antes que ele seja inserido. Estamos fazendo isso para ter acesso rápido ao URL e não precisarmos reconstruí-lo posteriormente.Se executássemos o aplicativo, ele seria executado para sempre, coletando todos os dados postados no Hacker News que correspondessem ao nosso filtro.
Se quisermos consultar nossos dados no MongoDB, poderíamos usar uma consulta MQL como a seguinte:
1 use("hacker-news"); 2 3 db.mentions.find({ "score": { "$gt": 3 } });
A query MQL acima encontraria todos os documentos com uma pontuação superior a 3. Com a análise de feedback, você não está analisando uma pontuação de 0 a 10. É melhor você ler a documentação para ver como as coisas são pontuadas.
Você acabou de ver um exemplo de uso do MongoDB e do Node.js para capturar dados relevantes do Hacker Novidades à medida que acontecem. Isso pode ser útil para manter seu próprio feed de tópicos específicos ou pode ser estendido para outros casos de uso, como monitorar o que as pessoas estão dizendo sobre sua marca e usar o código como uma ferramenta de relatório de feedback.
Este tutorial pode ser expandido além do que examinamos neste exemplo. Por exemplo, poderíamos adicionar MongoDB Atlas Triggers para procurar determinadas pontuações e enviar uma mensagem no Twilio ou Slack se uma correspondência em nossos critérios for encontrada.
Se você tiver dúvidas ou comentários sobre este tutorial, disponibilize um momento para fazê-los nos MongoDB Community.