Java网页数据采集器实例教程[续篇-远程操作]
本期概述
上期我们讲到了html页面采集后的数据查询, 但是这仅仅是在本地查询数据库,如果我们想通过远程 来进行数据的采集,存储和查询,那又该怎么做呢?
今天我们一起来学习下 如何通过本地客户端 远程访问服务器 进行数据的采集,存储和查询.
数据采集页面 2011-2012赛季英超球队战绩
学习简单远程访问(RMI实例)
首先我们学习下简单的 客户端 远程访问 服务器 例子
这里用到了 Java RMI (Remote Method Invocation)
Java RMI是一种机制, 是能够让一个 java虚拟机 调用 另一个java 虚拟机上对象的方法, 从而实现远程访问.
但是通过客户端实现该远程访问 必须绑定一个远程接口对象.
好了 我们来写下例子代码
定义远程接口
首先 我们写个远程接口 HelloInterface 该接口继承了远程对象Remote
接口HelloInterface 里面有个sayHello的方法 用于客户端连接后 打招呼
由于sayHello方法 继承了远程Remote对象 所以需要抛一个 RemoteException 远程异常
package Remote_Interface;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 接口HelloInterface 继承了 远程接口 Remote 用于客户端Client远程调用
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
public interface HelloInterface extends Remote{
public String sayHello(String name) throws RemoteException;
}
实现接口(实现接口在服务端)
接下来 我们实现下 该接口里的方法 实现接口的方法在服务端
通过 HelloInterfaceImpl类 实现了接口HelloInterface里的方法
注意: 这里HelloInterfaceImpl 同样继承了 UnicastRemoteObject 远程对象 这个必须写 虽然不写 代码智能提示不会提示有错 但服务器启动后会莫名其妙报错
由于 UnicastRemoteObject 远程对象 需要抛出一个RemoteException 异常 所以 这里用了一个构造方法 HelloInterfaceImpl() 来抛这个异常
package Server;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import Remote_Interface.HelloInterface;
/**
* HelloInterfaceImpl 用于实现 接口HelloInterface 的远程 SayHello方法
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
@SuppressWarnings("serial")
// 继承 UnicastRemoteObject 远程对象 这个一定要写 否则 服务器启动报异常
public class HelloInterfaceImpl extends UnicastRemoteObject implements HelloInterface{
//HelloInterfaceImpl的构造方法 用于抛出UnicastRemoteObject 远程对象里的异常
protected HelloInterfaceImpl() throws RemoteException {
}
public String sayHello(String name) throws RemoteException {
//该信息 在客户端上发出
String strHello = "你好! " + name+" 欢迎访问服务器!";
//这条信息 是在服务器上 打印出来
System.out.println(name +" 正在 访问本服务器!");
return strHello;
}
}
编写服务端
接下来 我们来写 服务端 由于RMI 实现远程访问的机制是指 客户端通过在RMI注册表上寻找远程接口对象的地址(服务端地址) 达到实现远程访问的目的
所以这里 我们需要在 服务端 创建一个远程对象的注册表 用于绑定和注册 服务器端地址和远程接口对象 便于后期客户端能够成功找到服务端 (详细请看代码注释)
package Server;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import Remote_Interface.HelloInterface;
/**
* Server 类 用于 启动 注册服务器
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
public class Server {
public static void main(String[] args) {
try {
// 定义远程接口HelloInterface 对象 用于绑定在服务器注册表上 该接口由HelloInterfaceImpl()类实现
HelloInterface hInterface = new HelloInterfaceImpl();
int port = 6666; // 定义一个端口号
// 创建一个接受对特定端口调用的远程对象注册表 注册表上需要接口一个指定的端口号
LocateRegistry.createRegistry(port);
// 定义 服务器远程地址 URL格式
String address = "rmi://localhost:" + port + "/hello";
// 绑定远程地址和接口对象
Naming.bind(address,hInterface);
// 如果启动成功 则弹出如下信息
System.out.println(">>>服务器启动成功");
System.out.println(">>>请启动客户端进行连接访问");
} catch (MalformedURLException e) {
System.out.println("地址出现错误!");
e.printStackTrace();
} catch (AlreadyBoundException e) {
System.out.println("重复绑定了同一个远程对象!");
e.printStackTrace();
} catch (RemoteException e) {
System.out.println("创建远程对象出现错误!");
e.printStackTrace();
}
}
}
编写客户端
服务器写完了 我们来写下访问服务器需要的客户端
客户端上同样 需要定义一个 远程访问的地址 - 即服务器地址
然后 通过在RMI注册表上寻找 该地址; 如果找到 则建立连接.
package Client;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import Remote_Interface.HelloInterface;
/**
* Client 用于连接 并访问 服务器Server
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
public class Client {
public static void main(String[] args) {
// 定义一个端口号 该端口号必须与服务器的端口号相同
int port = 6666;
// 同样定义一个远程地址 该地址为服务器的远程地址 所以 与服务器的地址是一样的
String address = "rmi://localhost:" + port + "/hello";
// 在RMI注册表上需找 对象为HelloInterface的地址 即服务器地址
try {
HelloInterface hInterface = (HelloInterface) Naming.lookup(address);
// 一旦客户端找到该服务器地址 则 进行连接
System.out.println("<<<客户端访问成功!");
//客户端 Client 调用 远程接口里的 sayHello 方法 并打印出来
System.out.println(hInterface.sayHello("SoFlash"));
} catch (MalformedURLException e) {
System.out.println("错误的地址!");
e.printStackTrace();
} catch (RemoteException e) {
System.out.println("创建远程对象出错!");
e.printStackTrace();
} catch (NotBoundException e) {
System.out.println("未绑定的远程对象!");
e.printStackTrace();
}
}
}
运行程序
首先启动服务器 这里用的是 导出的服务器 jar文件
然后启动客户端 访问
例子介绍完了 你是否明白了呢? :p
附上例子 源代码 下载 Remote_Hello.zip
jar文件 客户端 Hello_Client_jar.zip 服务端 Hello_Client_jar.zip
远程数据采集实现
定义远程接口
例子学完 那么让我们来 一起实现下 远程访问服务器 实现 足球网站的数据采集
首先 我们写个 远程 接口 DataQueryInterface 它继承了 远程对象Remote 里面包含了一些空方法 在服务器的DataQueryImpl类里面实现
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
package Remote_Interface;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Vector;
/**
* 接口 DataQueryInterface 继承了 远程接口 Remote 用于远程访问
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
public interface DataQueryInterface extends Remote {
public void initialDataBase() throws RemoteException;
public void dataCollectAndStore() throws RemoteException;
public Vector<String> getAllTeams() throws RemoteException;
public Vector<String> querySpecifiedTeam(String league) throws RemoteException;
public List<String> queryByDate(String date) throws RemoteException;
}
编写服务端(服务端包含了接口的实现)
接口完成了 我们来写下 服务器 代码
Server 类 主函数 绑定了远程接口 (详细介绍 请看代码注释)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
package Server;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import Remote_Interface.DataQueryInterface;
/**
* Server 类 用于启动服务器
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
public class Server {
/**
* 服务器上主函数 用于启动服务器
*/
public static void main(String[] args) {
try {
//定义远程接口DataQueryInterface对象 用于绑定在服务器上 该接口由 DataQueryImpl()类实现
DataQueryInterface dqi = new DataQueryImpl();
int port = 12345; //定义一个端口号
//创建一个接受对特定端口调用的远程对象注册表 注册表上需要接口一个指定的端口号
LocateRegistry.createRegistry(port);
//定义 服务器远程地址 URL格式
String address = "rmi://localhost:" + port + "/RemoteQuery";
//绑定远程地址和接口对象
Naming.bind(address,dqi);
//如果启动成功 则弹出如下信息
System.out.println(">>>服务器启动成功");
System.out.println(">>>请启动客户端进行连接访问");
} catch (MalformedURLException e) {
System.out.println("地址出现错误!");
e.printStackTrace();
} catch (AlreadyBoundException e) {
System.out.println("重复绑定了同一个远程对象!");
e.printStackTrace();
} catch (RemoteException e) {
System.out.println("创建远程对象出现错误!");
e.printStackTrace();
}
}
}
DataCollectionAndStorage 类 以及 dataCollectAndStore() 方法 用于收集和存储数据
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
实现接口(在服务端)
DataQueryImpl 类 用于实现接口DataQueryInterface 以及里面的所有方法 (具体请看代码及注释)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
package Server;
import Remote_Interface.DataQueryInterface;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
/**
* DataQueryImpl类 用于实现 接口 DataQueryInterface
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
@SuppressWarnings("serial")
public class DataQueryImpl extends UnicastRemoteObject implements
DataQueryInterface {
/**
* DataQueryImpl类的空构造方法
* 这个构造方法一定要写 因为继承 UnicastRemoteObject类 需要抛出了一个远程异常
* 这里使用 DataQueryImpl类 来抛这个异常
* @throws RemoteException
*/
public DataQueryImpl() throws RemoteException {
}
/**
* 因为实现了 远程接口DataQueryInterface里的方法 所以需要抛出一个远程异常 以下所有接口里的方法同理
* 用于清空数据库里比赛结果
*/
public void initialDataBase() throws RemoteException {
String initialSql = "delete from premiership";
MySql ms = new MySql();
try {
// MySql类里 提供了一个 删除数据的 方法 executeUpdate()
ms.datatoMySql(initialSql);
System.out.println("数据库清空成功!");
} catch (Exception e) {
System.out.println("数据库清空失败!");
}
}
/**
* 因为这里只用了一个远程接口 DataQueryInterface
* 所以实现该接口的类DataQueryImpl 需要实施所有的方法
* 这里调用了 DataCollectionAndStorage类里收集和存储数据的方法
*/
public void dataCollectAndStore() throws RemoteException {
DataCollectionAndStorage ds = new DataCollectionAndStorage();
ds.dataCollectAndStore();
}
/**
* 用于 获取当前数据库里一共有多少球队 方便后期查看自己喜欢的球队比赛成绩
* @return 所有的参与过比赛的球队名
*/
public Vector<String> getAllTeams() throws RemoteException {
// 使用一个向量来 存取 从数据库中读到的值
Vector<String> vecAllTeams = new Vector<String>();
String allteamsSql = "select HomeTeam,AwayTeam from premiership group by HomeTeam;";
ResultSet rs = null;
MySql ms = new MySql();
// 调用 MySql类里 查看数据的方法
rs = ms.queryMySql(allteamsSql);
try {
// 如果 ResultSet数据集里的数据不为空 则获取相应的 数据 添加到 向量vecAllTeams里
while (rs.next()) {
if (!vecAllTeams.contains(rs.getString("HomeTeam")))
vecAllTeams.add(rs.getString("HomeTeam"));
else if (!vecAllTeams.contains(rs.getString("AwayTeam")))
vecAllTeams.add(rs.getString("AwayTeam"));
}
} catch (SQLException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
// 返回 取到的所有球队名
return vecAllTeams;
}
/**
* 查看具体的球队比赛结果
* @param league
* @return 具体球队的所有比赛结果
*/
public Vector<String> querySpecifiedTeam(String league) {
// 创建一个向量 来装载 从数据库中 读到的数据
Vector<String> lsMatches = new Vector<String>();
String specifiedTeamSql = "select * from premiership where HomeTeam ='"
+ league + "' or AwayTeam ='" + league + "'";
MySql ms = new MySql();
ResultSet rs = null;
rs = ms.queryMySql(specifiedTeamSql);
try {
while (rs.next()) {
lsMatches.add(rs.getString("Date"));
lsMatches.add(rs.getString("HomeTeam"));
lsMatches.add(rs.getString("AwayTeam"));
lsMatches.add(rs.getString("Result"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return lsMatches;
}
/**
* 查看 某一天的 比赛结果
* @param date
* @return 某一天的所有比赛结果
*/
public List<String> queryByDate(String date) throws RemoteException {
// 使用一个 list泛型来装载 比赛结果
List<String> lsMatchesOnDate = new ArrayList<String>();
String sqlDate = "SELECT * FROM premiership WHERE Date ='" + date + "'";
// 创建一个ResultSet数据集 用来 获取查询到的结果集
ResultSet rs = null;
MySql ms = new MySql();
// 调用 MySql 类里的 查看数据库数据的方法
rs = ms.queryMySql(sqlDate);
try {
// 如果 ResultSet数据集 不为空
while (rs.next()) {
// 则 从ResultSet数据集 中取出 相应的 字段值 添加到 list泛型里
lsMatchesOnDate.add(rs.getString("Date"));
lsMatchesOnDate.add(rs.getString("HomeTeam"));
lsMatchesOnDate.add(rs.getString("AwayTeam"));
lsMatchesOnDate.add(rs.getString("Result"));
}
} catch (SQLException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
// 最后 返回 取到的所有比赛数据结果
return lsMatchesOnDate;
}
}
DataStructure类 简单的数据结构 用于页面采集到的数据 临时性的存储
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
package Server;
/**
* DataStructure 类 一个简单的数据结构
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
public class DataStructure {
//定义数据字段
public String homeTeam;
public String awayTeam;
public String date;
public String result;
}
GroupMethod类 以及 regularGroup()方法 用于匹配 并抓取 html 页面上需要的数据
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
package Server;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* GroupMethod 类 用于匹配和抓取 html页面的数据
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
public class GroupMethod {
// 传入2个字符串参数 一个是pattern(我们使用的正则) 另一个matcher是html源代码
public String regularGroup(String pattern, String matcher) {
Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(matcher);
if (m.find()) { // 如果读到
return m.group();// 返回捕获的数据
} else {
return ""; // 否则返回一个空值
}
}
}
MySql类 用于实施 MySql数据库操作 里面包含了2个方法
- datatoMySql() 方法 用于向数据库里插入数据
- queryMySql() 方法 用于查询数据
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
package Server;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* MySql类用于实施MySql数据库操作
* @author SoFlash - 博客园 http://www.cnblogs.com/longwu
*/
public class MySql {
// 定义MySql驱动,数据库地址,数据库用户名 密码, 执行语句和数据库连接
public String driver = "com.mysql.jdbc.Driver";
public String url = "jdbc:mysql://127.0.0.1:3306/htmldatacollection";
public String user = "root";
public String password = "root";
public Statement stmt = null;
public Connection conn = null;
/**
* 创建一个插入数据的方法 executeUpdate()
* @param insertSQl
*/
public void datatoMySql(String insertSQl) {
try {
try {
Class.forName(driver).newInstance();
} catch (Exception e) {
System.out.println("无法找到驱动器");
e.printStackTrace();
}
// 创建连接
conn = DriverManager.getConnection(url, user, password);
// 创建一个 Statement 对象来将 SQL 语句发送到数据库
stmt = conn.createStatement();
// 执行SQL 插入语句
stmt.executeUpdate(insertSQl);
// 执行完 停止执行语句
stmt.close();
// 执行完关闭数据库连接
conn.close();
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
/**
* 创建一个用于select查看数据的方法 executeQuery();
* @param strSelect
* @return ResultSet
*/
public ResultSet queryMySql(String strSelect) {
// 创建一个数据集 用于获取查询到的行数据
ResultSet rs = null;
try {
Class.forName(driver).newInstance();
} catch (Exception e) {
System.out.println("无法找到驱动器!");
e.printStackTrace();
}
try {
// 创建连接
conn = DriverManager.getConnection(url, user, password);
// 创建一个 Statement 对象来将 SQL 语句发送到数据库
stmt = conn.createStatement();
// 执行查询语句 获取ResultSet对象
rs = stmt.executeQuery(strSelect);
} catch (SQLException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
//返回结果集
return rs;
}
}
编写客户端
客户端 Client 类(主函数) 用于连接并访问 服务器
通过 在RMI注册表上需找 服务器的地址 来获取对服务器的连接和访问 (详细请看 代码注释)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
运行数据采集器
代码写好了 我们来运行看看
启动服务器
启动客户端连接服务器
输入1 清空数据 服务器方 显示 "数据库清空成功!"
客户端显示 "服务器上数据清空完毕!"
输入2 操作服务器插入数据
我们看到 大量的数据被成功插入到服务器上
客户端也最终 显示 "服务器上数据收集成功!"
输入3 在客户端上 查看 服务器上收集到的所有球队名称
输入4选择 按球队查询比赛结果 再输入球队名称 查看到该球队的比赛记录 这里查看的球队是 NewCastle
输入5 选择按日期查看比赛结果 这里查看的日期是 03.01.2012
我们输入1 清空下数据库 检测下 是否真正清空
输入3 查看球队名称测试 结果显示 数据库里已经没有数据
这样 我们的远程客户端 访问服务器就做好了 :)
这里我们的java网页数据采集器教程也就告于段落了 如果对此感兴趣的学友 可以加入收藏或者关注一下! 欢迎常来学习和讨论 :)
附上源代码 htmlDataQuery_RemoteVisit.zip
以及 jar文件 服务端 htmlDataQuery_Server.zip 客户端 htmlDataQuery_Client.zip