开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
背景
想做一款自己的导航网站,为了美观,想要显示网站的对应图标,网站的默认图标都是放在网站根目录下的favicon.ico文件,但是这个默认图标是可以修改的,而且改的方式还千奇百怪,比如设置为相对路径、外链、svg文件等,为了完成这个导航,自己研究了一遍爬虫如何获取图标。
技术摘要
node:通过node脚步去爬取页面 cheerio:cheerio是一款页面解析插件,可以把爬取到的页面字符串转换成接近JQuery的操作 axios:通过axios获取页面,因为我们只需要获取head信息,所以无论是传统网页还是单页面应用都不影响
实现原理
1、通过axios.get请求获取页面 2、cheerio解析页面,通过cheerio类似Jquery的操作获取到页面的link[rel*=icon]的标签,获取该标签的href属性。 3、判断获取的href值,判断是相对路径还是绝对路径,得到最后的链接 4、如果请求网页失败,或者没有找到link[rel*=icon]标签,即设置了默认图标 /favicon.ico
遇到的问题
原理很简单,但是实际开发却遇到了很多问题,幸好最后都一一解决了:
- axios请求报错:
UNABLE_TO_VERIFY_LEAF_SIGNATURE请求sass官网时遇到了这个错误,原因是网址的数字证书(https)设置不正确,这里将axios的校验证书设置设置为false,参考来源:axios忽略SSL证书,不校验https证书
axios.get(href, {
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
})
- link标签匹配 html可以通过link标签设置网站的标签栏图标,经过测试,目前发现有一下几种方式:
- 没有设置link[rel]标签
- 相对路径:
<link rel="/static/favicon.ico"> - 绝对路径:
<link rel="icon" type="image/png" href="https://img.yzcdn.cn/vant/logo.png"> - rel存在多个值:
<link rel="shortcut icon" type="image/x-icon" href="https://cdn.segmentfault.com/r-02842691/favicon.ico"> - 不含协议的绝对路径(太狗了):
<link rel="icon" sizes="32x32" type="image/png" href="//img.nodejs.cn/favicon.png">
根据上面几种情况,爬取对应的标签以及处理结果即可。
- 网页访问图标地址部分报 403 设置网页标签头即可解决:
<meta name="referrer" content="no-referrer" />
代码实现
获取某个链接的ico(未处理结果)
const cheerio = require('cheerio');
const axios = require('axios')
const https = require('https');
async function getItemIcon(href) {
let ico = ''
try {
const res = await axios.get(href, { // 设置不校验证书噢
httpsAgent: new https
.Agent({
rejectUnauthorized: false
})
})
const $ = cheerio.load(res.data); // 初始化cheerio对象
$('link[rel*="icon"]').each(function() { // rel不是 === icon,也有可能存在 rel="xxx icon"的情况
const rel = $(this).attr('rel')
if (/[^-]icon/.test(rel) || rel === 'icon') { // 排除rel="mask-icon"这种情况
ico = $(this).attr('href') // 这里的函数不要使用箭头函数!!否则this会丢失
}
})
if (!ico) {
ico = '/favicon.ico'
}
} catch (e) {
ico = '/favicon.ico'
}
return ico
}
处理icon结果
const url = require('url');
const href = 'http://www.axios-js.com/' // 模拟用户传入的链接
const u = url.parse(href) // 通过node的url对象,处理用户传入的链接,因为有可能传入的是子页面,又可能末尾带或不带/,转为url对象统一处理
let src = await getItemIcon(element.href) // 形式有完整链接、/、//;/表示相对路径,//如'//img.nodejs.cn/favicon.png',则只需要拼接上协议即可
if (src.startsWith('//')) { // 不带协议的完整链接
src = u.protocol + src
} else if (!src.startsWith('http')) { // 相对路径链接
src = `${u.protocol}//${u.host}${src.startsWith('/') ? src : '/' + src}` // 判断icon路径前面有没有/
}
console.log('最后结果:' + src)
别忘记给html页面,设置meta:
<meta name="referrer" content="no-referrer" />