Atenção: este texto é de 2019 e seu conteúdo está desatualizado. Ele é mantido aqui como referência histórica.
Traduzido do en usando google translate
Em primeiro lugar, este será um texto longo, à medida que escrevo cada passo do meu pensamentos ao longo do caminho para escrever este rastreador simples. Se você está com pressa, apenas pule para o [código] (# the_entire_code).
Por que escrever um rastreador? POR QUE NÃO?
Então, basicamente, o que eu quero é um aplicativo que forneça um URL inicial e deve me retornar um mapa do site desenhado em caracteres ascci:
A propósito, vou chamar este projeto de: spidy
$ run spidy http://lewebpag.es
/ (index)
|
|------ /projects
| |
| |------ /agrade
| |
| |------ /prank
|
|------ /blog
|
|------ /hello-world
|
|------ /01-2019-report
|
|------ /how-to-not-be-productive
|
|------ /redux-thuk-utils
Ou algo que se pareça com esta _maestra_de_mapa do site.
Em postagens posteriores do projeto, quero reutilizar este código para fazer mais coisas, como escrever uma ferramenta para me ajudar a verificar a regressão visual em meus projetos ou em alguns dados de páginas, como identificadores de mídia social.
Nosso código deve fazer o seguinte:
Usarei nodejs apenas por preguiça, mas talvez no futuro, irei reescrever isso em python ou qualquer outra linguagem.
Existem muitas maneiras de fazer solicitações HTTP no node: vanilla nodejs HTTP, axios, node-fetch, request, etc. Hoje não estou me sentindo louco o suficiente para usar vanilla HTTP e escrever meu próprio wrapper em torno dele para fazer solicitações facilmente, então eu estou indo pela rota solicitada.
Usarei regex para os elementos \ <a > porque não preciso percorrer o DOM, só preciso descobrir esses preciosos hrefs. Apenas como uma nota, se você precisa fazer mais do que apenas descobrir algumas strings em um texto HTML, _ NÃO USE REGEXP_ você vai me agradecer mais tarde. (HTML é uma linguagem de "programação" estruturada, não apenas um monte de texto aleatório para correspondência de padrão. Use um analisador!)
Vamos usar a estrutura npm package.json para organizar nosso projeto se você
não tem node e npm instalados e configurados, verifique isto antes
continuando.
Estou usando o Linux Mint, então use suas células cerebrais para adaptar tudo o que vir aqui para o seu sistema operacional preferido.
cd workspaces/
mkdir spidy
cd spidy/
npm init -y
Vá mais devagar, partimos de nossa visão abstrata de nível superior e percorremos cada passo para a implementação. Esta é uma boa maneira de ter uma compreensão de todo projeto.
Primeiro, vamos escrever um código sem sentido e aprimorá-lo mais tarde.
// 1. Get initial URL and site domain from cli argument
const domain = arguments.domain
// 2. Crawl into next non-visited URL
let page = crawUrl(domain)
let hrefs = getHrefsFromPage(page)
// 3. Search for \<a\> elements
// * Get its href
// * Store the href URL, if the path, relative or absolute, resides inside the same domain
let containedUrls = hrefs.filter(url => isPathInsideDomain(url))
// 4. Get next non-visited URL
let newUrl = getNextUrlToVisit()
// 5. Got to 2, unless there are no more non-visited URLs
loop
// 6. Draw sitemap based on visited URLs
drawMap(sitemap)
Embora este código seja totalmente inútil neste estado, já podemos ver alguns dos suas partes, então vamos agora escrever essas funções.
Algumas das funções podem ser refatoradas posteriormente, se necessário.
function crawlUrl(url) {
// request URL
// return page
}
// change function name
function getHrefsFromHtml(html) {
// match a regexp
// for each match extract href
// return hrefs
}
function isPathAbsolute(url) {
// does URL starts with HTTP or https
}
// add domain as an attribute so we don't relay on globals
function isPathInsideDomain(url, domain) {}
function getNextUrlToVisit() {
// check list of toVisit URLs and return first non-visited URL
}
function drawMap(sitemap) {
// for each route
// draw route
// drawMap (subRoutes)
}
Vamos preencher as funções acima, uma por uma:
Como eu disse antes, vamos usar a biblioteca de solicitações:
npm install request-promise-native@1.0.7
const request = require("request-promise-native")
async function crawlUrl(url) {
return request.get(url)
}
Vamos usar um regex para corresponder a cada elemento âncora que tem um href Atribua e capture apenas a parte href.
const anchorRegex = /<a(?:.|\n)+?href=('.*?'|".*?")/gi
function getHrefsFromHtml(html) {
let matches
let hrefs = []
while ((matches = anchorRegex.exec(html)) !== null) {
let href = matches[1]
href = href.replace(/("|')/g, "")
hrefs.push(href)
}
return hrefs
}
function isPathAbsolute(url) {
return url.startsWith("http://") || url.startsWith("https://")
}
function isPathInsideDomain(url, domain) {
// if path is absolute
if (isPathAbsolute(url)) {
return url.startsWith(domain)
}
return true
}
Precisaremos de uma função assíncrona principal para chamar, caso contrário, não seríamos capazes de use a sintaxe async / await dentro dela.
let domain = process.argv[2]
console.log(domain)
// Verify if the domain exists and it is an absolute path
if (!domain || !isPathAbsolute(domain)) {
console.error("Provide a valid initial absolute path")
exit(1)
}
async function crawl() {
// Set vector of URLs to visit, starting with the top level website
let toVisit = [domain]
// Set vector of already visited websites
let visited = []
// While there are URLs to visit...
while (toVisit.length) {
// Visit first URL in vector
let currentUrl = toVisit.shift()
// Await page html
let pageHtml = await crawlUrl(currentUrl)
// Mark URL as visited
visited.push(currentUrl)
// Get <a>s href
let hrefs = getHrefsFromHtml(pageHtml)
let nonVisitedUrls = hrefs
// filter only URLs that are inside the domain
.filter(url => isPathInsideDomain(url, domain))
// remap all URLs as absolute paths
.map(url => (url.startsWith(domain) ? url : domain + url))
// remove last '/' from URL to not visit 'page' and 'page/'
.map(url => (url.endsWith("/") ? url.slice(0, -1) : url))
// filter already visited pages
.filter(url => !visited.includes(url))
// filter pages that are already marked to be visited
.filter(url => !toVisit.includes(url))
// merge toVisit URLs and new nonVisitedUrls
toVisit = [...toVisit, ...nonVisitedUrls]
}
drawMap(visited)
}
crawl(domain)
function drawMap(urls, start = domain, tab = 0) {
let parentUrls = urls
.map(url => url.replace(new RegExp("^" + start), ""))
.filter(url => /^\/?[^\/]*$/.test(url))
if (tab === 0) {
parentUrls.shift()
console.log("/ (index)")
}
parentUrls.forEach(parent => {
let prefix = "| ".repeat(tab)
console.log(prefix + "|")
console.log(prefix + "|---- " + parent)
let childrenUrls = urls.filter(url => url.startsWith(start + parent))
drawMap(childrenUrls, start + parent + "/", tab + 1)
})
}
Agora, para ver nosso script funcionando, precisamos apenas executar:
node spidy.js https: // marceloll.com
Vai levar algum tempo.
O código aqui é apenas um rastreador muito simples, por isso não está otimizado, mas sim o emprego.
spidy.js
package.json
Agora temos uma ferramenta simples para rastrear uma página da web e seus links internos, mas ainda carece de alguns recursos:
Tente adicionar esses recursos por conta própria.
Você encontrou um bug no código?
Você tem sugestões para melhorar este tutorial?
Ou você apenas acha que meu código é uma merda e quer escrever por que você acha isso?
Vá em frente e use a seção de comentários para isso!