由于web前端技术的日新月异,传统爬虫对页面的爬取结果愈发不能满足用户的需求。对于安全领域来讲,如果关注网站安全,那么获取页面的内容信息是第一步需要做的事情,所以开发出一款能够获取基于ajax实现的动态页面的爬虫是很有必要的。
在写爬虫之前,我们首先需要了解爬虫的原理。对于我们需要获取页面内容的目标网站而言,其页面内容可以看做是从首页扩展出来的一张图,而爬虫要做的事情就是遍历这一张图的每个节点,获取节点的页面信息。利用数据结构的知识我们知道,只需要利用最简单的宽度优先算法就可以实现。广度优先算法的算法原理如下所示:
从页面1中扩展出子页面2,3,4,然后分别扩展2,3,4,得到他们自己的子页面,广度优先算法就是基于这样逐层的扩展完成算法流程的。我们可以利用一个队列来存储这些节点,队列先进先出的特性,刚好符合广度优先算法逐层扩展的算法逻辑。在整个广度优先遍历的过程中,需要对已经爬取过的链接节点进行标记,避免重复爬取。同时为了增加爬虫的执行效率,需要对该算法进行并发上的优化,最简单的优化方式就是在对每一层的页面节点进行扩展的采取按层并发执行,具体的实现就是对于每一个页面节点存储一个层数信息,当前节点的层数等于父层节点的层数+1,在广度优先对队列中的节点进行出队操作时,一次性将某层的所有节点全部取出来,然后放入到一个执行线程池中,线程池会在全部任务执行完之后返回。同时,可以通过给执行线程池配置不同的线程数,达到控制被爬网站最大并发爬取数的目的。避免因爬虫造成对方网站的损失。自此,爬虫调度引擎便实现完成了。下面,我将以Java代码的方式给大家简单讲解上面的流程。
public void doCrawlProcess(){
PriorityQueue<UrlNode> urlNodeQueue = new PriorityQueue<>(); // UrlNode是页面节点
urlNodeQueue.add(rootNode);
while (!urlNodeQueue.isEmpty()) {
// 扩展下层节点
...
// 将所有该层的节点加入到线程池中
ExecutorService pool = new ThreadPoolExecutor(crawler.getThreadNumber(),
crawler.getThreadNumber(),
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
for (UrlNode urlNode1 : nextFloorUrlNode) {
callables.add(() -> {
crawlUrlService.getChildUrl(crawler, urlNode1, uncheckedNextFloorUrlNodes);
return 1;
});
}
pool.invokeAll(callables); // 线程池执行所有的任务
// 注意这里的代码不全,不能直接使用
}
}
上面部分的内容其实和传统爬虫没什么区别,同时如果为了达到控制爬取个数,深度,速度的限制,都可以直接在上面的方法中进行扩展。现在需要基于动态页面抓取这样的需求,对传统的页面下载模块进行替换。其实业界实现web2.0页面的抓取的方法比较多,我这里使用的是谷歌推出 的puppeteer框架,该框架是直接操作的谷歌浏览器,一般以headless的模式执行。这里是puppeteer的文档官网,提供了相近的nodejs的api。nodejs是一个易于上手的语言。相信你可以利用这一篇文档快速的进行web2.0页面抓取的开发。在这里,我需要提醒你几个点,首先是由于nodejs没有多线程的概念,所以在初始化Brower的时候,不需要考虑并发的问题,为了减少内存的使用量,我们需要对一个Nodejs服务,只打开一个brower服务,每一个不同的url打开不同的page,然后当页面利用完毕,直接关闭page即可。第二个问题,是如果线上出现了在过了几个小时之后节点出现了内存占满导致服务不可用的情况,你需要将该Nodejs服务放入到systemd的service中进行管理,方便进行定时重启的任务。最后再利用代码结束这部分的内容
async function crawl(url){
if(typeof globalBrower == 'undefined' || globalBrower == 'undefine'){
globalBrower = await puppeteer.launch({
headless:true,
executablePath:'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
});
}
var page = await globalBrower.newPage();
try{
page.setDefaultNavigationTimeout(110 * 1000);
page.setUserAgent('Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36');
page.setJavaScriptEnabled(true);
page.setCacheEnabled(false);
page.setRequestInterception(true);
page.on('request', interceptedRequest => {
let url = interceptedRequest.url();
if(url.indexOf('.png') > -1 || url.indexOf('.jpg') > -1
|| url.indexOf('.gif') > -1
|| url.indexOf('.pdf') > -1
|| url.indexOf('.txt') > -1
|| url.indexOf('.mp4') > -1
|| interceptedRequest.resourceType() === 'image')
interceptedRequest.abort();
else
interceptedRequest.continue();
});
await page.goto(url);
await page.waitForSelector('html');
let html = await page.content();
return html;
}catch(err){
}
finally{
page.close();
}
}