Java:基于Java的爬虫程序示例
·
这是一篇17年记的笔记,现在笔记vip过期了,懒得充值,现在把笔记都搬到CSDN上来,所以这篇文章属于从笔记誊抄过来的,那时的老代码,可能写的不够好,但是那个时候解决了朋友的一个小小需求,大家自行取舍看是否有帮助
昨天一朋友打来电话,问能不能帮忙写个程序从一个网站上抓一些数据,然后能通过Excel汇总出来,之前他是一条一条数据去查,查出来后复制粘贴到Excle里,比较耗时耗力,他大概说了下场景,大致是这样的:
- 先输入车牌号,点击查询按钮,查出车辆基本信息;

- 然后点击基本信息后面的详细按钮,弹出详情页

- 然后详情页里的信息一一copy到excel里保存下来
- 这样一来,如果只需要查询几个车牌的信息,那复制粘贴一下还行,如果有成百上千条车牌数据需要汇总,那真是一件令人崩溃的事情。
了解了需求以后,首先想到了用python来写个爬虫程序,python以前学过一点皮毛,能看懂,不咋会写;Java呢是老本行,但好像很少看过有人用Java去写爬虫;
- 网上找了些python爬网页的例子,照着操作了一下,发现能跑成功,但是好多语法不熟,写起来很慢,没有Java顺手,有的还不知道咋实现;研究一会不是很成功后,果断放弃,准备用Java来实现;
- 度娘了一下Java爬虫,还真有很一些人用Java做了一些例子,于是综合了几个例子,确定了用到哪些技术:解析网页用Jsoup(可以将网页解析成DOM对象,解析成功后可以使用类似于操作DOM对象的各种方法),导出用poi:
- 整理思路,先想办法读到网页上的所有有用信息,汇总起来,然后一起导出;
- 开工,先分析网页结构:
- 输入车牌号,点击查询按钮后,出现查询结果,然后F12打开Google浏览器的调试模式:

- 切换到Network,看到点击查询按钮后发出了如下请求,带了些如下参数:

- 分析查询结果页面,有用信息全部在这个tbody节点里(如车牌号,车辆颜色等)

- 再来分析点击详细按钮触发的事件

发现执行的是一个js方法,于是搜索该方法,发现写在一个js文件里,方法找到如下

可以看到ViewVehicleDetail('2407047', '粤BBC197', '黄色');这个方法,对应的第一个参数是id(应该是数据库里的记录id),第二个参数是车牌号,第三个参数是车牌颜色,这个方法其实也就是组装了一个a标签,带上了这些关键信息,然后做了个a标签的触发事件;点击后就弹到这条记录的详情页了:

查出来的结果全在这个tbody里

- 输入车牌号,点击查询按钮后,出现查询结果,然后F12打开Google浏览器的调试模式:
原理搞清楚后,用程序实现起来就容易了
先理一下逻辑:先读到要查询的车牌号(不管读本地txt文件,还是Excle文件,或者是网页上做个输入框输入),然后一条一条模拟请求去查询,然后想办法解析出想要的数据,并且存起来,最后一起汇总导出成Excel;
-
建工程,用到的jar包(可以用Maven)

-
码代码
程序默认读取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(); } } } } -
本地跑了一下,没啥问题,打成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();
}
}
}
}
更多推荐
所有评论(0)