之前使用内存数据结构(队列或者链表)来实现爬虫队列,但是在一些大型的搜索引擎中有十几亿的URL需要抓取。因此,内存数据结构并不适用于这些应用,最适合的一种方法是使用内存数据库,或者直接使用数据库来存储这些URL。本节讲的是一种非常流行的内存数据库——Berkeley DB。


爬虫队列的特点

  • 能够存储海量数据,当数据超出内存限制的时候,能够固化在硬盘上
  • 存取数据速度非常快
  • 能够支持多线程访问
    综上,Hash表成为存储结构的不二选择。通常在进行Hash存储的时候Key值都是url字符串,但是为了更加节省空间,通常会对URL进行压缩。常用的压缩方法是MD5压缩算法【MD5算法介绍】。

    MD5算法的简单例子

public class MD5 {public static String getMD5(byte[] source) {String s = null;//将字节转换成十六进制表示的字符char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9','a', 'b', 'c', 'd', 'e', 'f'};try {java.security.MessageDigest md =java.security.MessageDigest.getInstance("MD5");md.update(source);//MD5的计算结果是一个128位的长整数,用字节表示就是16个字节byte temp[] = md.digest();//每个字节用十六进制表示的话,使用两个字符,所以表示成十六进制需要32个字符char str[] = new char[16 * 2];//k 表示转换结果中对应的字符的位置int k = 0;//从第一个字节开始,将MD5的每一个字节转换成十六进制字符for (int i = 0; i < 16; i++) {//取出第i个字节byte byte0 = temp[i];//取字节中高4位的数字转换,>>>逻辑右移,符号位跟随移动str[k++] = hexDigits[byte0 >>> 4 & 0xf];//取字节中的低4位的柱子转换str[k++] = hexDigits[byte0 & 0xf];}s = new String(str);} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return s;}
}

测试代码:

public class Main {public static void main(String[] args) {MD5 md =new MD5();String source="www.baidu.com";byte byt[]=source.getBytes();String result=md.getMD5(byt);System.out.println(result);}
}

输出结果

网页上免费的MD5散列数计算器 输入字符串“www.baidu.com”之后的结果

使用Berkeley DB构建爬虫队列

Hash存储的Value值通常会对URL和相关的信息进行封装,封装成为一个对象进行存储。
我们需要选择一个线程安全、使用Hash存储,并且能够应对海量数据的内存数据库是存储URL最合适的数据结构。

Berkeley DB简介

Berkeley DB是一个嵌入式数据库,他适合于管理海量的、简单的数据。Berkeley DB不能取代关系型数据库,但在某一些方面,他却令关系型数据库望尘莫及。
Key/Value(关键字/值)是Berkeley DB用来进行数据库管理的基础。每个Key/Value对(键值对)构成一条记录。而整个数据库实际上就是由许多这样的结构单元所构成的。通过这种方式,开发人员在使用Berkeley DB提供的API访问数据库时,只需提供关键字就能够访问到相应的数据。当然也可以提供key和部分Data来查询符合条件的相近数据。
Berkeley DB底层实现采用B树,可以看成能够存储大量数据的HashMap。Berkeley DB简称BDB。它的C++版本首先出现,之后在这个基础上实现了Java的本地版本。

Berkeley DB的使用

创建环境

Berkeley DB是通过环境对象EnvironmentConfig来对数据库进行管理的,每个EnvironmentConfig对象可以管理多个数据库。新建一个EnvironmentConfig对象的代码如下:

EnvironmentConfig envConfig=new EnvironmentConfig();envConfig.setTransactional(false);envConfig.setAllowCreate(true);/*** envDir 是用户指定的一个目录。只要是由同一个* EnvironmentConfig指定的数据库的数据文件和日志文件,都会放在这个目录下* EnvironmentConfig也是一种资源,当使用完毕后,需要关闭。*/exampleEnv=new Environment(enDir,envConfig);exampleEnv.sync();exampleEnv.close();exampleEnv.null;

创建数据库

创建好环境以后,就可以用他来创建数据库了。用Berkeley DB创建数据库时,需要指定数据库的属性,就好比在Oracle中创建数据库中要指定java_pool、buffer_size等属性一样。Berkeley DB 使用DatabaseConfig来管理一个具体的DataBase:

String databaseName="ToDoTaskList.db";DatabaseConfig dbConfig=new DatabaseConfig();dbConfig.setAllowCreate(true);dbConfig.setTransactional(false);/*** 打开用来存储类信息的数据库* 用来存储类信息的数据库 不要求能够存储重复的关键字*/dbConfig.setSortedDuplicates(false);Database myClassDb=exampleEnv.openDatabase(null,"classDb",dbConfig);//初始化用来存储序列化对象的catalog类catalog =new StoreClassCatalog(myClassDb);TupleBinding keyBinding =TupleBinding.getPrimitiveBinding(String.class);//把Value作为对象的序列化方式存储SerialBinding valueBinding=new SerialBinding(catalog,NewsSource.class);store=exampleEnv.openDatabase(null,databaseName,dbConfig);

确定存储的数据类型

当数据库建立起来以后,就要确定网数据库里面存储的数据类型(也就是确定key和value的值)。Berkeley DB 数据类型是使用EntryBinding对象来确定的。

/***其中SerialBinding表示这个对象能够序列化到磁盘上,因此,构造函数的第二个参数一定要实现了序列化接口的对象。*/
EntryBinding keyBinding =new SerialBinding(javaCatalog,String.class);

创建一个以Berkeley DB为底层数据结构的Map:

//创建数据存储的Mapthis.map=new StoredSortedMap(store,keyBinding,valueBinding,true);

使用Berkeley DB构建爬虫队列示例

首先Berkeley DB存储是一个Key/Value的结构,并且key和value对象都要实现Java序列化接口。因此,我们先来构建value对象,即一个封装了很多重要属性的URL类。

Berkeley DB存储的value类

public class CrawlUrl implements Serializable {private static final long serialVersionUID = 7931672194843948629L;public CrawlUrl() {}private String oriUrl;//原始URL的值,主机部分是域名private String url;//URL的值,主机部分是IP,为了防止重复主机的出现private int urlNo;//URL NUMprivate int statusCode;//获取URL返回的结果码private int hitNum;//此URL被其他文章引用的次数private String charSet;//此URL对用文章的汉字编码private String abstractText;//文章摘要private String author;//作者private int weight;//文章的权重(包含导向词的信息)private String description;//文章的描述private int fileSize;//文章大小private Sspi.TimeStamp lastUpdateTime;//最后修改时间private Date timeToLive;//过期时间private String title;//文章名称private String type;//文章类型private String[] urlReference;//引用的链接private int layer;//爬取的层次,从种子开始一次为第0层,第1层...public int getLayer() {return layer;}public void setLayer(int layer) {this.layer = layer;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public int getUrlNo() {return urlNo;}public void setUrlNo(int urlNo) {this.urlNo = urlNo;}public int getStatusCode() {return statusCode;}public void setStatusCode(int statusCode) {this.statusCode = statusCode;}public int getHitNum() {return hitNum;}public void setHitNum(int hitNum) {this.hitNum = hitNum;}public String getCharSet() {return charSet;}public void setCharSet(String charSet) {this.charSet = charSet;}public String getAbstractText() {return abstractText;}public void setAbstractText(String abstractText) {this.abstractText = abstractText;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public int getWeight() {return weight;}public void setWeight(int weight) {this.weight = weight;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public int getFileSize() {return fileSize;}public void setFileSize(int fileSize) {this.fileSize = fileSize;}public Sspi.TimeStamp getLastUpdateTime() {return lastUpdateTime;}public void setLastUpdateTime(Sspi.TimeStamp lastUpdateTime) {this.lastUpdateTime = lastUpdateTime;}public Date getTimeToLive(){return timeToLive;}public void setTimeToLive(Date timeToLive){this.timeToLive=timeToLive;}public String getTitle(){return title;}public void setTitle(String title){this.title=title;}public String getType(){return type;}public void setType(String type){this.type=type;}public String[] getUrlReference(){return urlReference;}public void setUrlReference(String[] urlReference){this.urlReference=urlReference;}public final String getOriUrl(){return oriUrl;}public void setOriUrl(String oriUrl){this.oriUrl=oriUrl;}
}

TODO 表的接口

public interface Frontier {public CrawlUrl getNext()throws Exception;public boolean putUrl(CrawlUrl url)throws Exception;
}

使用一个抽象类来封装对数据库Berkeley DB的操作

public abstract class AbstractFrontier {private Environment env;private static final String CLASS_CATALOG = "java+class_catalog";protected StoredClassCatalog javaCatalog;protected Database catalogdatabase;protected Database database;public AbstractFrontier(String homeDirectory)throws DatabaseException, FileNotFoundException {//打开envSystem.out.println("Openning environment in:" + homeDirectory);EnvironmentConfig envConfig = new EnvironmentConfig();envConfig.setTransactional(true);envConfig.setAllowCreate(true);env = new Environment(new File(homeDirectory), envConfig);//设置DatabaseConfigDatabaseConfig dbConfig = new DatabaseConfig();dbConfig.setTransactional(true);dbConfig.setAllowCreate(true);//打开catalogdatabase = env.openDatabase(null, CLASS_CATALOG, dbConfig);javaCatalog = new StoredClassCatalog(catalogdatabase);//设置DatabaseConfigDatabaseConfig dbConfig0 = new DatabaseConfig();dbConfig0.setTransactional(true);dbConfig0.setAllowCreate(true);//打开database = env.openDatabase(null, "URL", dbConfig);}//关闭数据库,关闭环境public void close() throws DatabaseException {database.close();javaCatalog.close();env.close();}//put方法protected abstract void put(Object key, Object value);//get方法protected abstract Object get(Object key);//delete方法protected abstract Object delete(Object key);
}

实现真正的TODO表

public class BDBFrontierextends AbstractFrontier implements Frontier {private StoredMap pendingUrisDB = null;//使用默认的路径和缓存大小构造函数public BDBFrontier(String homeDirectory)throws DatabaseException, FileNotFoundException {super(homeDirectory);EntryBinding keyBnding = new SerialBinding(javaCatalog, String.class);EntryBinding valueBinding = new SerialBinding(javaCatalog, CrawlUrl.class);pendingUrisDB = new StoredMap(database, keyBnding, valueBinding, true);}//获得下一条记录@Overridepublic CrawlUrl getNext() throws Exception {CrawlUrl result = null;if (!pendingUrisDB.isEmpty()) {Set entrys = pendingUrisDB.entrySet();System.out.println(entrys);Map.Entry<String, CrawlUrl> entry =(Map.Entry<String, CrawlUrl>) pendingUrisDB.entrySet().iterator().next();result = entry.getValue();delete(entry.getKey());}return result;}//存入URL@Overridepublic boolean putUrl(CrawlUrl url) throws Exception {put(url.getOriUrl(), url);return true;}//存入数据库的方法@Overrideprotected void put(Object key, Object value) {pendingUrisDB.put(key, value);}//取出@Overrideprotected Object get(Object key) {return pendingUrisDB.get(key);}//删除@Overrideprotected Object delete(Object key) {return pendingUrisDB.remove(key);}//根据URL计算键值,可以使用各种压缩算法,包括MD5private String caculateUrl(String url) {return url;}
}

测试数据

public class Main {//测试函数public static void main(String[] args) {try {BDBFrontier bdbFrontier = new BDBFrontier("c:\\bdb");//需要在C盘下新建文件夹bdbCrawlUrl url = new CrawlUrl();url.setOriUrl("http://www.163.com");bdbFrontier.putUrl(url);System.out.println(((CrawlUrl) bdbFrontier.getNext()).getOriUrl());bdbFrontier.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}
}

输出结果

以上就是使用Berkeley DB来实现TODO表的具体过程。

《自己动手写网络爬虫》笔记5-设计爬虫对列相关推荐

  1. Python 网络爬虫笔记9 -- Scrapy爬虫框架

    Python 网络爬虫笔记9 – Scrapy爬虫框架 Python 网络爬虫系列笔记是笔者在学习嵩天老师的<Python网络爬虫与信息提取>课程及笔者实践网络爬虫的笔记. 课程链接:Py ...

  2. 记录《自己动手写网络爬虫 》书中涉及的内容学习一些算法

    第1篇  自己动手抓取数据 第1章  全面剖析网络爬虫 3 1.1  抓取网页 4 1.1.1  深入理解URL 4 1.1.2  通过指定的URL抓取 网页内容 6 1.1.3  Java网页抓取示 ...

  3. 自己动手写Docker学习笔记

    零.前言 本文为<自己动手写 Docker>的学习,对于各位学习 docker 的同学非常友好,非常建议买一本来学习. 书中有摘录书中的一些知识点,不过限于篇幅,没有全部摘录 (主要也是懒 ...

  4. 自己动手写网络抓包工具

    看了太多的"自己动手",这次咱也"自己动手"一下,写个简单的网络抓包工具吧.要写出像tcpdump和wireshark(ethereal)这样的大牛程序来,咱也 ...

  5. 【爬虫笔记】Scrapy爬虫技术文章网站

    文章目录 一.Xpath 1.xpath简介 2.xpath语法 二.CSS选择器 三.爬取伯乐在线--初级 1.创建Scrapy项目 2.编写item.py文件 3.编写spider文件 4.编写p ...

  6. 丑憨批的爬虫笔记2(爬虫引发的问题+robots协议)

    去搜 user-agent!!!! referer!!!!! 网页中怎么查看请求头header信息 点一下Name里的东西就会出来 规模大小分类 robots协议 User-agent: * /// ...

  7. Python 网络爬虫笔记11 -- Scrapy 实战

    Python 网络爬虫笔记11 – Scrapy 实战 Python 网络爬虫系列笔记是笔者在学习嵩天老师的<Python网络爬虫与信息提取>课程及笔者实践网络爬虫的笔记. 课程链接:Py ...

  8. ctf up怎么写 write_??零基础写网络爬虫的思路??

    网络爬虫,用一句话简单总结,就是一种按照一定的规则,自动的抓取万维网信息的程序或者脚本.写这篇文章的初衷是有个知友私信我说,模仿了很多网上用Python写爬虫的例子,但到了需要自己动手写爬虫的时候又不 ...

  9. 《自己动手写操作系统》读书笔记——初识保护模式

    <自己动手写操作系统>读书笔记--初识保护模式 http://www.cnblogs.com/pang123hui/archive/2010/11/27/2309930.html 书本第三 ...

  10. php怎么自己写框架,PHP学习笔记,自己动手写个MVC的框架

    最新在大家自己的博客的过程中,发现各种开源的博客系统都或多或少的用起来别扭.于是想动手自己写个博客系统.既然写,就想好好写.那就先写个MVC框架.一点一点来.写的过程中有很多想法.还希望大家能够多多指 ...

最新文章

  1. 基于DDD的.NET开发框架 - ABP初探
  2. mysql 1449 : The user specified as a definer ('root'@'%') does not exist 解决方法
  3. 使用请求头认证来测试需要授权的 API 接口
  4. sort()函数、C++
  5. C++之泛型编程(模板)
  6. Memcache安装 2
  7. TIOBE 6 月编程语言排行榜:Perl 成为 Python 过分炒作的牺牲品?
  8. 操作系统—死锁的检测和解除
  9. Dart基础第1篇:Dart环境搭建、Dart开发工具
  10. 项目日报模板_能力再强也要常向领导汇报工作,掌握万能模板,不做职场小透明...
  11. linux开发板推荐
  12. JS学习之路系列总结二阴阳阵(此文犹如武林之中的易筋经,是你驰骋IT界的武功心法,学会JS五大阵法就学会了JS,博主建议先学三才阵)...
  13. crack.vbs病毒,u盘里的所有文件全部变成快捷方式
  14. 【科普】HDMI vs DisplayPort vs DVI 傻傻分不清楚
  15. 开源协议(Open Source License)
  16. 人才招聘网站设计代码(毕业设计和课程设计)
  17. Inception V1:Going deeper with convolutions
  18. 2020大一下期学期计划
  19. TeXLive 2020 下载与安装
  20. Ubuntu不能打开exfat格式的U盘

热门文章

  1. python方差分析_R语言方差分析ANOVA
  2. 本特利990-05-50-02-00变送器
  3. android onitemclick参数,Android里的AdapterView中OnItemClickListener监听器四个参数的含义...
  4. 最新SSD固态硬盘颗粒QLC、SLC、MLC、TLC详解
  5. 集合框架之Connection(马士兵教育视频教程总结笔记)
  6. 零时科技创始人邓永凯先生受邀出席中国创交会之科创湾区创新论坛
  7. 基于Rasa_NLU的微信chatbot
  8. 2017年3月4月无人机航空摄影总结
  9. 【U盘量产工具】热插拔导致U盘进入写保护——安国主控AU6989SN-GT
  10. LabView-之1: 串口驱动