网络爬虫,网络蜘蛛,英文称作 Web Crawler or Web Scrapying,以下简称爬虫,是一个可以用来从网络获取资料的技术。本篇主要是分享一下在学习 Engine Bai的文章和他的进阶教程过r程中遇到的大坑,以及如何解决问题的一些思考。依照文章中的思路,依旧是通过IPeen爱评网作为范例,但在语言实现上选择JavaScript,一来是说到爬虫选用python语言的相对较多,而用js的就相对少,无论资料还是讨论热度都不足,二是相比python而言,个人对js更熟悉一些,易于操作。还有就是我确实不太会py啦。
爬虫是对网页文档进行解析处理的程序,而有些我们所需要的数据,就遍布在网页文档的各个元素之中,在浏览器中我们看到的是网页界面,但本身只是一个个HTML标签元素组成的文本档案。
这里我们要获取的事店家列表上a标签的链接,之后再对这些a标签链接进一步获取解析列表中每个店家的具体信息。
文章中提到通过识别a标签中的data-label=‘店名’属性可以锁定到这些有店家链接的标签上,其实假如规律不太好发现的话,可以在Chrome浏览器下右键‘检查’功能>选中目标元素>在Elements下的标签上右键>Copy>Copy Selector, 获取css选择器,特别是class属性的值,基本所有一个列表里的店家的元素会使用同一组class以进行相同的样式处理。
这里用的是nodejs来实现的,初级的爬取用的是node-crawler,高级的有用到selenium-webdriver,使用场合不大一样,后面会提到。安装的部分不再赘述。
明确下我们的目标:是抓取iPeen的美食店家的资料,具体的说有店家的id、店名、食物小类别、地址、经纬坐标。所以根据文章里提到的,从店家列表网页,也就是“http://www.ipeen.com.tw/search/taiwan/000/1-0-0-0/”。
首先创建一个可以抓取到所有店家资料页面的链接,并存储在数组shopLinks
中,
var cForList = new Crawler({
maxConnections: 10,
callback: (error, res, done) => {
if(error){
console.log(error);
}else{
const $ = res.$;
const uri = res.request.uri;
const shopLinksATags = $("a[data-label='店名']");
$(shopLinksATags).each((i, elem) => {
const href = $(elem).attr('href');
const completedHref = getCompletedURL(uri, href);
shopLinks.push(completedHref);
});
console.log(shopLinks);
}
done();
}
});
通过 $("a[data-label='店名']")
找到所有data-label=‘店名'
的 标签。
接着再对每个店家页面分析出详细的资料,但很快你就会发现事情并不单纯。
一开始我在上面的回调中引用一个针对详细资料编写的Crawler,把数组中的所有链接添加到其对象的解析队列中,在多次尝试,甚至是在Chrome浏览器中直接检查元素查看,在浏览器console中选中标记,在浏览器中能获取到数据,而在nodejs中却是爆出解析错误。那么,这是为什么呢?
因为网页内容是动态生成的(或是异步请求之类的),你所看到的事经过浏览器将JavaScript执行后产生的结果,而一般的爬虫不会执行这些JavaScript。
在Chrome中右键“显示网页源代码”,看到的HTML文档是没有动态产生内容的部分,也即一般爬虫所能到达的领域;而在渲染完成的网页上右键检查元素,此时的元素是经过动态产生的结果,一般爬虫是无法获得这部分的资料的。只要对比其中差异,就能判断当前网页是否是动态生成。
让爬虫先模拟浏览器取得动态产生的内容后,再去取得分析HTTP。
具体的,在进阶教程中有描述,大意是使用selenium-webdriver来通过浏览器渲染页面,获取需要的资讯。一开始我是配合chromedrive使用的,但问题挺多,一是实体浏览器启动并开始页面解析所费时间颇长,简单爬一两个页面也费时费力,二是经自动化控制的Chrome浏览器,插件基本无法使用,这就意味着代理插件派不上用场,这就是使得连接比较困难,很容易连接超时。
选用无壳浏览器phantomjs的webdrive后体感上快了不少,同时也令我觉得在爬取数据的用途上来讲,phantomjs的优势会比一般浏览器的要大。
const getData = (uri) => {
const driver = new webdriver.Builder().forBrowser('phantomjs').build();
const href = uri;
const shopId = href.slice(href.lastIndexOf('/') + 1, href.length).split('-')[0];
driver.get(uri);
driver.findElement(By.css('#shop-header > div.info > h1 > span')).getText().then((txt) => { console.log(shopId); console.log(txt); }, (msg)=> {console.log(msg)});
driver.findElement(By.css('#shop-header > div.info > div.brief > p.cate.i > a')).getText().then((txt) => {console.log(txt)}, (msg) => {console.log(msg); });
driver.findElement(By.css('#shop-header > div.info > div.brief > p.addr.i > a')).getText().then((txt) => {console.log(txt)}, (msg) => {console.log(msg); });
driver.findElement(By.css('#shop-header > div.info > div.brief > p.addr.i > a')).getAttribute('href').then((txt) => { printoutGPS(txt); }, (msg)=> console.log(msg));
driver.close();
}
这里特定元素的选择我用上面提到的直接复制成CSS选择器的形式,其实XPath也是个不错的选择。
然后就是我有些不解的地方吧,selenium-webdriver的会话的生命周期还不太理解,如果是一个实例解析所有链接的话,在跳转到下一个链接的过程中会报错;而如果每个实例一一对应一个链接的话,感觉执行速度慢的令我有些怀疑人生OTZ。
总之如果有机会的话可能尝试下直接使用phantomjs进行解析吧,毕竟selenium的功能在爬虫方面的应用还是有些大材小用的感觉。
周末快到了,希望大家周末快乐吧。