用node实现一个客户邮箱挖掘爬虫的一些思考

原创 Dean 工作笔记 二维码阅读
2019-04-19 23:40

现今很多人在从事外贸行业,对于外贸,现在主要有两种销售方式,一种是走线上平台比如敦煌、速卖通、亚马逊等等,一种依然是走传统的线下模式,主要通过展会、搜索引擎等方式来挖掘获取客户资源进而销售产品,本文主要是从技术的角度谈论实现一个爬虫来帮我们快速挖掘客户邮箱等信息。

首先我们回顾一下如何手工通过搜索引擎来挖掘客户邮箱:

1、在搜索引擎比如google输入我们的关键词比如water filter company,然后搜索,在搜索结果中我们会得到很多做净水器的公司网站。

2、点击搜索结果中的网址进入网站,浏览网站信息,并在当前页面找是否存在邮箱等信息,有的话直接记录下来,没有的话继续当前网页上链接的同域的其他链接,继续浏览直到寻找到我们需要的邮箱信息,然后再浏览搜索结果中的下一个信息,按照相似的步骤获取邮箱信息。

通过上面的步骤,我们会发现手动挖掘客户邮箱虽然不是很麻烦,但是效率很低,而且很容易忽略一些重要信息,毕竟我们不是从搜索结果中点进去就直接能找到邮箱,可能还需要继续浏览同域的其他网址才能找到邮箱,而且网页的内容可能很长,我们很可能会忽略掉重要的信息,这个也就是本文的重点,如何利用爬虫根据入口页找到该域的网页可能存在的邮箱。

通过上面的分析,我们很容易就可以写出一个简易版的爬虫,不过在写代码之前我们需要先创建项目目录还有安装项目依赖:

1、首先创建目录mailRobot,然后运行命令(前提是本机已安装好node环境):

npm init -y

这一步主要生成package.json文件用于记录项目依赖。

2、安装依赖

npm i request cheerio --save

3、等待依赖安装完成后新建app.js, 作为我们程序的入口文件,后面我们所有的代码都将放在这里。

const request = require('request');
const cheerio = require('cheerio');

function fetch(url) {
	return new Promise((resolve, reject) => {
		request({
			url
		}, (err, response, body) => {
			if (err) {
				return reject(err);
			}
			resolve(body);
		});
	});
}

function getLinks(content, url) {
	const $ = cheerio.load(content);

	const domain = getDomain(url);
	const $anchors = $('a');

	const links = [];

	$anchors.map((index, item) => {
		const $a = $(item);
		const href = $a.attr('href');

		// 1、排除主页/
		// 2、排除#,javascript:;,mailto
		// 3、排除图片,脚本
		if (href && href !== '/' && !/^[#jm]/i.test(href) && !/(jpg|png|gif|jpeg|svg|bmp|js)$/gi.test(href)) {
			const realUrl = joinPath(url, href);
			let reg = new RegExp(domain, 'i');
			if (reg.test(realUrl)) {
				links.push(realUrl);
			}
			reg = null;
		}
	});

	return links;
}

function joinPath(url, path) {
  if (/^https?/.test(path)) {
    return path;
  } else if (/^\/\//.test(path)) {
    return url.split('/')[0] + path;
  } else {
    const pathArr = url.split(/(?<!\/)\/(?!\/)/);
    if (path[0] === '/') {
      return pathArr[0] + path;
    } else {
      if (path.substr(0, 2) === './') {
        pathArr.pop();
        return pathArr.join('/') + '/' + path.substr(2);
      } else {
        const backArr = path.split('../');
        pathArr.length = pathArr.length - backArr.length;
        return pathArr.join('/') + '/' + backArr[backArr.length - 1];
      }
    }
  }
}

function getDomain(url) {
	return url.replace(/(?<=[a-z])\/.*/, '');
}

function checkIfHasEmail(content) {
  return /\w+@\w{2,}\.\w{2,}/.test(content);
}

function getEmails(content) {
  const matches = content.match(/\w+@\w{2,}(\.\w{2,})+/g);
  if (matches &amp;&amp; matches.length) {
    const newMatches = matches.filter(m => {
      return !/(jpg|png|gif|jpeg|svg|bmp)/g.test(m) &amp;&amp; !/domain\.com/.test(m);
    });
    return newMatches || [];
  }
  return '';
}

function analysis(links, callback) {
	const result = [];
	function analysisPage(links, callback, isEntry = false) {
		const url = links.shift();
		if (url) {
			let newLinks = links.slice(0);
			const domain = getDomain(url);
			fetch(url)
				.then(body => {
					// 如果当前页存在邮箱
					const isHasEmail = checkIfHasEmail(body);
					if (isHasEmail) {
						const emails = getEmails(body);

						// 记录邮箱
						result.push({
							domain,
							url,
							emails
						});

						// 去除同域待爬取的其它链接
						newLinks = newLinks.filter(u => {
							return u.indexOf(domain) === -1;
						});
					} else {
						// 如果是入口页 则抓取当页同域的连接用于继续爬取
						if (isEntry) {
							const pageLinks = getLinks(body, url);
							newLinks = [...new Set(newLinks.concat(pageLinks))];
						}
					}

					analysisPage(newLinks, callback);
				})
				.catch(err => {
					console.log(err);
					analysisPage(newLinks, callback);
					console.log('\033[31manalysis link', url, 'failed!\033[39m');
				});
		} else {
			callback &amp;&amp; callback(result);
		}
	}
	analysisPage(links, callback, true)
}

然后将我们的爬虫跑起来,

analysis(['https://www.deanhan.cn'], result => {
	console.log(result);
});

本站邮箱是留在about页面的,我们最后跑出来的结果也是:

爬虫基本是实现了,但是当待分析的连接过多的时候,爬虫在各效率方面表现的就不太理想了,下面我们一一分析:

1、对于一个域,当我们分析到了邮箱的时候,则会停止对当前页其它页面的分析,但是连接是无顺序的,有可能带邮箱的连接在最后面,我们需要把前面的连接都分析完才能找到邮箱,这样很耗时间,我们如何优化呢?

其实我们手工挖掘过客户邮箱不难发现,大部分存在邮箱地址的的url特征都差不多,比如contact,about之类的,那么我们可以根据这些特征来排序待分析的链接,将其排在前面,这样爬虫很早就爬到了这个链接,找到邮箱就早早结束了,怎么做呢?

首先我们引入一个变量features来记录特征值:

const features = ['contact', 'about'];

修改分析网址的方法如下:

function analysis(links, callback) {
	const result = [];
	function analysisPage(links, callback, isEntry = false) {
		const url = links.shift();
		if (url) {
			let newLinks = links.slice(0);
			const domain = getDomain(url);
			fetch(url)
				.then(body => {
					// 如果当前页存在邮箱
					const isHasEmail = checkIfHasEmail(body);
					if (isHasEmail) {
						const emails = getEmails(body);

						// 记录邮箱
						result.push({
							domain,
							url,
							emails
						});

						// 去除同域待爬取的其它链接
						newLinks = newLinks.filter(u => {
							return u.indexOf(domain) === -1;
						});
					} else {
						// 如果是入口页 则抓取当页同域的连接用于继续爬取
						if (isEntry) {
							const pageLinks = getLinks(body, url);
							newLinks = [...new Set(newLinks.concat(pageLinks))];
							let reg = new RegExp(features.join('|'));
							newLinks = newLinks.filter((a, b) => {
								return reg.test(b) ? 1 : -1;
							});
							reg = null;
						}
					}

					analysisPage(newLinks, callback);
				})
				.catch(err => {
					console.log(err);
					analysisPage(newLinks, callback);
					console.log('\033[31manalysis link', url, 'failed!\033[39m');
				});
		} else {
			callback &amp;&amp; callback(result);
		}
	}
	analysisPage(links, callback, true)
}

其实我们在这个基础上还可以做的更具科学感一点,比如每次找到邮箱后我们提取url中的特征值,然后记录下来,用于后面排序待分析链接,这样子就相当于给我们的爬虫一点学习功能了。

我们能做的其实还有很多比如:运行异常后下次启动自动恢复到上次的状态,自动调过已经爬取过的域名等等,这些功能都不是很难,有兴趣的朋友可以自己去试试。

本文地址:https://www.deanhan.cn/node-email-dig.html
版权声明:本文为原创文章,版权归 Dean 所有,欢迎分享本文,转载请保留出处!
  • 支付宝二维码 支付宝
  • 微信二维码 微信