使用Node和cheerio爬取网站的ico图标

655 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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

遇到的问题

原理很简单,但是实际开发却遇到了很多问题,幸好最后都一一解决了:

  1. axios请求报错:UNABLE_TO_VERIFY_LEAF_SIGNATURE 请求sass官网时遇到了这个错误,原因是网址的数字证书(https)设置不正确,这里将axios的校验证书设置设置为false,参考来源:axios忽略SSL证书,不校验https证书
axios.get(href, {
	httpsAgent: new https.Agent({
		rejectUnauthorized: false
	})
})
  1. 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">

根据上面几种情况,爬取对应的标签以及处理结果即可。

  1. 网页访问图标地址部分报 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" />