写在开头:
先吐槽一下这个学期:终于熬过这个全是实验的学期了,我去。我从11周就开始写实验了,写到16周(考试周前一周)才写完,然后进入紧张刺激的复习环节。期末周(17周和18周)17周啥专业课都不考,就考了公选和马原;18周考六门,考完一门立刻备战下一门。真不知道自己是怎么熬过来的。
声明:本人java入门水平,佬们没必要浪费时间看这坨代码。
想对你说的话:如果你觉得你有足够的精力和自信的话,可以试着在看懂代码修改代码(虽然写的一坨),大伙也不希望到时候给老师检查全是一摸一样的UI吧。
宇宙免责声明:本篇文章爬取的所有文本仅用于向老师展示,不存在任何非法爬取信息且获利的行为,如果有如何违规的地方,请第一时间与我联系,我会在第一时间删除本文。同时引用一个链接来表明自己有做过相关的功课:爬虫知识点丨“爬虫”的13条合规边界_爬虫合法性使用的相关要求-CSDN博客
实验一:用JavaSocket编程开发聊天室
一.系统描述:分析和描述系统的基本要求和内容;
1. 用Java图形用户界面编写聊天室服务器端和客户端,支持多个客户端连接到一个服务器。每个客户端能够输入账号。
2. 可以实现群聊(聊天记录显示在所有客户端界面)。
3. 完成好友列表在各个客户端上显示。
4. 可以实现私人聊天,用户可以选择某个其他用户,单独发送信息。
5. 服务器能够群发系统消息,能够强行让某些用户下线。
6. 客户端的上线下线要求能够在其他客户端上面实时刷新。
7. 用户能够自己建立小群聊天并解散小群(选做)。
二.功能模块结构:包括如何划分功能模块,各个功能模块的结构图、流程图,以及各模块的功能描述;
模块划分:
1.客户端:
- 客户端UI设计(总体上仿照电脑版qq):账号(本代码用昵称代替)的创建;好友/群聊列表;消息输入框;消息显示框;发送对象的昵称显示;好友添加/群聊创建输入框;可以选择好友添加/群聊创建模式的下拉式菜单。
- 好友申请弹窗:当A向B发送好友请求时,会弹出对应的好友请求弹窗,按照B的不同反应(同意/拒绝)进行对应信息的回传。
- 本地聊天记录加载:从本地缓存文件中读取对应的聊天记录。
- 通信协议:1.消息接收协议;2.更新好友列表协议;3.接受好友请求协议;4.好友请求被拒绝协议;5.下线时通知服务器端协议;6.下线但不通知服务器端协议(已废弃,作为5号协议的过渡协议而创造的协议);8.群聊解散失败/没有权限协议。下为协议对应的流程图:
- 重载的run()函数:用于监听服务器端发送过来的协议。
- 好友列表选项更换监听:当更换聊天对象时,要及时读取对应的聊天记录,并修改发送对象的昵称显示框。
- 键盘监听:当按下ENTER键时,若是在好友添加/群聊创建输入框中按的,则进行好友添加/群聊创建功能。若是在消息输入框中按的,则进行消息的发送。
2.服务器端:
- 服务器端UI设计:好友/群聊列表;强制下线按钮;消息输入框;消息显示框。
- 群聊类:用于记录群聊相关的信息(群名,群主,群成员等)。
- 聊天线程类:用于保存某个已经建立的链接,并保存此客户端的好友列表和群聊列表。通过这个类向对应的客户端发送或接受协议。
- 通信协议(属于聊天线程类):1.消息转发协议:进行消息的中转。2.要求用户端按照服务器端的数据更新好友列表的协议。3.好友请求转发协议:进行好友请求的转发。4.好友请求的回复转发协议:对于刚刚发过去的好友请求,将对方的回应中转回去。5.用户端关闭协议(废弃,为了引出6号协议的试用性协议)6.下线通知别人协议:自己下线的同时告知服务器端和自己的好友们;7.群聊创建协议;8.群聊解散协议。下为协议对应的流程图:
- 重载的run()函数(属于聊天线程类):用于监听客户端发送过来的协议。
- 强制下线功能:借助客户端的5号协议实现。
- 客户/群聊列表选项更换监听:当更换聊天对象时,要及时修改发送对象的昵称显示框。
- 键盘监听:当按下ENTER键时,若是在消息输入框中按的,则进行消息的发送。
- 对应的聊天线程的查找函数:给出对象的昵称,从而获得该对象的聊天线程
- 对应的群查找函数:给出群聊的昵称,从而获得该群的相关信息。
- 更新服务器端的好友/群聊显示列表的函数:当有新用户进入,或者有用户上(下)线时会用到。
- 重载run()函数(属于服务器类):对于新用户,建立新的聊天线程供其使用;而对于老用户,依旧把老的聊天线程供其使用。
三.主要模块的算法说明:即实现该模块的思路;
主要模块:
- 客户端的通信协议:
- 消息接受协议:当接受到协议号为“1”的协议时,接下来服务器端会发送过来发送方的昵称以及发送过来的内容。此客户端只需要接受这两个部分,然后将内容保存到与发送方相对应的本地文件中即可。最后如果正好在浏览与这个好友/群聊的聊天记录,则直接重新读入本地文件中的消息。
- 刷新好友列表协议:当接受到协议号为“2”的协议时,接收服务器端发来的好友列表及其状态(在线/离线)并更新到客户端上。
- 接收好友请求协议:当接受到协议号为“3”的协议时,服务器端会发送过来发送方的昵称,在接收了昵称之后,弹出好友申请弹窗,根据用户不同的反应(按下同意/拒绝按钮或者关掉弹窗),给服务器端发送不同的消息,最后回收好友申请弹窗的资源。
- 发送的好友请求被拒绝协议:当接受到协议号为“4”的协议时,服务器端会发送过来拒绝方的昵称,说明用户发送的相应好友请求被拒绝了,在接收了昵称之后,弹出一个消息框告知用户,拒绝方拒绝了用户的好友请求。
- 下线并通知(回复)好友协议:当接受到协议号为“5”的协议时,说明被服务器端强制下线了,客服端发送6号协议给服务器端,让服务器端和自己的好友都得知自己下线的消息,然后回收本客户端的资源。
- 下线但不通知(回复)好友协议(过渡协议):当接受到协议号为“6”的协议时,说明被服务器端强制下线了,于是回收本客户端的资源。与5号协议相比缺少了与服务器端的通信的功能。
- 解散群聊失败协议:当接受到协议号为“8”的协议时,说明解散群聊失败,因为此客户端不是群主,没有解散的权限,所以失败了。弹出消息窗口提示群聊解散失败。
- 服务器端的通信协议:
- 转发消息协议:当接受到协议号为“1”的协议时,说明客户端发送消息了,服务器端要进行消息转发。如果是发给客户端自己的,那就不用再转发了,因为自己的消息在发送的时候就会保存在本地,并读出本地聊天记录;如果是发给好友的,那么先找到好友的聊天线程,再发送1号协议过去,最后把发送方的昵称和发送内容转发过去即可;若是发在群聊里的,那么找到这个群的群成员信息,对除了自己和系统两者的其他所有群成员进行消息的转发。
- 告知用户端更新好友列表协议:当接受到协议号为“2”的协议时,说明要更新此客户的聊天列表,发送2号协议给客户端,让其等待接收新的好友状态列表,根据好友的聊天线程是否还在运行来判断好友是否还在线,然后发送好友昵称和状态,最后发送群聊名称,以“over2”的发送为结束标志,结束好友列表的更新。
- 转发好友请求协议:当接受到协议号为“3”的协议时,说明发送方A向某个客户B发送了好友请求,服务器端会接收到A发送的B的昵称,然后服务器端按照这个昵称查找对应的聊天线程,若查找成功,则向其发送3号协议,并附带上A的昵称。若失败,则无事发生。
- 好友请求的回复的转发协议:当接受到协议号为“4”的协议时,说明B对A发送来的好友请求进行了处理,服务器端要将处理结果反馈给A。服务器端接收到回复的内容(同意/拒绝),以及A的昵称。若B同意了A的好友请求,则在双方的好友列表上加上对方的昵称,并使用各自聊天线程的服务器端的2号协议,来更新二者的客户端的好友列表;若拒绝,则服务器端向A发送4号协议,并附带上B的昵称。
- 客户端下线协议(过渡协议):当接受到协议号为“5”的协议时,说明客户端被强制下线,则关闭服务器端与客户端的套接字,并更新服务器端的用户在线状态。
- 客户端下线并通知好友协议:当接受到协议号为“6”的协议时,遍历该用户的好友系统,对他的在线好友的聊天线程调用服务器端的2号协议,来更新他们的好友列表,最后关闭服务器端与客户端的套接字。
- 创建群聊协议:当接受到协议号为“7”的协议时,说明有人要创建群聊了。服务器端获得该用户发来的群昵称和该用户的昵称(作为群主的昵称),为其创建一个新的群聊,并更新群主的聊天列表,接着通过群主发来的群成员昵称,更新这些群成员的好友列表,最后以“over”结束群成员的邀请,最后的最后系统更新自己的列表。
- 解散群聊协议:当接受到协议号为“8”的协议时,说明有人要解散群聊。服务器端接收客户端发送过来的群聊名称,若群聊存在,若是群主发送的请求,则先更新每个群成员的好友列表,再把群聊从服务器端永久删除,最后更新服务器端的好友列表,若不是群主发送的,则回复8号协议给客户端,若群聊不存在,则无事发生。
四.运行结果:包括典型的界面、输入和输出数据等;
服务器端UI:
客户端UI:
(登录前)
(登录后)
好友申请:
聊天记录:
群聊创建:
解散群聊失败:
拒绝好友请求:
离线显示:
五.有待提升的地方;
1.UI界面还大有改进空间。
2.群聊创建的时候,可以给邀请的成员发送邀请,而不是像我这样直接添加群成员,问都不问。
3.群聊创建完以后,设计一个邀请按钮,可以再邀请别人。而不是只能在创建群聊的时候邀请一次。
4.群聊删除的时候,设计一个解散群聊(群主显示这个)/退出群聊(群成员显示这个)按钮。这个可能需要将客户端代码部分重写,最好把好友列表的好友和群聊分开来存储,对于每个聊天列表里的项目,先判断是不是群(决定有没有这个按钮,当然对于好友聊天界面来说是“删除好友”,对于群聊天界面才是这两个版本的按钮),再判断此用户是否为群主(是群主就显示“解散群聊”,不是就显示“退出群聊”)。在鼠标点击这个按钮以后,客户端就会执行相应的功能。
5.可以将服务器端的数据连接到一个数据库中(或者用一个文件也行),保存注册过的用户的昵称,群聊信息,并实现聊天记录的云存储。
六.源代码;
客户端:
package class6;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
public class chatClient extends JFrame implements ListSelectionListener,KeyListener,WindowListener,Runnable{//ActionListener,
Socket s = null;//客户端对于套接字
String nickName = null;//昵称
PrintStream ps;//输出流
BufferedReader br;//读入流
DefaultListModel<String> listModel = new DefaultListModel<>();//列表
JList<String> friendlist;//好友列表
JPanel mainPanel = new JPanel(new BorderLayout());
JPanel westPanel = new JPanel(new BorderLayout());
JPanel eastPanel = new JPanel(new BorderLayout());
JLabel jlb = new JLabel("");//存放聊天对象昵称的标签
JTextArea jta = new JTextArea();
JScrollPane jta1 = new JScrollPane(jta);//带滑动条的聊天框
JTextArea jtf = new JTextArea();
JScrollPane jtf1 = new JScrollPane(jtf);//带滑动条的消息输入框
JTextField addFriend = new JTextField("");//好友昵称/群昵称输入框
//JButton jbt = new JButton("解散群聊");
String[] items = {"添加好友","创建群聊"};
JComboBox<String> jcb = new JComboBox<>(items);//两种模式:添加好友/创建群聊
FriendRequest fr;//好友请求弹窗
String pse;//发送的协议号
String pre;//接收的协议号
String Sendto;//发送给谁
String searchid;//输入的要添加的好友的昵称
boolean running = true;
boolean visiable = true;
class FriendRequest extends JFrame implements ActionListener
{
String agree = null;//回复
JPanel jpn = new JPanel();
JButton agreeButton = new JButton("同意");//同意按钮
JButton disagreeButton = new JButton("拒绝");//拒绝按钮
FriendRequest(String Name)//UI初始化
{
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.setSize(300,150);
this.setLocationRelativeTo(null);
this.setResizable(false);
this.setTitle("好友申请");
this.setAlwaysOnTop(true);
jpn.setLayout(new BoxLayout(jpn,BoxLayout.Y_AXIS));
agreeButton.addActionListener(this);
disagreeButton.addActionListener(this);
jpn.add(new JLabel("收到来自"+Name+"的好友请求"));
jpn.add(Box.createVerticalStrut(10));
jpn.add(agreeButton);
jpn.add(Box.createVerticalStrut(10));
jpn.add(disagreeButton);
this.add(jpn);
}
public void actionPerformed(ActionEvent e)
{
if(e.getSource() == agreeButton)//若点击同意按钮,则回复同意
{
agree = "true";
}
else if(e.getSource() == disagreeButton)//若点击拒绝按钮,则回复拒绝
{
agree = "false";
}
}
}
public void showfr(FriendRequest fre)//显现好友请求弹窗
{
fre.setVisible(true);
fre.jpn.setVisible(true);
}
public void trypath(String filepath)//尝试打开文件,若不存在,则创建一个新文件
{
try {
if(!Files.exists(Paths.get(filepath)))
{
Files.createFile(Paths.get(filepath));
}
}catch(Exception e) {e.printStackTrace();}
}
public void readall(String filepath)//读取文件的所有内容(聊天记录)
{
try{
jta.setText(null);
BufferedReader breader = new BufferedReader(new FileReader(filepath));
String line;
while((line = breader.readLine()) != null)
{
jta.append(line+"\n");
}
breader.close();
}catch(Exception ex) {ex.printStackTrace();}
}
public void todo(String a)//一系列协议
{
try {
if(a.equals("1"))//消息接收协议
{
String from = br.readLine();//保存发送方的昵称
String message = br.readLine();//保存消息的内容
//System.out.println(message);
BufferedWriter bw = new BufferedWriter(new FileWriter("/Users/zpt/eclipse-workspace/"+nickName+"with"+from+".txt",true));
bw.write(message);//将消息的内容写入对应的文本文件中
bw.newLine();
bw.close();
if(from.equals(friendlist.getModel().getElementAt(friendlist.getSelectedIndex())))
readall("/Users/zpt/eclipse-workspace/"+nickName+"with"+from+".txt");//读取文件中的所以消息
}
else if(a.equals("2"))//刷新好友列表
{
listModel.clear();//清空好友列表
String tempid;
while(!(tempid = br.readLine()).equals("over2"))//未接收到结束命令
{
listModel.addElement(tempid);//添加发送过来的好友
}
//System.out.println("好友列表加载完毕");
}
else if(a.equals("3"))//好友请求协议
{
String from = br.readLine();//保存发送方的昵称
//System.out.println("接收到来自"+from+"的好友请求");
fr = new FriendRequest(from);//创建消息框
showfr(fr);
pse = "4";//准备发送四号协议给服务器
while(fr.isVisible())//若没有关闭此窗口
{
Thread.sleep(10);
//System.out.println(fr.agree);
if(fr.agree != null)//若按下了按钮,则退出循环
{
ps.println(pse);
ps.println(fr.agree);//发送agree的内容
ps.println(from);//发送接收方(原来发送好友请求的发送方)的昵称
break;
}
}
if(!fr.isVisible())//若关闭了此窗口
{
ps.println(pse);
ps.println("false");//发送“拒绝”
ps.println(from);//发送接收方(原来发送好友请求的发送方)的昵称
}
//System.out.println("fr is closing");
fr.dispose();//回收窗口的资源
}
else if(a.equals("4"))//好友请求被拒绝
{
String from = br.readLine();
JOptionPane.showMessageDialog(this,from+"拒绝了您的好友请求");//告知用户对方拒绝了好友请求
}
else if(a.equals("5"))//下线回复
{
pse = "6";//准备发送六号协议,刷新好友的好友列表
JOptionPane.showMessageDialog(this,"您即将被强制下线");//告知用户即将被强制下线
running = false;
ps.println(pse);
Thread.sleep(100);
s.close();//关闭套接字
this.dispose();//回收本聊天窗口
}
else if(a.equals("6"))//下线不回复(已废弃)
{
running = false;
JOptionPane.showMessageDialog(this,"您即将被强制下线");
Thread.sleep(100);
s.close();
this.dispose();
}
else if(a.equals("8"))//解散群聊失败
{
JOptionPane.showMessageDialog(this,"您没有该权限");//不是群主,解散失败
}
else//无效协议号
{
System.out.println("无效协议:"+a);
}
}catch(Exception e) {e.printStackTrace();}
}
public void run()
{
try{
while(running)
{
pre = br.readLine();
if(pre == null)
continue;
System.out.println("客户器接收到"+pre+"号协议");
todo(pre);
pre = null;
Thread.sleep(1);
}
}catch(Exception e) {e.printStackTrace();}
}
/* public void actionPerformed(ActionEvent e)
{
if(e.getSource() == jbt)
{
searchid = addFriend.getText();
if(searchid.equals("creategroup"))
{
String GroupName = JOptionPane.showInputDialog("请输入群昵称");
pse = "7";
ps.println(pse);
ps.println(GroupName);
ps.println(nickName);
while(true)
{
Sendto = JOptionPane.showInputDialog("请输入您要邀请的好友的昵称");
if(Sendto !=null)
{
ps.println(Sendto);
if(Sendto.equals("over"))
break;
}
}
}
else
{
ps.println("3");
ps.println(searchid);
}
}
}*/
public void valueChanged(ListSelectionEvent e)//监听JList的选择
{
if (!e.getValueIsAdjusting()) { // 当选择结束后再处理,避免多次触发
// 检查是否有选中的项目
if (!friendlist.getSelectionModel().isSelectionEmpty()) {
// 获取选中项目的索引
int Index = friendlist.getSelectedIndex();
// 获取选中项目的内容
String selectedValue = friendlist.getModel().getElementAt(Index);
Sendto = selectedValue.split("\\s+")[0];
jlb.setText(Sendto);
//System.out.println("Selected item: " + Sendto);
trypath("/Users/zpt/eclipse-workspace/"+nickName+"with"+Sendto+".txt");
readall("/Users/zpt/eclipse-workspace/"+nickName+"with"+Sendto+".txt");
} else {
System.out.println("No item selected.");
}
}
}
public void keyTyped(KeyEvent e){}
public void keyReleased(KeyEvent e) //松开Enter键后清空消息输入框的文本,并让光标回到左上角
{
if(e.getSource() == jtf)
{
if(e.getKeyCode() == KeyEvent.VK_ENTER)
{
jtf.setText(null);
jtf.setCaretPosition(0);
}
}
}
public void keyPressed(KeyEvent e)
{
if(e.getSource() == jtf)//如果是在消息输入框按的,就可以发送消息
{
try {
if(e.getKeyCode() == KeyEvent.VK_ENTER)
{
String message = jtf.getText();
ps = new PrintStream(s.getOutputStream());
if(message != null)
{
if(message.equals("/dissolve"))
{
pse ="8";
ps.println(pse);
ps.println(Sendto);
}
else
{
pse = "1";
ps.println(pse);
ps.println(message);
ps.println(Sendto);
jta.append(nickName+"说:"+message+"\n");
BufferedWriter bw = new BufferedWriter(new FileWriter("/Users/zpt/eclipse-workspace/"+nickName+"with"+Sendto+".txt",true));
bw.write(nickName+"说:"+message);
bw.newLine();
bw.close();
}
}
}
}catch(Exception exc) {exc.printStackTrace();}
}
else if(e.getSource() == addFriend)//如果是添加好友/创建群聊的输入框中按的
{
if(e.getKeyCode() == KeyEvent.VK_ENTER)
{
String selectedItem = (String)jcb.getSelectedItem();
if(selectedItem.equals("添加好友"))//发送好友请求
{
searchid = addFriend.getText();
ps.println("3");
ps.println(searchid);
}
else if(selectedItem.equals("创建群聊"))//创建群聊
{
String GroupName = addFriend.getText();
pse = "7";
ps.println(pse);
ps.println(GroupName);
ps.println(nickName);
while(true)
{
Sendto = JOptionPane.showInputDialog("请输入您要邀请的好友的昵称");
if(Sendto !=null)
{
ps.println(Sendto);
if(Sendto.equals("over"))
break;
}
}
}
}
}
}
public void windowClosed(WindowEvent e){ps.println("6");}
public void windowDeactivated(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public void windowClosing(WindowEvent e) {ps.println("6");}
public void init()//界面初始化
{
friendlist = new JList<>(listModel);
this.setTitle("客户端");
this.setSize(1000,600);
this.setBackground(Color.LIGHT_GRAY);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
this.setLocationRelativeTo(null);
this.add(mainPanel);
mainPanel.setSize(this.getWidth(),this.getHeight());
westPanel.setSize(200,600);
eastPanel.setSize(800,600);
//jbt.setPreferredSize(new Dimension(40,60));
jcb.setPreferredSize(new Dimension(100,60));
friendlist.setPreferredSize(new Dimension(200,500));
jlb.setPreferredSize(new Dimension(800,60));
jta1.setPreferredSize(new Dimension(800,300));
jtf1.setPreferredSize(new Dimension(800,240));
friendlist.setBackground(Color.LIGHT_GRAY);
friendlist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jta.setEditable(false);
jtf.setColumns(10);
friendlist.addListSelectionListener(this);
jtf.addKeyListener(this);
addFriend.addKeyListener(this);
//jbt.addActionListener(this);
this.addWindowListener(this);
mainPanel.add(westPanel,BorderLayout.WEST);
mainPanel.add(eastPanel,BorderLayout.CENTER);
westPanel.add(jcb,BorderLayout.EAST);
westPanel.add(friendlist,BorderLayout.SOUTH);
westPanel.add(addFriend,BorderLayout.CENTER);
eastPanel.add(jlb,BorderLayout.NORTH);
eastPanel.add(jtf1,BorderLayout.SOUTH);
eastPanel.add(jta1,BorderLayout.CENTER);
mainPanel.setVisible(true);
eastPanel.setVisible(true);
westPanel.setVisible(true);
}
public chatClient() throws Exception
{
init();
nickName = JOptionPane.showInputDialog("输入昵称");
s = new Socket("127.0.0.1",59999);
ps = new PrintStream(s.getOutputStream());
ps.println(nickName);
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
JOptionPane.showMessageDialog(this,"连接成功");
this.setTitle("客户端:" + nickName);
new Thread(this).start();
}
public static void main(String[] args) throws Exception
{
chatClient cc = new chatClient();
}
}
服务器端:
package class6;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class chatServer extends JFrame implements ListSelectionListener,KeyListener,ActionListener,Runnable{
Socket s = null;//用于存放协议发送方的套接字
Socket receiver = null;//用于存放协议接收方的套接字
ServerSocket ss = null;//服务器套接字
ArrayList<ChatThread> clients = new ArrayList<ChatThread>();//用户列表
ArrayList<Group> GroupList = new ArrayList<Group>();//群列表
DefaultListModel<String> listModel = new DefaultListModel<>();
JList<String> friendlist;//好友列表
JPanel mainPanel = new JPanel(new BorderLayout());
JPanel westPanel = new JPanel(new BorderLayout());
JPanel eastPanel = new JPanel(new BorderLayout());
JTextArea jta = new JTextArea();
JTextField jtf = new JTextField(null);
JButton jbt = new JButton("强制下线");
PrintStream ps;//输出流
BufferedReader br;//读入流
String ReceiverName;//接收者的昵称
String ReadIn;//读入的内容
String p;//协议
class Group //群类
{
String GroupName;//群名
String GroupOnwer;//群主的昵称
ArrayList<String> groupmember = new ArrayList<String>();//群成员列表
Group(String name,String onwer)
{
this.GroupName = name;
this.GroupOnwer = onwer;
groupmember.add(onwer);
}
public void AddGroupMember(String name)
{
groupmember.add(name);
}
}
class ChatThread extends Thread//聊天线程类
{
ArrayList<String> friendlist = new ArrayList<String>();//用户的好友列表
ArrayList<String> grouplist = new ArrayList<String>();//用户的群聊列表
BufferedReader b1 = null;//读入流
PrintStream p1 =null;//输出流
boolean running = true;
String nickName;//用户昵称
Socket s;//套接字
String p;//协议号
public void todo(String a)//具体协议
{
try {
if(a.equals("1"))//转发消息协议
{
String message = this.b1.readLine();//获得发送来的消息内容
//System.out.println("服务器接收到:"+message);
String ReceiverName = this.b1.readLine();//获得发送的对象
//System.out.println("服务器"+ReceiverName+"接收到");
if(!ReceiverName.equals(nickName))//如果是发给自己的,那就不用再转发了,本地保存即可
{
if(lookforclient(ReceiverName) != null)//若在用户内找到接收者了
{
receiver = lookforclient(ReceiverName).s;//获取接收者的套接字
p1 = new PrintStream(receiver.getOutputStream());
p = "1";//发送1号协议给接收者
p1.println(p);
p1.println(nickName);//发送发送者的昵称给接收者
p1.println(nickName+"说:"+message);//发送消息内容给接收者
}
else//若没在用户内找到接收者,则在群聊列表中查找群名
{
Group tempGroup = lookforgroup(ReceiverName);//获得群聊
if(tempGroup != null)
{
for(String name:tempGroup.groupmember)//遍历群成员
{
if(name.equals(nickName))//自己不用发
{
continue;
}
if(name.equals("系统"))//系统不用发,其他人都要发
continue;
receiver = lookforclient(name).s;
p1 = new PrintStream(receiver.getOutputStream());
p = "1";
p1.println(p);
p1.println(tempGroup.GroupName);
p1.println(nickName+"说:"+message);
}
}
else//既不是用户,也不是群聊,则发送失败
{
System.out.println("未找到该对象");
}
}
receiver = null;
}
}
else if(a.equals("2"))//告知用户端更新好友列表协议
{
if(!s.isClosed())//只有当接收方在线时,才进行下去
{
p1 = new PrintStream(s.getOutputStream());
p = "2";//发送二号协议
p1.println(p);
for(String friend:friendlist)//遍历接收方的好友列表
{
if(friend.equals("系统"))//系统肯定在线,不用管
{
p1.println("系统");
}
else
{
//System.out.println("正在查找"+friend);
if(lookforclient(friend).running)//根据好友的running变量来确定好友的在线/离线状态,并发送
p1.println(friend+" 在线");
else
p1.println(friend+" 离线");
}
}
for(String group:grouplist)//群聊不用管在线/离线状态,直接发送
{
p1.println(group);
}
p1.println("over2");//告知接收方,好友列表读取结束
}
}
else if(a.equals("3"))//转发好友请求协议
{
String ReceiverName = b1.readLine();
if(lookforclient(ReceiverName) != null)//若能找到好友
{
receiver = lookforclient(ReceiverName).s;
p1 = new PrintStream(receiver.getOutputStream());
p = "3";//发送三号协议
p1.println(p);
p1.println(nickName); //发送发送方的昵称给接收方
}
else
{
//如果该用户不存在,则回复请求发送失败
}
}
else if(a.equals("4"))//好友请求的回复转发协议
{
String reply = b1.readLine();//接收接收方的回复:同意/拒绝
String ReceiverName = b1.readLine();//接收上个协议的发送方(这个协议的接收方)的昵称
//System.out.println(reply);
//System.out.println(ReceiverName);
if(reply.equals("true"))//若同意
{
ChatThread tempct =lookforclient(ReceiverName);
tempct.friendlist.add(nickName);//更新对方的好友列表
tempct.todo("2");//调用二号洗衣,刷新好友列表
this.friendlist.add(ReceiverName);//更新自己的好友列表
this.todo("2");//调用二号洗衣,刷新好友列表
}
else//若拒绝
{
receiver = lookforclient(ReceiverName).s;
p1 = new PrintStream(receiver.getOutputStream());
p="4";//发送四号协议
p1.println(p);
p1.println(nickName);
}
}
else if(a.equals("5"))//客户端关闭协议(废弃)
{
running = false;//表示客户端断开连接
updatelist();//刷新系统的在线列表
s.close();
}
else if(a.equals("6"))//下线通知别人协议
{
this.running = false;
for(String name:friendlist)//遍历好友列表
{
if(name.equals("系统"))//若为系统
{
updatelist();//刷新系统的在线列表
}
else
{
if(!name.equals(nickName))//若不是自己
{
if(lookforclient(name).s.isClosed())//若好友不在线,则跳过
continue;
lookforclient(name).todo("2");//好友在线,则刷新好友的好友列表
}
}
}
s.close();//断开连接
}
else if(a.equals("7"))//群聊创建协议
{
String groupid = b1.readLine();//获得群昵称
//System.out.println("群名:"+groupid);
String grouponwer = b1.readLine();//获得群主的昵称
//System.out.println("群主:"+grouponwer);
GroupList.add(new Group(groupid,grouponwer));//添加该群聊到服务器端的群聊列表
ChatThread tempct = lookforclient(grouponwer);
tempct.grouplist.add(groupid);//给群主的好友列表里面加上这个群
tempct.todo("2");//刷新群主的好友列表
Group tempgroup = lookforgroup(groupid);
while(!(ReceiverName = b1.readLine()).equals("over"))//当入群邀请未结束时
{
//System.out.println("已经向"+ReceiverName+"发送");
tempgroup.AddGroupMember(ReceiverName);//在群成员列表内添加群成员
tempct = lookforclient(ReceiverName);//寻找该成员
if(tempct == null)//若未找到,则不管
continue;
tempct.grouplist.add(groupid);//给该群成员的好友列表加上本群
tempct.todo("2");//刷新该群成员的好友列表
//System.out.println("已经向"+ReceiverName+"发送完毕");
}
updatelist();//刷新系统的列表
}
else if(a.equals("8"))//群聊解散协议
{
String tempgroup = b1.readLine();
if(lookforgroup(tempgroup)!=null)
{
String temponwer = lookforgroup(tempgroup).GroupOnwer;
if(nickName.equals(temponwer))//若是群主发出的解散请求
{
Group delete = lookforgroup(tempgroup);
for(String name:delete.groupmember)//遍历群成员,将所有成员的好友列表更新
{
ChatThread tempct = lookforclient(name);
tempct.grouplist.remove(tempgroup);
tempct.todo("2");
}
for(int i = 0;i<GroupList.size();i++)//在服务器端,把群聊删除
{
if(GroupList.get(i).GroupName.equals(tempgroup))
{
GroupList.remove(i);
break;
}
}
updatelist();//刷新系统的列表
}
else//若不是群主发出的解散请求
{
p1 = new PrintStream(s.getOutputStream());
p = "8";//发送八号协议,表示解散失败
p1.println(p);
}
}
}
else//其他的都是无效协议
{
System.out.println("无效协议:"+a);
}
}catch(Exception e) {e.printStackTrace();}
}
ChatThread(String n,Socket s)
{
try {
this.s = s;
this.nickName = n;
b1 = new BufferedReader(new InputStreamReader(this.s.getInputStream()));
p1 = new PrintStream(s.getOutputStream());
friendlist.add(nickName);
friendlist.add("系统");
grouplist.add("所有人");
this.start();
}catch(Exception e) {e.printStackTrace();}
}
public void run()
{
try {
while(true)
{
Thread.sleep(100);
while(running)
{
if(s == null)
{
running = false;
break;
}
b1 = new BufferedReader(new InputStreamReader(this.s.getInputStream()));
String p = b1.readLine();
if(p == null)
{
b1.close();
continue;
}
System.out.println("服务器接收到"+p+"号协议");
todo(p);
p = null;
}
}
}
catch(Exception e)
{
this.running = false;
e.printStackTrace();
}
}
}
public void actionPerformed(ActionEvent e)//强制下线按钮的实现
{
try {
if(e.getSource() == jbt)
{
if(lookforclient(ReceiverName) != null)//如果时一个用户,则让该用户下线
{
receiver = lookforclient(ReceiverName).s;
ps = new PrintStream(receiver.getOutputStream());
p = "5";
ps.println(p);
}
else
{
Group tempGroup = lookforgroup(ReceiverName);//如果是一个群,则让所有群成员下线
if(tempGroup != null)
{
for(String name:tempGroup.groupmember)
{
if(lookforclient(name) != null)
{
ChatThread tempct = lookforclient(name);
tempct.running = false;
for(String fname:tempct.friendlist)
{
if(fname.equals("系统"))
{
updatelist();
}
else
{
if(tempct.s.isClosed())
continue;
ps = new PrintStream(tempct.s.getOutputStream());
p = "5";
ps.println(p);
lookforclient(fname).todo("2");
}
}
}
}
}
else
{
System.out.println("未找到下线对象");
}
}
}
}catch(Exception exc) {exc.printStackTrace();}
}
public void valueChanged(ListSelectionEvent e)//列表读取
{
if (!e.getValueIsAdjusting()) { // 当选择结束后再处理,避免多次触发
// 检查是否有选中的项目
if (!friendlist.getSelectionModel().isSelectionEmpty()) {
// 获取选中项目的索引
int Index = friendlist.getSelectedIndex();
// 获取选中项目的内容
String selectedValue = friendlist.getModel().getElementAt(Index);
ReceiverName = selectedValue.split("\\s+")[0];
//System.out.println("Selected item: " + ReceiverName);
} else {
System.out.println("No item selected.");
}
}
}
public void keyTyped(KeyEvent e){}
public void keyReleased(KeyEvent e) {}
public void keyPressed(KeyEvent e)//按Enter键发送系统消息
{
try {
if(e.getKeyCode() == KeyEvent.VK_ENTER)
{
String message = jtf.getText();
if(lookforclient(ReceiverName) != null)
{
receiver = lookforclient(ReceiverName).s;
ps = new PrintStream(receiver.getOutputStream());
p = "1";
ps.println(p);
ps.println("系统");
ps.println("系统消息:"+message);
}
else
{
Group tempGroup = lookforgroup(ReceiverName);
if(tempGroup != null)
{
for(String name:tempGroup.groupmember)
{
if(name.equals("系统"))
continue;
receiver = lookforclient(name).s;
ps = new PrintStream(receiver.getOutputStream());
p = "1";
ps.println(p);
ps.println(tempGroup.GroupName);
ps.println("系统消息:"+message);
}
}
else
{
System.out.println("未找到该对象");
}
}
jta.append("系统消息:"+message+"\n");
jtf.setText(null);
}
}catch(Exception exc) {exc.printStackTrace();}
}
public ChatThread lookforclient(String Name)//查找用户昵称对应的聊天线程
{
int i;
for(i=0;i<clients.size();i++)
{
if(clients.get(i).nickName.equals(Name))
{
return clients.get(i);
}
}
System.out.println("未找到此用户");
return null;
}
public Group lookforgroup(String Name)//查找群昵称对应的群
{
int i;
for(i=0;i<GroupList.size();i++)
{
if(GroupList.get(i).GroupName.equals(Name))
{
return GroupList.get(i);
}
}
System.out.println("未找到此群");
return null;
}
public void updatelist()//更新服务器端列表
{
listModel.clear();
for(int i=0;i<GroupList.size();i++)
{
listModel.addElement(GroupList.get(i).GroupName);
}
for(int i=0;i<clients.size();i++)
{
if(clients.get(i).running)
listModel.addElement(clients.get(i).nickName+" 在线");
else
listModel.addElement(clients.get(i).nickName+" 离线");
}
}
public void init()//客户端UI初始化
{
this.setTitle("服务器端");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
friendlist = new JList<>(listModel);
this.setSize(1000,600);
this.setLocationRelativeTo(null);
this.setBackground(Color.ORANGE);
this.add(mainPanel);
mainPanel.setSize(this.getWidth(),this.getHeight());
westPanel.setSize(200,600);
eastPanel.setSize(800,600);
jbt.setPreferredSize(new Dimension(200,60));
friendlist.setPreferredSize(new Dimension(200,500));
jta.setPreferredSize(new Dimension(800,300));
jtf.setPreferredSize(new Dimension(800,240));
friendlist.setBackground(Color.LIGHT_GRAY);
friendlist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jta.setEditable(false);
jtf.setColumns(10);
friendlist.addListSelectionListener(this);
jtf.addKeyListener(this);
jbt.addActionListener(this);
mainPanel.add(westPanel,BorderLayout.WEST);
mainPanel.add(eastPanel,BorderLayout.CENTER);
westPanel.add(jbt,BorderLayout.CENTER);
westPanel.add(friendlist,BorderLayout.SOUTH);
eastPanel.add(jtf,BorderLayout.SOUTH);
eastPanel.add(jta,BorderLayout.CENTER);
mainPanel.setVisible(true);
eastPanel.setVisible(true);
westPanel.setVisible(true);
this.setVisible(true);
}
public chatServer() throws Exception
{
init();
GroupList.add(new Group("所有人","系统"));
updatelist();
ss = new ServerSocket(59999);
new Thread(this).start();
}
public void run()//服务器的run函数,用于接收新的链接,并分配聊天线程。
{
try
{
while(true)
{
this.s = ss.accept();
if(this.s == null)
{
continue;
}
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
ReadIn = br.readLine();
if(lookforclient(ReadIn) !=null)//如果是老用户,则使用老的聊天线程
{
ChatThread ct = lookforclient(ReadIn);
ct.s = this.s;
ct.b1 = new BufferedReader(new InputStreamReader(this.s.getInputStream()));
ct.p1 = new PrintStream(this.s.getOutputStream());
ct.running = true;
for(String name:ct.friendlist)
{
if(lookforclient(name) !=null)
{
ChatThread tempct = lookforclient(name);
tempct.todo("2");
}
}
updatelist();
}
else//如果是新用户,则使用新的聊天线程
{
ChatThread ct = new ChatThread(ReadIn,this.s);
clients.add(ct);
GroupList.get(0).AddGroupMember(ReadIn);
ct.todo("2");
updatelist();
}
}
}catch(Exception e) {e.printStackTrace();}
}
public static void main(String[] args) throws Exception
{
chatServer cs = new chatServer();
}
实验二:用JavaURL编程爬取并分析网页敏感词
一.系统描述:分析和描述系统的基本要求和内容;
1.编写设计界面,输入一个网址,能够持续爬取从该地址开始链接的所有相关网页,并能控制爬取工作的开始和停止。
2.对爬取网页中的文本进行提取。
3.建立敏感词库,用文本文件保存,能够修改更新该敏感词库。
4.将爬取网页的文本中的敏感词提取出来并高亮显示。
5.为所有爬取的网页建立可视化有向图网(选做)。
6.编写一个主界面,整合上述功能。
二.功能模块结构:包括如何划分功能模块,各个功能模块的结构图、流程图,以及各模块的功能描述;
- 主界面:包括:“修改敏感词库”按钮,“爬取网页”按钮和“退出”按钮。用于将功能整合在一起。
- 敏感词库:本质上是一个文本文件,通过一个读写输入框JTextArea,进行修改,然后通过一个“保存”按钮进行文件内容的修改的保存。
- 网页爬取器:一个网页链接输入框,一个“开始”按钮,一个“停止”按钮,一个读写输入框JTextArea用来显示爬出的文本。下面是爬取过程的流程图:
三.主要模块的算法说明:即实现该模块的思路;
网页爬取器的主要功能spidering函数:对于输入的路径path,我们用WebDriver(需要导入的外部库,详情见文末参考文献)进行网页的自动访问,通过等待一个固定的时间来保证网页渲染的完成,此时再获取网页源代码的纯文本形式,使用Jsoup(需要导入的,详情见文末参考文献)的parse函数将纯文本转换成Document类型,从而取出所有网页链接,并保存到suburl数组中。对于文本形式的网页源代码,我们通过delHTMLTag函数,以及空格和换行符的去除处理,来获得纯文本。用此纯文本与敏感词库的每一个敏感词进行匹配,若匹配成功,则修改对应位置的文字的属性。之后打开有向图的产生网页,通过一系列模拟操作的执行,输入子网页链接到textarea中,这样中间的有向图就是我们需要的有向图了。每输入一个子网页链接的同时,打开一个新的爬取网页器,并将链接放到“网页链接输入框”中,以便后续的爬取。(因为电脑性能原因,我不敢让它自动开始爬取)
四.运行结果:包括典型的界面、输入和输出数据等;
主界面:
敏感词库及其修改界面:
点击保存按钮即可保存修改:
网页爬取页面:
输入要爬取的网址并按下开始:
1.自动使用浏览器访问对应网页,且等待一个固定时间,让网页js,css渲染后,爬取网页的代码,并用正则表达式删去所有的网页标签以及文本中的空格和换行符等以获取纯文本。(以https://www.bilibili.com/为例子)
2.自动打开网站(https://csacademy.com/app/graph_editor/)为爬取的网页链接进行有向图的输出。
3.为爬取的网页链接打开一个新的爬取窗口:
以上为部分子网页链接,因为相关链接有点多,所以我限制了子网页链接爬取的数量,而且也未开启自动爬取子网页,因为我电脑承受不住。
4.最后是原来的爬取窗口的文本输出以及敏感词高亮显示:
五. 有待提升的地方;
1.UI界面有待提升。
2.有向图每次只能画一个网页到他的子网页,这样的两层树状结构,不能画多层。如何解决有向图的生成问题。(不用外部网站也许可以)
3.爬取的文本内容是否可以分段?是否可以标注来源?(即从源代码的哪个部分爬到的)
六.源代码;
主界面:
package class6;
import class6.spider;
import class6.vocaburary;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class start extends JFrame implements ActionListener{
JPanel jpn = new JPanel(new GridLayout(3,1));
JButton jbt1 = new JButton("修改敏感词库");
JButton jbt2 = new JButton("爬取网页");
JButton jbt3 = new JButton("退出");
void init()
{
this.setSize(500,250);
this.setTitle("开始界面");
this.setLocationRelativeTo(null);
jpn.setSize(this.getWidth(),this.getHeight());
jbt1.setPreferredSize(new Dimension(150,100));
jbt1.addActionListener(this);
jbt2.setPreferredSize(new Dimension(150,100));
jbt2.addActionListener(this);
jbt3.setPreferredSize(new Dimension(150,100));
jbt3.addActionListener(this);
jpn.add(jbt1);
jpn.add(jbt2);
jpn.add(jbt3);
this.add(jpn);
this.setVisible(true);
}
start()
{
init();
}
public void actionPerformed(ActionEvent ae)
{
if(ae.getSource() == jbt1)
{
vocaburary v = new vocaburary();
}
else if(ae.getSource() == jbt2)
{
spider s = new spider();
}
else if(ae.getSource() == jbt3)
{
this.dispose();
}
}
public static void main(String[] args)
{
start s = new start();
}
}
敏感词库修改界面:
package class6;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
public class vocaburary extends JFrame implements ActionListener{
JPanel jpn1 = new JPanel(new BorderLayout());
JPanel jpn2 = new JPanel();
JPanel jpn3 = new JPanel();
JTextArea jta = new JTextArea();
JScrollPane jsp = new JScrollPane(jta);
JButton jbt = new JButton("保存");
BufferedReader br;
BufferedWriter bw;
public void init()//UI设计
{
this.setTitle("敏感词库");
this.setSize(1000,600);
this.setLocationRelativeTo(null);
this.add(jpn1);
jpn1.setSize(this.getWidth(), this.getHeight());
jpn2.setSize(200,100);
jpn3.setSize(800,500);
jsp.setPreferredSize(new Dimension(800,500));
jbt.setPreferredSize(new Dimension(200,100));
jbt.addActionListener(this);
jpn2.add(jbt);
jpn3.add(jsp);
jpn1.add(jpn2,BorderLayout.NORTH);
jpn1.add(jpn3,BorderLayout.CENTER);
this.setVisible(true);
}
vocaburary()
{
try {
init();
br = new BufferedReader(new FileReader("/Users/zpt/eclipse-workspace/敏感词库.txt"));//敏感词库读入流
String temp;
temp = br.readLine();
while(true)//读出所有内容
{
jta.append(temp);
temp = br.readLine();
if(temp == null)
break;
jta.append("\n");//除了最后一行,都要加换行符
}
br.close();
}catch(Exception e) {e.printStackTrace();JOptionPane.showMessageDialog(this, "文件打开失败");}
}
public void actionPerformed(ActionEvent ae)
{
if(ae.getSource() == jbt)
{
try {
bw = new BufferedWriter(new FileWriter("/Users/zpt/eclipse-workspace/敏感词库.txt",false));//敏感词库写入流
bw.write(jta.getText());
bw.newLine();
bw.close();
JOptionPane.showMessageDialog(this, "修改成功");
}catch(Exception e) {e.printStackTrace();JOptionPane.showMessageDialog(this, "内容不能为空");}
}
}
public static void main(String[] args)
{
vocaburary v = new vocaburary();
}
}
网页爬取器界面:
package class6;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.By;
public class spider extends JFrame implements Runnable,ActionListener{
WebDriver driver1,driver2;
static ArrayList<String> url = new ArrayList<String>();
ArrayList<String> suburl = new ArrayList<String>();
JPanel jp1 = new JPanel(new BorderLayout());//底
JPanel jp2 = new JPanel();//北
JPanel jp3 = new JPanel();//中
JLabel jlb1 = new JLabel("请输入网址:");
JTextField jtf1 = new JTextField();
JButton jbt1 = new JButton("开始");
JButton jbt2 = new JButton("停止");
JTextPane jtp1 = new JTextPane();
JScrollPane jsp1 = new JScrollPane(jtp1);
String goal;
boolean running = false;
public void init()//UI设计
{
this.setTitle("网页爬取器");
this.setSize(1000,600);
this.setLocationRelativeTo(null);
jtp1.setEditable(false);
this.add(jp1);
jp1.setSize(this.getWidth(),this.getHeight());
jp2.setSize(1000,100);
jp2.setLayout(new BoxLayout(jp2,BoxLayout.X_AXIS));
jp3.setSize(1000,500);
jlb1.setPreferredSize(new Dimension(100,50));
jtf1.setPreferredSize(new Dimension(700,50));
jbt1.setPreferredSize(new Dimension(100,50));
jbt1.addActionListener(this);
jbt2.setPreferredSize(new Dimension(100,50));
jbt2.addActionListener(this);
jsp1.setPreferredSize(new Dimension(800,500));
jp2.add(jlb1);
jp2.add(jtf1);
jp2.add(jbt1);
jp2.add(jbt2);
jp3.add(jsp1);
jp1.add(jp2,BorderLayout.NORTH);
jp1.add(jp3,BorderLayout.CENTER);
this.setVisible(true);
}
spider()
{
try {
init();
System.setProperty("webdriver.chrome.driver","/Users/zpt/Downloads/chromedriver-mac-arm64/chromedriver");
}catch(Exception e) {e.printStackTrace();JOptionPane.showMessageDialog(this, "文件打开失败");}
}
public void run()
{
while(running)
{
spidering(goal);
}
System.out.println("爬取结束");
}
public void actionPerformed(ActionEvent e)
{
if(e.getSource() == jbt1)
{
this.running = true;
goal = jtf1.getText();
if(goal == null)
{
JOptionPane.showMessageDialog(this, "网址不能为空");
}
else
{
if(url.indexOf(goal) == -1)
{
url.add(goal);
new Thread(this).start();
}
}
}
else if(e.getSource() == jbt2)
{
this.running = false;
}
}
public String delHTMLTag(String htmlStr)//删除html标签,获得文本字符串
{
String regEx_script="<script[^>]*?>[\\s\\S]*?<\\/script>"; //定义script的正则表达式
String regEx_style="<style[^>]*?>[\\s\\S]*?<\\/style>"; //定义style的正则表达式
String regEx_html="<[^>]+>"; //定义HTML标签的正则表达式
Pattern p_script=Pattern.compile(regEx_script,Pattern.CASE_INSENSITIVE);
Matcher m_script=p_script.matcher(htmlStr);
htmlStr=m_script.replaceAll(""); //过滤script标签
Pattern p_style=Pattern.compile(regEx_style,Pattern.CASE_INSENSITIVE);
Matcher m_style=p_style.matcher(htmlStr);
htmlStr=m_style.replaceAll(""); //过滤style标签
Pattern p_html=Pattern.compile(regEx_html,Pattern.CASE_INSENSITIVE);
Matcher m_html=p_html.matcher(htmlStr);
htmlStr=m_html.replaceAll(""); //过滤html标签
return htmlStr.trim(); //返回文本字符串
}
public void spidering(String path)
{
try{
driver1 = new ChromeDriver();
driver1.get(path);//打开目标网页
Thread.sleep(3000);
String pageSource;
pageSource = driver1.getPageSource();//获取通过js和css渲染过的源代码
Document doc1;
doc1 = Jsoup.parse(pageSource);
Elements links = doc1.select("a[href]");//获得网页链接
String temp;
//System.out.println(links.size());
for(Element link:links)
{
temp = link.attr("href");
temp = temp.replace("//", "https://");
suburl.add(temp);
System.out.println("link:"+temp);
}
String content = delHTMLTag(pageSource);
content = content.replaceAll(" ","");//删除空格
content = content.replaceAll("(\\r?\\n)+", "");//删除换行符等
jtp1.setText(content);
BufferedReader br = new BufferedReader(new FileReader("/Users/zpt/eclipse-workspace/敏感词库.txt"));//敏感词库读取器
String[] words;
StyledDocument doc2 = jtp1.getStyledDocument();
System.out.println("敏感词:");
while(true)
{
temp = br.readLine();//一行一行地读入敏感词
if(temp == null)//如果读完了,就关闭读入流,并退出
{
br.close();
System.out.println(1);
break;
}
System.out.println(temp);
words = temp.split("\\s+");//按空格分开每个敏感词
SimpleAttributeSet attrs = new SimpleAttributeSet();//设置文字属性
StyleConstants.setForeground(attrs, Color.BLUE);
StyleConstants.setBold(attrs, true);
for(String word:words)
{
int pos = 0;
while((pos = doc2.getText(0, doc2.getLength()).indexOf(word,pos)) != -1)//如果能找到敏感词,就修改为蓝色字体
{
doc2.setCharacterAttributes(pos, word.length(), attrs, false);
pos += word.length();
}
}
}
driver2 = new ChromeDriver();
driver2.get("https://csacademy.com/app/graph_editor/");//使用此网站生成有向图
Thread.sleep(3000);
WebElement button = driver2.findElement(By.xpath("/html/body/div[3]/div/div/div/div/div[1]/div[1]/div/button[2]"));
button.click();
WebElement ta = driver2.findElement(By.xpath("/html/body/div[3]/div/div/div/div/div[1]/div[2]/div[2]/textarea"));
for(int i =1;i<=9;i++)//移动光标到最后
{
ta.sendKeys(Keys.ARROW_DOWN);
}
for(int i = 1;i<= 44;i++)//删除作为示例的内容
{
ta.sendKeys(Keys.BACK_SPACE);
}
int count = 0;
for(String url:suburl)//将子网页链接输入网页的textarea
{
spider s = new spider();
ta.sendKeys(path+" "+url+"\n");
s.jtf1.setText(url);
count++;
if(count>5)
break;
}
}catch(Exception e) {e.printStackTrace();}
finally {
running = false;
driver1.quit();
}
}
public static void main(String[] args)
{
spider s = new spider();
}
}
参考文献:
1. 爬虫知识点丨“爬虫”的13条合规边界:爬虫知识点丨“爬虫”的13条合规边界_爬虫合法性使用的相关要求-CSDN博客
2.java去除html标签,提取文本内容:java去除html标签,提取文本内容_前端怎么把标签去掉提取出文字-CSDN博客
3.如何创建maven项目,并导入外部包(使用Eclipse编译器导入jsoup,下面的org.seleniumhq.selenium也同理): Jsoup的使用_eclipse配置jsoup-CSDN博客
4.如何下载chromedriver,以及需要下载的外部包(org.seleniumhq.selenium):JAVA自动化/爬虫 ---- WebDriver的使用_webdriver java-CSDN博客