Readability.org é uma ferramenta útil, mas ela sofre por ser útil, popular e gratuita – o serviço muitas vezes está fora do ar ou lento.
Se você é um hard user do Readability, pode estar interessado em saber que existem muitas bibliotecas de software que tentam recriar a função principal do Readability – que tiram o corpo principal do texto de uma página carregada com anúncios e outras distrações. Algumas delas funcionam melhor do que outras, mas o Readability.js da Mozilla parece fazer o melhor.
No entanto, a biblioteca da Mozilla foi escrita para ser executada dentro do navegador. Para usá-la com o node, existe o readability-node, que me colocou na desconfortável posição de ter que atrapalhar o meu caminho com o node. Mas deixa pra lá, eu sou corajoso o suficiente e você também. Vamos em frente e usar a readability-node para construir a nossa ferramenta e tirar um pouco da carga do readability.org.
Escrevendo o app
A principal funcionalidade é bastante simples. Readability-node espera um DOM, que pode ser fornecido via jsdom, por isso vamos usar essa biblioteca também. Use cd para atualizar o diretório do projeto e execute estes comandos:
$ echo '{"name": "Readability-clone"}' $ npm install readability-node jsdom --save
Depois disso, comece a editar seus app.js e vamos tentar fazer isso rodar. A primeira coisa que temos a fazer é descobrir como obter o código-fonte de uma url arbitrária. Aqui está o que eu pensei:
https = require('https'); r = require('readability-node'); jsdom = require('jsdom').jsdom; https.get("https://adambard.com/blog/the-web-is-a-mature-platform/", function(res){ var src = ''; res.on('data', function(d){ src += d; }); res.on('end', function(){ console.log(src); }); });
Note que temos que acompanhar todas as nossas operações IO vagamente com esses callbakcs, em vez de usar funções síncronas e esperar, como em uma linguagem civilizada. Isso ocorre porque o node.js prioriza non-blockingness acima de tudo, especialmente sanidade.
Você pode rodar esse código com $ node app.js para ver se ele funciona como esperado.
Agora que temos o fonte, precisamos alimentá-lo através de readability-node via jsdom.
https = require('https'); r = require('readability-node'); jsdom = require('jsdom').jsdom; var uri = "https://adambard.com/blog/the-web-is-a-mature-platform/"; https.get(uri, function(res){ var src = ''; res.on('data', function(d){ src += d; }); res.on('end', function(){ var doc = jsdom(src, {features: { FetchExternalResources: false, ProcessExternalResources: false }}); var article = new r.Readability(uri, doc).parse(); console.log(article.title, "\n\n", article.content); }); });
Assim, a parte robusta do app está pronta. O resto é apenas a criação de uma interface de usuário para usar um uri dinâmico e exibir bem o resultado. Vamos em frente e fazer um servidor que irá mostrar o resultado e, enquanto isso, comece a buscar a URL da cadeia de consulta quando possível.
url = require('url'); http = require('http'); https = require('https'); r = require('readability-node'); jsdom = require('jsdom').jsdom; function handleRequest(req, resp){ var uri = url.parse(req.url, true).query.url uri = uri || "https://adambard.com/blog/the-web-is-a-mature-platform/"; https.get(uri, function(res){ var src = ''; res.on('data', function(d){ src += d; }); res.on('end', function(){ var doc = jsdom(src, {features: { FetchExternalResources: false, ProcessExternalResources: false }}); var article = new r.Readability(uri, doc).parse(); resp.write( "<html><head><meta charset='utf-8'><title>" + article.title + "</title></head><body>" + article.content + "</body></html>"); }); }); } var server = http.createServer(handleRequest); server.listen(process.env.PORT || 3000, function(){ console.log("OK");});
Você pode executar o aplicativo e apontar seu navegador para http://localhost:3000/ para ver o seu servidor rodando em toda a sua glória. Em algum momento, vamos querer alguns templates. Eu gosto de mustache, então vamos usar isso e criar um template dir:
$ npm install mu2 --save $ mkdir templates
Agora podemos editar templates/index.html conforme o necessário. Aqui está o que fiz:
<html> <head> <meta charset="UTF-8"> <title>{{title}}</title> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Libre+Baskerville"> <link rel="stylesheet" href="/css/style.css"> </head> <body> <header> <h1><a href="/">Fungibility</a></h1> <form method="get" action="/"> <label for="url">Enter URL:</label> <input type="text" name="url"> <button>Load</button> </form> </header> <main> <div class="container"> <h2>{{title}}</h2> {{{content}}} </div> </main> </body> </html>
Agora, nós temos um problema: precisamos de uma maneira para servir /css/style.css. Mas, já que estamos realmente meio-assing essa coisa toda, vamos servi-la a partir do Node:
url = require('url'); path = require('path'); fs = require('fs'); http = require('http'); https = require('https'); r = require('readability-node'); jsdom = require('jsdom').jsdom; mu = require('mu2'); mu.root = __dirname + '/templates'; function render(resp, ctx){ // A helper function to render index.html, our only template mu.compileAndRender('index.html', ctx).pipe(resp); } function serveFile(filename, resp){ fs.readFile(filename, "binary", function(err, file) { if(err) { resp.writeHead(500, {"Content-Type": "text/plain"}); resp.write(err + "\n"); resp.end(); return; } resp.writeHead(200); resp.write(file, "binary"); resp.end(); }); } function serveReadability(uri, resp){ uri = uri || "https://adambard.com/blog/the-web-is-a-mature-platform/"; https.get(uri, function(res){ var src = ''; res.on('data', function(d){ src += d; }); res.on('end', function(){ var doc = jsdom(src, {features: { FetchExternalResources: false, ProcessExternalResources: false }}); var article = new r.Readability(uri, doc).parse(); render(resp, article); }); }); } function handleRequest(req, resp){ var req_url = url.parse(req.url, true); var filename = path.join(process.cwd(), req_url.pathname); var uri = req_url.query.url; fs.exists(filename, function(exists) { if(exists && !fs.statSync(filename).isDirectory()){ serveFile(filename, resp); }else{ serveReadability(uri, resp); } }); } var server = http.createServer(handleRequest); server.listen(process.env.PORT || 3000, function(){ console.log("OK");});
Você pode (re)iniciar o servidor e ver que agora tem um layout relativamente bom para o seu app. Assim, a linha de base está praticamente pronta; vou deixar os ajustes e o css como um exercício para o leitor (apenas crie um arquivo /css/style.css no diretório do seu projeto e edite-o, e ele deve funcionar).
Fazendo o deploy do Heroku
Esta parte também é fácil. Tenha certeza de ter o mais recente heroku toolbelt, então
$ git init $ echo 'web: node app.js' > Procfile $ echo 'node_modules' > .gitignore $ git add . && git commit -m "Initial commit" $ heroku create $ git push heroku master
Navegue até a url fornecida pelo Heroku, e você deve ver algo surgir. É simples assim!
E se eu quiser apenas colocar meu clone?
Eu chamei meu projeto de fungibility e coloquei-o no GitHub para qualquer um que queira usá-lo (ele também tem um pouco de tratamento de erros):
$ git clone https://github.com/adambard/fungibility.git $ cd fungibility $ heroku create $ git push heroku master
Isso deve funcionar bem. Ou você pode apenas usar fungibility diretamente.
***
Adam Bard faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://adambard.com/blog/self-hosted-readability-on-heroku-with-nodejs/