最近在找前端的工作,无奈对大杭州的各大区不是很熟悉,每次看到公司的地址都要点进地图里看看具体位置。于是生出一个想法,能不能把招聘的信息标注在地图上,这样看起来就很直观了。
花了一天时间,撸了一个简单的版本,发出来分享一下,也为自己作一个小结。
附上GitHub地址,欢迎交流指正~
分析
我的习惯是在写代码之前先分析一下自己的需求和要达到的效果,以便于选择合适的技术和工具。
分析过程:
- 我的首要需求是招聘的信息,这部分信息要通过爬虫去抓取。因为之前写过Node.js的爬虫,所以选择Node.js来编写爬虫。
- 发送请求可以使用Node.js自带的
https
模块,也可以使用其他第三方的模块。我选择了superagent
模块,用起来比较方便。(这里提一句,因为我爬的是拉勾,人家用的是https协议,所以要用https模块去发送请求) - 考虑到爬取的链接会比较多,一口气发送那么多请求秒秒钟就被人家封IP了,所以要控制一下并发数,这里我选择了
async
模块。 - 爬到的数据用
cheerio
模块处理,如果有编码问题,可以用iconv-lite
转换一下编码。拉勾用的是utf-8,就省去了这一步。 - 拿到所有数据之后,要把数据显示在地图上,毫无疑问要用到地图,这里选择用百度地图API。
- 考虑到易用性,加一个
opn
模块,自动打开浏览器,进入网页。
实现
编写爬虫之前先分析一下url,打开前端开发的页面,发现它的url是这样的:
https://www.lagou.com/zhaopin/qianduankaifa/?filterOption=3
再打开第二页,是这样:
https://www.lagou.com/zhaopin/qianduankaifa/2/?filterOption=3
于是我们找到了一个规律,每一页的url可以通过函数来生成
function url(pageNum){
return `https://www.lagou.com/zhaopin/qianduankaifa/${pageNum}/?filterOption=3`;
}
假设我们随便爬一个
const superagent = require('superagent');
const cheerio = require('cheerio');
let _url = url(1);
superagent
.get(_url)
.end((err,res)=>{
if(err) console.log(err);
let $ = cheerio.load(res);
//...
//这里用cheerio处理res,拿到我们要的链接,用法参照jQuery
});
响应回来的res是整个页面的html,把它交给cherrio进行处理。
OK,然后回到浏览器来看一下,一个页面中一共列了15条招聘信息,我们要拿到它们的链接好进入招聘详情页抓取需要的数据。于是,F12打开调试工具,找到对应a标签的位置
这里可以看到,a标签中的href属性的值就是我们需要的链接,目标明确了,接下来用cheerio处理,就像用jQuery那样,简单粗暴
let hrefs = [];
$('a.position_link').each(function () {
hrefs.push($(this).attr('href'));
});
unique(hrefs); //简单的去个重,防止有重复的链接出现
考虑到反爬虫的机制,我们可以设置一下请求头来模仿浏览器请求,比如我们设置一个"User-Agent"字段
superagent
.get(_url)
.set({'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'})
.end((err,res)=>{
if(err) console.log(err);
});
除了通过模仿浏览器请求来绕过反爬虫策略,还应该控制一下并发,这里用async模块
const async = require('async');
let limit = 5;
async.mapLimit(urls,limit,(url,callback)=>{
//code...
callback(null,data); //每次并发操作的结果data通过callback传递给results
},(err,results)=>{
if(err) console.log(err);
console.log(results); //results是一个数组,包含了每个并发任务中传过来的data
});
每个并发任务完成后,处理完的data通过调用callback函数传递到最终的结果数组results中。上述代码,等所有并发任务完成后,会执行回调函数,打印出结果数组。
如此一来,我们就可以拿到包含详情页url的数组,然后通过这些url再去抓取具体的职位信息。待所有需要的信息都抓取到后,我们就可以拼装数据,把数据挂载到路由下了。
数据有了,剩下的就是把数据显示在百度地图上了,使用方法参见百度地图API。
使用百度地图API v1.5以后的版本(最新版v2.0)前要先申请一个密钥(ak),然后在index.html中引入
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=你的密钥"></script>
ps: 抓取到的地址信息是文字形式的地址,而百度地图API使用的是经纬度坐标,所以要将地址转换成经纬度。百度地图中提供了Geocoder类,可以生成一个地址解析器,将地址信息解析成经纬度坐标。
API文档
- express : GitHub地址,官网地址
- superagent : GitHub地址,官网地址
- async : GitHub地址,官网地址
- cheerio : GitHub地址,官网地址
- opn : GitHub地址
- 百度地图 :官网地址
小结
虽然只是一个并不复杂的项目,但还是暴露出了一些问题,比如模块的划分,感觉还不够合理,代码的规划也略显凌乱。之后还会对代码进行修改,努力写出更加优雅的代码。