Java爬虫:大量抓取二手房信息并存入云端数据库过程详解(二)

——前面的博客已经讲过网页解析的问题,这里写一下关于巨量页面的解析和暂时存储
分析:
粗略计算一下,一个二手房网站有大概100页,每一页有大概30个房屋页面链接,所以运行一次要解析3000个页面,单个线程运行的解析效率很低,解析一遍可能要花几个小时,这是不能忍受的,所以在此采用了Java的多线程机制。

、、首先是要存储的房屋信息,解析页面的博客里经出现过SecondHouse类,这里附上源代码:


//用于创建房屋对象存储信息
public class SecondHouse {
	//构造函数
	public SecondHouse(String elemName, int area, int price, String community, String floor, int buildTime,
			String direction, String location, int unit_price, String houseStyle, String decoration, int viewerSum) {
		super();
		this.elemName = elemName;
		this.area = area;
		this.price = price;
		this.community = community;
		this.floor = floor;
		this.buildTime = buildTime;
		this.direction = direction;
		this.location = location;
		this.unit_price = unit_price;
		this.houseStyle = houseStyle;
		this.decoration = decoration;
		this.viewerSum = viewerSum;
	}

		//----------------房屋属性-----------//
		private String elemName;   //标题
		private int area;          //房屋面积
		private int price;         //售价
		private String community;  //小区名
		private String floor;         //楼层
		private int buildTime;     //建造时间
		private String direction;  //朝向
		private String location;   //区域
		private int unit_price;  //单价
		private String houseStyle; //户型
		private String decoration; //装修情况
		private int viewerSum;   //关注人数
		
	//属性的set和get方法

各属性的set和get方法这里就省略了。

我的思路是:每一页建立一个线程,所以100页就会有100个线程,每个线程解析30个房屋页面。

首先写一个方法返回每一页包含的房屋,一个SecondHouse数组:

//返回从贝壳网获得的房屋数据
	public SecondHouse[] getHousesOnBeiKe(int page) {
		String[] houseUrlList = null;
		try {
				//获取每一页链接
				houseUrlList = GetUrlStrList.getEershoufangUrlList(page);
				//解析每一个房屋的数据
				for (int j = 0; j < houseUrlList.length; j++) {
					SecondHouse house = null;
					try {
					house = GetUrlStrList.getErshoufangInfo(houseUrlList[j]);
					}catch (Exception e) {
						System.out.println("失败!");
						continue;
					}
					//添加
					System.out.println("成功!");
					houses.add(house);
				}
		}catch (Exception e) {
			return null;
		}
		return  houses.toArray(new SecondHouse[houses.size()]);
	}

里面的GetUrlStrList.getEershoufangUrlList(page)、GetUrlStrList.getErshoufangInfo(houseUrlList[j])这两个GetUrlList类中的方法就是上一个博客里解析房屋列表页面和房屋信息页面的方法。

另外:这个方法里面的houses是一个我在类里面定义的静态ArrayList集合,对象初始化时同时初始化集合:

//用于存储所有房屋
    static ArrayList<SecondHouse> houses;
	public HouseList() {
		super();
		houses = new ArrayList<>();
	}

因为这种静态页面的解析语句是固定的,所以解析网页的格式一旦有变化,这个爬虫就废了。但是这些批量的网页总有个别的html语句和解析语句有出入,导致产生异常,不过只是极少数情况,因此采用捕获异常就跳过这一条房源信息的解析,直接进入下一个页面,解析成功的房屋才存入houses集合里面,最后将集合类型转换成数组类型。

下一步是多线程实现:
、、开始时尝试每解析一页就将该页的房屋写入数据库,但是却在运行时出现了数据库连接异常,调试发现当多个线程同时连接一个数据库时,会出现资源竞争问题,解决方法有很多,我是在调用线程的类中设定一个存储所有房屋的集合,通过初始化线程对象时传递这个集合对象进入子线程:

调用线程对象的类:

//使用静态变量存储所有房屋元素
    static Vector<SecondHouse> houseSet;

//创建线程的实现:
//多线程集合
Vector<Thread> threads = new Vector<>();
for (int i = 1; i <= 50; i++) {
			Thread1 td1 = new Thread1(i, houseSet);
			threads.add(td1);
			td1.start();
		}

线程类:同理,页数的传递也通过构造方法实现

//前50页运行线程类
class Thread1 extends Thread{
	//用于计数页数
	int count;
	Vector<SecondHouse> houseSet;
	public Thread1(int count,Vector<SecondHouse> houseSet) {
		super();
		this.count = count;
		this.houseSet = houseSet;
	}
	//在run方法里面调用前面返回房屋数组的方法
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		super.run();
		HouseList list = new HouseList();
		SecondHouse[] houses = list.getHousesOnBeiKe(count);
		if (houses != null) {
			for (SecondHouse secondHouse : houses) {
			//这里的houseSet就是用于存储所有房屋元素的集合
				houseSet.add(secondHouse);
			}
		}
}

这种做法意味着只需要创建一次数据库链接就可以传入所有房屋元素;

PS:在主线程和子线程的运行时出现了一个较大的问题,就是子线程还未运行完成,主线程就已经运行完了,导致的结果就是,房屋解析完成,数据库链接也没问题。但是数据库里面没有写入任何信息。所以这里解决的是主线程在子线程结束后才继续运行:

//多线程集合
		Vector<Thread> threads = new Vector<>();
		for (int i = 1; i <= 50; i++) {
			Thread1 td1 = new Thread1(i, houseSet);
			threads.add(td1);
			td1.start();
		}
		//等待所有子线程结束join方法
		for (Thread thread : threads) {
			try {
				thread.join();
			} catch (InterruptedException e) {
				System.out.println("执行失败!");
			}
		}

即将创建的子线程先存入一个vector集合里面,再在主线程里面用join方法调用子线程运行,调用join方法的线程会先运行,所以主线程必须等到所有子线程结束后才能运行。

PS:
为什么用Vector集合,因为,Vector集合是线程安全的,它的方法由sychronized修饰,即多个线程使用时只能等一个线程使用完成,下一个才能继续使用。不过我考虑过用sychronized修饰数据库链接对象Connection,实现生产者消费者模式,感觉有点复杂就放弃了,应该也可以。

未解决的问题:
在考虑join方法实现子线程和主线程先后问题时,我用过线程池管理,但是用if(线程池对象.isTerminated())判断线程结束与否来决定执行下一步时,结果还是主线程先于子线程完成了,这个判断没发挥任何用处,不知道为什么,还希望大佬解释一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值