这是一篇17年记的笔记,现在笔记vip过期了,懒得充值,现在把笔记都搬到CSDN上来,所以这篇文章属于从笔记誊抄过来的,那时的老代码,可能写的不够好,但是那个时候解决了朋友的一个小小需求,大家自行取舍看是否有帮助

昨天一朋友打来电话,问能不能帮忙写个程序从一个网站上抓一些数据,然后能通过Excel汇总出来,之前他是一条一条数据去查,查出来后复制粘贴到Excle里,比较耗时耗力,他大概说了下场景,大致是这样的:
  1. 先输入车牌号,点击查询按钮,查出车辆基本信息;
    在这里插入图片描述
  2. 然后点击基本信息后面的详细按钮,弹出详情页
    在这里插入图片描述
  3. 然后详情页里的信息一一copy到excel里保存下来
  • 这样一来,如果只需要查询几个车牌的信息,那复制粘贴一下还行,如果有成百上千条车牌数据需要汇总,那真是一件令人崩溃的事情。
    了解了需求以后,首先想到了用python来写个爬虫程序,python以前学过一点皮毛,能看懂,不咋会写;Java呢是老本行,但好像很少看过有人用Java去写爬虫;
  • 网上找了些python爬网页的例子,照着操作了一下,发现能跑成功,但是好多语法不熟,写起来很慢,没有Java顺手,有的还不知道咋实现;研究一会不是很成功后,果断放弃,准备用Java来实现;
  • 度娘了一下Java爬虫,还真有很一些人用Java做了一些例子,于是综合了几个例子,确定了用到哪些技术:解析网页用Jsoup(可以将网页解析成DOM对象,解析成功后可以使用类似于操作DOM对象的各种方法),导出用poi:
  • 整理思路,先想办法读到网页上的所有有用信息,汇总起来,然后一起导出;
  • 开工,先分析网页结构:
    1. 输入车牌号,点击查询按钮后,出现查询结果,然后F12打开Google浏览器的调试模式:
      在这里插入图片描述
    2. 切换到Network,看到点击查询按钮后发出了如下请求,带了些如下参数:
      在这里插入图片描述
    3. 分析查询结果页面,有用信息全部在这个tbody节点里(如车牌号,车辆颜色等)
      在这里插入图片描述
    4. 再来分析点击详细按钮触发的事件
      在这里插入图片描述
      发现执行的是一个js方法,于是搜索该方法,发现写在一个js文件里,方法找到如下
      在这里插入图片描述
      可以看到ViewVehicleDetail('2407047', '粤BBC197', '黄色');这个方法,对应的第一个参数是id(应该是数据库里的记录id),第二个参数是车牌号,第三个参数是车牌颜色,这个方法其实也就是组装了一个a标签,带上了这些关键信息,然后做了个a标签的触发事件;点击后就弹到这条记录的详情页了:
      在这里插入图片描述
      查出来的结果全在这个tbody里
      在这里插入图片描述

原理搞清楚后,用程序实现起来就容易了
先理一下逻辑:先读到要查询的车牌号(不管读本地txt文件,还是Excle文件,或者是网页上做个输入框输入),然后一条一条模拟请求去查询,然后想办法解析出想要的数据,并且存起来,最后一起汇总导出成Excel;

  1. 建工程,用到的jar包(可以用Maven)
    在这里插入图片描述

  2. 码代码

    程序默认读取D盘根目录下的vehicle.txt,当然也可以在运行jar包的时候指定盘符,这样程序就会到指定盘符下读取;

    package com.sayc.action;
    
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.regex.Pattern;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.poi.hssf.usermodel.HSSFCell;
    import org.apache.poi.hssf.usermodel.HSSFRow;
    import org.apache.poi.hssf.usermodel.HSSFSheet;
    import org.apache.poi.hssf.usermodel.HSSFWorkbook;
    import org.jsoup.Connection;
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;
    
    /**
     * 广东省道路运输管理局车辆信息查询系统
     * @author sayc
     * @version 1.0
     */
    public class VehicleInfoQuery {
    	
    	public static void main(String[] args) {
    		System.out.println("欢迎使用广东省道路运输车辆信息查询系统V1.0!");
    		// 盘符,默认是D盘
    		String driver = "D";
    		// 如果用户自己输入盘符,则使用用户输入的盘符
    		if (args.length == 1) {
    			String userInput = args[0];
    			String regEx = "[C-Zc-z]{1}";
    			Pattern pattern = Pattern.compile(regEx);
    			if (pattern.matcher(userInput).matches()) {
    				if (userInput.equalsIgnoreCase("H")) { // Help帮助
    					System.out.println("语法:【java -jar car.jar d】 d表示d盘,代表您的文件存放在d盘根目录下,这里您可以指定您本机上的任意盘符;如有疑问,可联系sayc@foxmail.com");
    					return;
    				} else {
    					driver = userInput.toUpperCase();
    				}
    			} else {
    				System.err.println("语法有误,请重新输入,或者键入【java -jar car.jar h】获取帮助信息");
    				return;
    			}
    		}
    		List<Map<String,String>> list = new ArrayList<Map<String,String>>();
    		// 读取车牌号信息
    		String carInfos = getCarInfo(driver + ":\\vehicle.txt");
    		if (StringUtils.isNotBlank(carInfos)) {
    			if (carInfos.contains("\r\n")) {
    				Map<String,String> data = null;
    				String[] carArr = carInfos.split("\r\n");
    				for (int i = 0,len = carArr.length; i < len; i++) {
    					String chepaihao = carArr[i];
    					if (StringUtils.isNotBlank(chepaihao)) {
    						data = getCarInfos(chepaihao.trim());
    						if (data.isEmpty()) {
    							System.err.println(String.format("*****异常:查询不到车牌号为%s的相关信息*****",chepaihao));
    							continue;
    						}
    						list.add(data);
    					} else {
    						System.err.println("*****错误:车牌号为空*****");
    					}
    				}
    				// 写入数据到Excel
    				if (list != null && list.size() > 0) {
    					createExcel(driver,carArr,list);
    				}
    			}
    		} else {
    			System.err.println("*****文件找不到!*****");
    			System.out.println("请输入:【java -jar car.jar d】 指定文件所在的盘(d表示d盘,您可以随意指定且大小写不限,如须帮助,请键入【java -jar car.jar h】获取帮助信息)");
    		}
    	}
    
    	/**
    	 * 连接信息公共设置
    	 * @param conn
    	 */
    	public static void setConnection(Connection conn) {
    		conn.timeout(30000);
    		// 模拟请求头
    		conn.header("Accept", "*/*");
    		conn.header("Content-Type", "application/x-www-form-urlencoded");
    		conn.header("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36");
    	}
    	
    	/**
    	 * 根据车牌号查询出车辆的详细信息
    	 * @param chepaihao
    	 * @return 键值对形式的详细信息
    	 */
    	public static Map<String, String> getCarInfos(String chepaihao) {
    		Map<String,String> data = new HashMap<String,String>();
    		String url = "http://121.33.200.106:8010/IndustryData/Vehicle";
    		try {
    			// 获取连接
    			Connection conn = Jsoup.connect(url);
    			// conn.data(key,val):设置post请求参数
    			conn.data("chepaihao", chepaihao); // 车牌号
    			conn.data("chepaiyanse", ""); // 车牌颜色
    			// 设置connection对象属性
    			setConnection(conn);
    			// 得到Document对象
    			Document doc = conn.post();
    			// 根据网页结构分析,第二个tbody为数据区
    			Elements tbody = doc.select("tbody").eq(1);
    			Elements tds = tbody.select("tr").select("td");
    			// 车牌号
    			String carId = tds.first().text();
    			// 车辆颜色
    			String carColor = tds.eq(1).text();
    			String id = tds.last().select("a").attr("onclick").toString();
    			if (StringUtils.isNotBlank(id)) {
    				id = id.split(",")[0];
    				id = id.substring(id.indexOf("('") + 2, id.length() - 1);
    			}
    			// 请求详细信息的url地址
    			String carInfoDetailUrl = "http://121.33.200.106:8010/IndustryData/VehicleDetail";
    			conn = Jsoup.connect(carInfoDetailUrl);
    			conn.data("id",id); // 数据库该行记录的id(猜测)
    			conn.data("chepaihao",carId); // 车牌号
    			conn.data("chepaiyanse",carColor); // 车牌颜色
    			conn.data("cxqghypt","false");
    			// 设置connection对象属性
    			setConnection(conn);
    			doc = conn.post();
    			// 获取车辆详细信息
    			data = getCarInfo(doc);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		return data;
    	}
    	
    	/**
    	 * 解析Html元素,封装成键值对
    	 * @param doc
    	 * @return 键值对形式的详细信息
    	 */
    	public static Map<String, String> getCarInfo(Document doc) {
    		Map<String,String> data = new HashMap<String,String>();
    		if (doc != null) {
    			Elements tbody = doc.select("tbody").eq(0);
    			Elements trs = tbody.select("tr");
    			for (int i = 0,len = trs.size(); i < len; i++) {
    				Element tr = trs.get(i);
    				data.put(tr.select("th").eq(0).text(), tr.select("td").eq(0).select("label").eq(0).text());
    				data.put(tr.select("th").eq(1).text(), tr.select("td").eq(1).select("label").eq(0).text());
    			}
    		}
    		return data;
    	}
    
    	/**
    	 * 读取文件中的车牌号信息
    	 * @param filePath
    	 * @return 粤BBC197\r\n...车牌号加换行符的数据
    	 */
    	public static String getCarInfo(String filePath) {
    		File file = null;
    		BufferedReader reader = null;
    		StringBuffer sb = new StringBuffer();
    		try {
    			file = new File(filePath);
    			if (file.exists()) {
    				reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"));
    				String line = null;
    				while ((line = reader.readLine()) != null) {
    					sb.append(line + "\r\n");
    				}
    				return sb.toString();
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			if (reader != null) {
    				try {
    					reader.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    		return null;
    	}
    
    	/**
    	 * 创建Excel到本地
    	 * @param list 车辆信息集合
    	 */
    	@SuppressWarnings("resource")
    	public static void createExcel(String driver,String[] carArr,List<Map<String, String>> list) {
    		FileOutputStream fos = null;
    		String outPath = driver + ":\\vehicle.xls";
    		try {
    			HSSFWorkbook workbook = new HSSFWorkbook();
    			HSSFSheet sheet = workbook.createSheet("车辆详细信息");
    			String[] keys = { "车牌号", "车牌颜色", "车辆种类", "是否为运政车辆", "辖区省", "辖区市","辖区县", "辖区镇", "业户代码", "业户名称", "运营商代码", 
    					"运营商名称", "设备编码","终端类型", "首次上传时间", "最近定位时间", "最近经度", "最近纬度", "GPS安装情况","是否上传GPS数据", "年审日期", "状态" };
    			// 创建标题行
    			HSSFRow row = sheet.createRow(0);
    			HSSFCell cell = null;
    			for (int i = 0, len = keys.length; i < len; i++) {
    				cell = row.createCell(i);
    				cell.setCellValue(keys[i]);
    			}
    			// 创建数据行
    			Map<String, String> data = null;
    			for (int i = 0, len = list.size(); i < len; i++) {
    				int k = i + 1;
    				data = list.get(i);
    				row = sheet.createRow(k);
    				for (int j = 0; j < keys.length; j++) {
    					cell = row.createCell(j);
    					String detail = data.get(keys[j]);
    					cell.setCellValue(detail);
    				}
    				System.out.println(String.format("正在写入车牌号为:%s 的数据......%d", carArr[i],k));
    			}
    			fos = new FileOutputStream(outPath);
    			workbook.write(fos);
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				if (fos != null) {
    					fos.close();
    					System.out.println(String.format("成功导出%d行数据,请到%s查看",list.size(),outPath));
    				}
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    
  3. 本地跑了一下,没啥问题,打成jar包,方便发给别人运行
    在这里插入图片描述
    运行java -jar car.jar 跑了一下,功能正常,可以正常将所有信息抓取下来并存到Excel文件中
    在这里插入图片描述
    在这里插入图片描述
    打开看看数据出来了没有
    在这里插入图片描述
    数据都被抓取出来了。。
    下午发给朋友试了一下,大数据量也可以导出来,算是实现了需求,程序如有瑕疵的地方,欢迎大家一起交流或者拍砖指正!
    完。。。。。懒得写了


---------------------------------11月24号更新----------------------------------
朋友跑了两次,发现一个问题,我的程序里,【查询全国货运平台数据情况】这个默认是没打勾的,我没带这个标识过去,而且有的车是能查出多条数据的,且多条数据的结果是不一样的,这个也得导出来;

在这里插入图片描述
如下,同一个车牌号,查出来的结果页是不一样的
在这里插入图片描述
经过反馈,我觉得要把程序改动一下,查询出来有几条数据,我就把这几条数据的详细情况都给统计出来。
还有一个问题,之前没考虑到,就是大数据量一直频繁去请求,对方服务器肯定会拒绝,所以现在改了一下,每10条数据请求一次,然后停顿5秒再去查

升级后的程序如下:

package vehicleInfo;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

/**
 * 广东省道路运输管理局车辆信息查询系统
 * @author sayc
 * @version 1.1
 */
public class VehicleInfoQuery {
	
	public static void main(String[] args) {
		System.out.println("欢迎使用广东省道路运输车辆信息查询系统V1.1!");
		// 盘符,默认是D盘
		String driver = "D";
		// 如果用户自己输入盘符,则使用用户输入的盘符
		if (args.length == 1) {
			String userInput = args[0];
			String regEx = "[C-Zc-z]{1}";
			Pattern pattern = Pattern.compile(regEx);
			if (pattern.matcher(userInput).matches()) {
				if (userInput.equalsIgnoreCase("H")) { // Help帮助
					System.out.println("语法:【java -jar car.jar d】 d表示d盘,代表您的文件存放在d盘根目录下,这里您可以指定您本机上的任意盘符;如有疑问,可联系sayc@foxmail.com");
					return;
				} else {
					driver = userInput.toUpperCase();
				}
			} else {
				System.err.println("语法有误,请重新输入,或者键入【java -jar car.jar h】获取帮助信息");
				return;
			}
		}
		List<Map<String,String>> allDataList = new ArrayList<Map<String,String>>();
		// 读取车牌号信息
		String carInfos = readCarInfo(driver + ":\\vehicle.txt");
		if (StringUtils.isNotBlank(carInfos)) {
			if (carInfos.contains("\r\n")) {
				String[] carArr = carInfos.split("\r\n");
				// 存储查询出来的基础信息集合
				List<Map<String,String>> baseInfoTempList = null;
				for (int i = 0,len = carArr.length; i < len; i++) {
					// 每次查询10条,期间间隔5秒
					if (i > 0 && i % 10 == 0) {
						try {
							System.err.println("为避免请求太频繁被拒绝,停顿5秒再查询");
							Thread.sleep(5000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					String chepaihao = carArr[i];
					if (StringUtils.isNotBlank(chepaihao)) {
						System.out.println(String.format("====正在爬取车牌号为 %s 的数据====", chepaihao));
						baseInfoTempList = getCarBaseInfo(chepaihao.trim());
						if (baseInfoTempList.isEmpty()) {
							System.err.println(String.format("*****异常:查询不到车牌号为%s的相关信息*****",chepaihao));
							continue;
						}
						if (baseInfoTempList.size() > 1)
							System.out.println(String.format(">>>>>车牌号为 %s 的数据查出来有%d条<<<<<", chepaihao,baseInfoTempList.size()));
						
						allDataList.addAll(baseInfoTempList);
					} else {
						System.err.println("*****错误:车牌号为空*****");
					}
				}
				// 写入数据到Excel
				if (allDataList != null && allDataList.size() > 0) {
					System.err.println("====数据读取完毕,开始写出数据====");
					createExcel(driver,allDataList);
				}
			}
		} else {
			System.err.println("*****文件找不到!*****");
			System.out.println("请输入:【java -jar car.jar d】 指定文件所在的盘(d表示d盘,您可以随意指定且大小写不限,如须帮助,请键入【java -jar car.jar h】获取帮助信息)");
		}
	}

	/**
	 * 连接信息公共设置
	 * @param conn
	 */
	public static void setConnection(Connection conn) {
		conn.timeout(30000);
		// 模拟请求头
		conn.header("Accept", "*/*");
		conn.header("Content-Type", "application/x-www-form-urlencoded");
		conn.header("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36");
	}
	
	/**
	 * 根据车牌号查询出车辆的详细信息
	 * @param chepaihao
	 * @return 键值对形式的详细信息
	 */
	public static List<Map<String, String>> getCarBaseInfo(String chepaihao) {
		List<Map<String, String>> list = new ArrayList<Map<String,String>>();
		String url = "http://121.33.200.106:8010/IndustryData/Vehicle";
		try {
			// 获取连接
			Connection conn = Jsoup.connect(url);
			// conn.data(key,val):设置post请求参数
			conn.data("chepaihao", chepaihao); // 车牌号
			conn.data("chepaiyanse", ""); // 车牌颜色
			// 设置connection对象属性
			setConnection(conn);
			// 得到Document对象
			Document doc = conn.post();
			// 根据网页结构分析,第二个tbody为数据区
			Elements tbody = doc.select("tbody").eq(1);
			Elements trs = tbody.select("tr");
			// 存储基础信息
			Map<String,String> data = null;
			for (int i = 0,len = trs.size(); i < len; i++) {
				Elements tds = trs.eq(i).select("td");
				// 车牌号
				String carId = tds.first().text();
				// 车辆颜色
				String carColor = tds.eq(1).text();
				// 数据库主键(猜测)
				String id = tds.last().select("a").attr("onclick").toString();
				if (StringUtils.isNotBlank(id)) {
					id = id.split(",")[0];
					id = id.substring(id.indexOf("('") + 2, id.length() - 1);
				}
				// 请求详细信息的url地址
				String carInfoDetailUrl = "http://121.33.200.106:8010/IndustryData/VehicleDetail";
				conn = Jsoup.connect(carInfoDetailUrl);
				conn.data("id",id); // 数据库该行记录的id(猜测)
				conn.data("chepaihao",carId); // 车牌号
				conn.data("chepaiyanse",carColor); // 车牌颜色
				conn.data("cxqghypt","true");
				// 设置connection对象属性
				setConnection(conn);
				doc = conn.post();
				// 获取车辆详细信息
				data = getCarDetailInfo(doc);
				list.add(data);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}
	
	/**
	 * 解析Html元素,封装成键值对
	 * @param doc
	 * @return 键值对形式的详细信息
	 */
	public static Map<String, String> getCarDetailInfo(Document doc) {
		Map<String,String> data = new HashMap<String,String>();
		if (doc != null) {
			Elements tbody = doc.select("tbody").eq(0);
			Elements trs = tbody.select("tr");
			for (int i = 0,len = trs.size(); i < len; i++) {
				Element tr = trs.get(i);
				data.put(tr.select("th").eq(0).text(), tr.select("td").eq(0).select("label").eq(0).text());
				data.put(tr.select("th").eq(1).text(), tr.select("td").eq(1).select("label").eq(0).text());
			}
		}
		return data;
	}

	/**
	 * 读取文件中的车牌号信息
	 * @param filePath
	 * @return 粤BBC197\r\n...车牌号加换行符的数据
	 */
	public static String readCarInfo(String filePath) {
		File file = null;
		BufferedReader reader = null;
		StringBuffer sb = new StringBuffer();
		try {
			file = new File(filePath);
			if (file.exists()) {
				reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"));
				String line = null;
				while ((line = reader.readLine()) != null) {
					sb.append(line + "\r\n");
				}
				return sb.toString();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	/**
	 * 创建Excel到本地
	 * @param list 车辆信息集合
	 */
	@SuppressWarnings("resource")
	public static void createExcel(String driver,List<Map<String, String>> list) {
		FileOutputStream fos = null;
		String outPath = driver + ":\\vehicle.xls";
		try {
			HSSFWorkbook workbook = new HSSFWorkbook();
			HSSFSheet sheet = workbook.createSheet("车辆详细信息");
			String[] keys = { "车牌号", "车牌颜色", "车辆种类", "是否为运政车辆", "辖区省", "辖区市","辖区县", "辖区镇", "业户代码", "业户名称", "运营商代码", 
					"运营商名称", "设备编码","终端类型", "首次上传时间", "最近定位时间", "最近经度", "最近纬度", "GPS安装情况","是否上传GPS数据", "年审日期", "状态" };
			// 创建标题行
			HSSFRow row = sheet.createRow(0);
			HSSFCell cell = null;
			for (int i = 0, len = keys.length; i < len; i++) {
				cell = row.createCell(i);
				cell.setCellValue(keys[i]);
			}
			// 创建数据行
			Map<String, String> data = null;
			for (int i = 0, len = list.size(); i < len; i++) {
				int k = i + 1;
				data = list.get(i);
				row = sheet.createRow(k);
				for (int j = 0; j < keys.length; j++) {
					cell = row.createCell(j);
					String detail = data.get(keys[j]);
					detail = StringUtils.isBlank(detail) ? "--" : detail;
					cell.setCellValue(detail);
				}
				System.out.println(String.format("正在写入车牌号为:%s 的数据......%d", data.get(keys[0]),k));
			}
			fos = new FileOutputStream(outPath);
			workbook.write(fos);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (fos != null) {
					fos.close();
					System.out.println(String.format("成功导出%d行数据,请到%s查看",list.size(),outPath));
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐