单机爬虫的逻辑以及问题解决

Posted callmegaga

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单机爬虫的逻辑以及问题解决相关的知识,希望对你有一定的参考价值。

1.1单机爬虫

  网络爬虫是指按照一定的规则,自动抓取互联网信息的程序或脚本。其原理很简单,就是获取到一个页面的内容,获取其中所有的下一级URL,然后访问。

  单线程的爬虫可以设计成递归的模式。即使,方法的入口是一个URL,方法中对URL对象内容进行解析,操作和存储,同时,在方法中获取子集URL并调用方法本身。

技术分享图片

 

  单线程递归的方式比较简单,但其不能充分利用计算机的资源,迭代后期大量URL访问过程在同一条线程内执行,必然也会互相抢夺网络资源,并且过程不可控,多线程并发比较难处理。

  可以引入生产者消费者模型的思路,将URL队列储存起来,与访问过程分开来。

技术分享图片

  这样,其多线程开发就简单了很多,只要添加多个访问器同时执行就行了。

  技术分享图片

  多线程的控制方面,可以加上一个控制器,其可以根据URL队列中的的数据量,控制访问器的新增与减少。

 

1.2单机爬虫遇到的问题以及解决方式

  本次爬取的网站是XXX.XXXXX.XXXXX,其结构与FTP文件夹无异,需求是下载其主目录下所有的文件并保存到本地。网站的数据结构类似树。

技术分享图片

  访问到每个URL时,如果是目录,则能从html代码中解析出子级URL,如果URL指向的是文件,则下载过来。

  当然我也想直接通过XFTP连接服务器直接下载,但我没有FTP的访问地址,只有HTTP的访问地址。况且,该地址的访问,下载都是限速了的,即使通过FTP,也不能很快的下载完所有内容。

 

1.2.1网站的用户名密码认证

  该网站需要通过用户名和密码访问,才能获取到里面的内容。

  解决方式:

   在访问代码中添加用户名和密码的认证

private static String getMethod(String url) {
	String rb = null;
	HttpClient client = new HttpClient();
	GetMethod getMethod = new GetMethod(url);
	try {
	     String str = Setting.username + ":" + Setting.password; // 这是能通过认证的用户名和密码
		byte[] b = str.getBytes("utf-8");
		str = new BASE64Encoder().encode(b); // 使用base64对用户名:密码进行加密
		getMethod.setRequestHeader("Authorization", "Basic " + str); // 在请求头添加Authorization字段
																			// “Basic
																			// ”这里有个空格
		client.executeMethod(getMethod);
		rb = getMethod.getResponseBodyAsString();
	} catch (Exception e) {
		e.printStackTrace();
	}
	return rb;
}

  

1.2.2url队列内容过多,内存消耗大的问题

   在单机爬虫中,一开始使用一个queue作为url队列,每一条下载线程,在执行下载动作前从队列中获取一条url,解析完之后,将解析完的url加到队列当中。

  最早的想法是,要么queue加个size判断,size过大时,暂停一些线程?

  但是每一个下载单元的执行过程,如果该url访问的是目录,其必然会生成一大堆子级url目录的。

  之后将url队列分为2个,在生成子级URL的时候,先判断一下,如果是目录,则放到一个队列中,如果是文件,则放到另一个队列中。

技术分享图片

  通过文件URL队列的大小来控制目录解析器的执行与暂停。

  但这样的话,结构就有些繁琐了,同时需要动态的控制目录解析器和文件下载器的数量,比较麻烦。

  之后的想法是用优先级队列,通过子级URL在加入到队列中,做一个判断,如果是文件,就放到队列前面优先执行。

  URL如果是目录,执行其会使URL队列的size增加,而如果URL是文件,则执行完,其size会减少,通过文件放前面的形式,一定程度上能缓解url队列过多的问题。

技术分享图片

1.2.3下载器和解析器的目标URL识别问题

  代码无法通过URL的构成完全判断其是目录还是文件,通过后缀判断有时候会少一些东西,通过地址中的最后一个点的位置去判断,也不能保证不会出错。

  因此,URL进入到执行单元时,是应该执行下载操作还是解析操作,就可能存在一定错误执行的概率。

  我的解决方式就是,统一执行对目录URL的解析操作,如果返回值为空,则执行下载操作。从而实现下载器和解析器的统一,减少系统的复杂度。

 

1.2.4url访问或下载的超时终止实现

  对于一个url,如果其指向一个文件,而大小又特别大,比如300M,则其执行完会需要很久很久的时间,并且其会消耗大量的缓存,对于这样的操作,应该尽量避免(太大的文件一般是安装包啥的,我只想要里面的文本文件或配置文件)。

  则需要实现一个可以定时的线程,其执行一段时间,一段时间内未结束,则强行终止。

  我的执行方式比较简单粗暴。

        Thread t1;
	Thread t2;

	private void download(String nextUrl) throws Exception {
		t1 = new Thread() {
			public void run() {
				try {
					List<String> lis = Downloader.whyNotDownloadIt(nextUrl);
					if (lis.size() > 0) {
						DefaultMethod.saveAnUrl(lis);
					}
				} catch (Exception e) {
					saveAnErrorUrl(nextUrl);
				}
				t2.stop();
			}
		};
		t2 = new Thread() {
			public void run() {
				try {
					Thread.sleep(Setting.max_download_time);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				t1.stop();
				saveAnErrorUrl(nextUrl);
			}
		};
		t1.start();
		t2.start();
		t1.join();
		t2.join();
	}

  在下载任务开始时,创建两条线程,一条执行任务,一条计时,两条线程在彼此代码的尾部加上终止对方的代码,同时下载器等待2条线程的执行,从而实现定时功能。

 

以上是关于单机爬虫的逻辑以及问题解决的主要内容,如果未能解决你的问题,请参考以下文章

scrapy主动退出爬虫的代码片段(python3)

java爬虫框架webmagic学习

爬虫基本原理介绍实现以及问题解决

开源爬虫框架优缺点盘点

解析3类开源爬虫框架的优缺点

开源爬虫框架各有什么优缺点?