基本概念
计算机网络:把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息,共享硬件、软件、数据信息等资源。
计算机网络的主要功能:
资源共享;
信息传输与集中处理;
均衡负荷与分布处理;
综合信息服务 (www/综合业务数字网络 ISDN)。
网络通信协议:计算机网络中实现通信必须有一些约定即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
网络通信接口:为了使两个结点之间能进行对话,必须在它们之间建立通信工具(即接口),使彼此之间能进行信息交换。接口包括两部分:
硬件装置: 实现结点之间的信息传送
软件装置: 规定双方进行通信的约定协议
网络分层
分层原因:由于结点之间联系很复杂,在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。
封装/拆分
网络爬虫
网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。
随着网络的迅速发展,万维网成为大量信息的载体,如何有效地提取并利用这些信息成为一个巨大的挑战。搜索引擎(Search Engine),例如传统的通用搜索引擎AltaVista,Yahoo!和Google等,作为一个辅助人们检索信息的工具成为用户访问万维网的入口和指南。但是,这些通用性搜索引擎也存在着一定的局限性,如:
(1) 不同领域、不同背景的用户往往具有不同的检索目的和需求,通用搜索引擎所返回的结果包含大量用户不关心的网页。
(2)通用搜索引擎的目标是尽可能大的网络覆盖率,有限的搜索引擎服务器资源与无限的网络数据资源之间的矛盾将进一步加深。
(3)万维网数据形式的丰富和网络技术的不断发展,图片、数据库、音频、视频多媒体等不同数据大量出现,通用搜索引擎往往对这些信息含量密集且具有一定结构的数据无能为力,不能很好地发现和获取。
(4)通用搜索引擎大多提供基于关键字的检索,难以支持根据语义信息提出的查询。
为了解决上述问题,定向抓取相关网页资源的聚焦爬虫应运而生。聚焦爬虫是一个自动下载网页的程序,它根据既定的抓取目标,有选择的访问万维网上的网页与相关的链接,获取所需要的信息。与通用爬虫不同,聚焦爬虫并不追求大的覆盖,而将目标定为抓取与某一特定主题内容相关的网页,为面向主题的用户查询准备数据资源。
1.聚焦爬虫工作原理以及关键技术概述
网络爬虫是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页,是搜索引擎的重要组成。传统爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。聚焦爬虫的工作流程较为复杂,需要根据一定的网页分析算法过滤与主题无关的链接,保留有用的链接并将其放入等待抓取的URL队列。然后,它将根据一定的搜索策略从队列中选择下一步要抓取的网页URL,并重复上述过程,直到达到系统的某一条件时停止。另外,所有被爬虫抓取的网页将会被系统存贮,进行一定的分析、过滤,并建立索引,以便之后的查询和检索;对于聚焦爬虫来说,这一过程所得到的分析结果还可能对以后的抓取过程给出反馈和指导。
相对于通用网络爬虫,聚焦爬虫还需要解决三个主要问题:
(1) 对抓取目标的描述或定义;
(2) 对网页或数据的分析与过滤;
(3) 对URL的搜索策略。
抓取目标的描述和定义是决定网页分析算法与URL搜索策略如何制订的基础。而网页分析算法和候选URL排序算法是决定搜索引擎所提供的服务形式和爬虫网页抓取行为的关键所在。这两个部分的算法又是紧密相关的。
2.抓取目标描述
现有聚焦爬虫对抓取目标的描述可分为基于目标网页特征、基于目标数据模式和基于领域概念3种。
基于目标网页特征的爬虫所抓取、存储并索引的对象一般为网站或网页。根据种子样本获取方式可分为:
(1)预先给定的初始抓取种子样本;
(2)预先给定的网页分类目录和与分类目录对应的种子样本,如Yahoo!分类结构等;
(3)通过用户行为确定的抓取目标样例,分为:
(a) 用户浏览过程中显示标注的抓取样本;
(b) 通过用户日志挖掘得到访问模式及相关样本。
其中,网页特征可以是网页的内容特征,也可以是网页的链接结构特征,等等。
Demo
<span style="font-size:18px;"><span style="font-size:18px;">package com.bjsxt.wsl; // HelloSpider类 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; public class HelloSpider { publicstatic void main(String[] args) throws IOException { for(int i = 0; i < 10; i++) { //声明地址 URLurl = new URL("http://www.iteye.com/news/31449"); //伪装成浏览器进行爬取数据 URLConnectionconnection = url.openConnection(); connection.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, likeGecko) Chrome/48.0.2564.116 Safari/537.36"); //抓取信息 InputStreaminputStream = connection.getInputStream(); //查看流中的数据(字符流) BufferedReaderbufferedReader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8")); //开始读取数据 StringBufferbuffer = new StringBuffer(); Stringline = ""; while((line = bufferedReader.readLine()) != null) { buffer.append(line); } System.out.println(buffer); } } }</span></span>
UDP编程
在现有的网络中,网络通讯的方式主要有两种:
1、 TCP(传输控制协议)方式
2、 UDP(用户数据报协议)方式
为了方便理解这两种方式,还是先来看一个例子。大家使用手机时,向别人传递信息时有两种方式:拨打电话和发送短信。使用拨打电话的方式可以保证将信息传递给别人,因为别人接听电话时本身就确认接收到了该信息。而发送短信的方式价格低廉,使用方便,但是接收人有可能接收不到。
在网络通讯中,TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。而UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。
这两种传输方式都是实际的网络编程中进行使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则都通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据的传递。
由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。
在Java API中,实现UDP方式的编程,包含客户端网络编程和服务器端网络编程,主要由两个类实现,分别是:
(1)DatagramSocket类实现“网络连接”,包括客户端网络连接和服务器端网络连接。虽然UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。
(2)DatagramPacket类实现对于网络中传输的数据封装,也就是说,该类的对象代表网络中交换的数据。在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,都必须被处理成DatagramPacket类型的对象,该对象中包含发送到的地址、发送到的端口号以及发送的内容等。其实DatagramPacket类的作用类似于现实中的信件,在信件中包含信件发送到的地址以及接收人,还有发送的内容等,邮局只需要按照地址传递即可。在接收数据时,接收到的数据也必须被处理成DatagramPacket类型的对象,在该对象中包含发送方的地址、端口号等信息,也包含数据的内容。和TCP方式的网络传输相比,IO编程在UDP方式的网络编程中变得不是必须的内容,结构也要比TCP方式的网络编程简单一些。
demo功能是实现将客户端程序的系统时间发送给服务器端,服务器端接收到时间以后,向客户端反馈字符串“OK”。实现该功能的客户端代码如下所示:
<span style="font-size:18px;"><span style="font-size:18px;">package udp; import java.net.*; import java.util.*; /** * 简单的UDP客户端,实现向服务器端发生系统时间功能 */ public class SimpleUDPClient { public static void main(String[] args) { DatagramSocket ds = null;//连接对象 DatagramPacket sendDp; //发送数据包对象 DatagramPacket receiveDp; //接收数据包对象 String serverHost ="127.0.0.1"; //服务器IP int serverPort = 10010; //服务器端口号 try{ //建立连接 ds = newDatagramSocket(); //初始化发送数据 Date d = new Date(); //当前时间 String content =d.toString(); //转换为字符串 byte[] data =content.getBytes(); //初始化发送包对象 InetAddress address =InetAddress.getByName(serverHost); sendDp = newDatagramPacket(data,data.length,address,serverPort); //发送 ds.send(sendDp); //初始化接收数据 byte[] b = newbyte[1024]; receiveDp = newDatagramPacket(b,b.length); //接收 ds.receive(receiveDp); //读取反馈内容,并输出 byte[] response =receiveDp.getData(); int len =receiveDp.getLength(); String s = newString(response,0,len); System.out.println("服务器端反馈为:"+ s); }catch(Exception e){ e.printStackTrace(); }finally{ try{ //关闭连接 ds.close(); }catch(Exceptione){} } } }</span></span>
Socket编程
基于tcp协议,建立稳定连接的点对点的通信;
实时、快速、安全性高、占用系统资源多、效率低;
“请求-响应”模式
客户端:在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序。
服务器:第一次通讯中等待连接的程序被称作服务器端(Server)程序。
Socket:发送TCP消息。
ServerSocket:创建服务器
套接字是一种进程间的数据交换机制。这些进程既可以在同一机器上,也可以在通过网络连接的不同机器上。换句话说,套接字起到通信端点的作用。单个套接字是一个端点,而一对套接字则构成一个双向通信信道,使非关联进程可以在本地或通过网络进行数据交换。一旦建立套接字连接,数据即可在相同或不同的系统中双向或单向发送,直到其中一个端点关闭连接。
URL(Uniform ResourceLocator):统一资源定位符,由4部分组成:协议、存放资源的主机域名、资源文件名和端口号。
URL是指向互联网“资源”的指针,资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
【扩展】
Uniform Resource Identifier,统一资源标识符;
URL:Uniform ResourceLocator,统一资源定位符;
URN:Uniform ResourceName,统一资源名称。
其中,URL,URN是URI的子集。
Web上地址的基本形式是URI,它代表统一资源标识符。有两种形式:
URL:目前URI的最普遍形式就是无处不在的URL或统一资源定位器。
URN:URL的一种更新形式,统一资源名称(URN, Uniform Resource Name)不依赖于位置,并且有可能减少失效连接的个数。但是其流行还需假以时日,因为它需要更精密软件的支持。
URI是以某种统一的(标准化的)方式标识资源的简单字符串。
Demo聊天室:实现简单控制台和服务器之间的聊天功能。
<span style="font-size:18px;"><span style="font-size:18px;">package com.bjsxt.wsl.s4; //HelloServer类 import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class HelloServer { //声明一个容器,存放所有当前正在访问的客户端 privatestatic List<ServerThread> threadList = new ArrayList<>(); publicstatic void main(String[] args) throws IOException { System.out.println("---------------我是服务器----------------"); //创建一个服务器 ServerSocketserverSocket = new ServerSocket(18888); //让服务器一直等待接受 //服务器开始监听端口等待客户端请求 while(true) { Socketsocket = serverSocket.accept(); //开始启动一个线程专门获取客户端的信息,并将信息返回 ServerThreadserverThread = new ServerThread(socket, threadList); //将当前客户端加入到list threadList.add(serverThread); newThread(serverThread).start(); } } } =============================================================================== package com.bjsxt.wsl.s4; //HelloSocket类 import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; public class HelloSocket { publicstatic void main(String[] args) throws UnknownHostException, IOException { System.out.println("-------------我是客户端--------------"); //创建一个客户端 Socketsocket = new Socket("192.168.1.164", 18888); //开启一个线程专门负责接收用户在控制台的输入,并把它写出给服务器 newThread(new SocketSendThread(socket)).start(); //开启一个线程专门负责接收服务器的返回信息并打印 newThread(new SocketAcceptThread(socket)).start(); } } =============================================================================== package com.bjsxt.wsl.s4; //ServerThread类 import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.util.List; public class ServerThread implementsRunnable { //封装流的信息 privateDataInputStream dataInputStream = null; privateDataOutputStream dataOutputStream = null; privateSocket socket = null; privateList<ServerThread> threadList = null; /** * 构造器 * @param socket */ publicServerThread(Socket socket, List<ServerThread> threadList) { try{ this.socket= socket; this.threadList= threadList; dataInputStream= new DataInputStream(socket.getInputStream()); dataOutputStream= new DataOutputStream(socket.getOutputStream()); }catch (IOException e) { e.printStackTrace(); } } @Override publicvoid run() { while(true) { //接收客户端发送给服务器的信息 sendAllSocket(accept()); } } /** * 将消息发送给其他人 * @param msg */ publicvoid sendAllSocket(String msg) { //遍历 for(ServerThread serverThread : threadList) { serverThread.send(msg); } } /** * 将小心返回给客户打断 * @param msg */ publicvoid send(String msg) { try{ dataOutputStream.writeUTF(socket.getInetAddress()+ " 说:" + msg); dataOutputStream.flush(); }catch (IOException e) { e.printStackTrace(); } } /** * 开始接受客户端的消息 * @return */ privateString accept() { Stringmsg = ""; try{ msg= dataInputStream.readUTF(); }catch (IOException e) { e.printStackTrace(); } returnmsg; } } =============================================================================== package com.bjsxt.wsl.s4; //SocketAcceptThread类 import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; public class SocketAcceptThread implements Runnable{ //封装流对象,防止重复创建 privateDataInputStream dataInputStream = null; publicSocketAcceptThread(Socket socket) { try{ //创建一个输入流获取服务器的返回信息 dataInputStream= new DataInputStream(socket.getInputStream()); }catch (IOException e) { e.printStackTrace(); } } @Override publicvoid run() { while(true) { accept(); } } /** * 接收并打印服务器的返回信息 */ privatevoid accept() { try{ Stringmsg = dataInputStream.readUTF(); System.out.println(msg); }catch (IOException e) { e.printStackTrace(); } } } =============================================================================== package com.bjsxt.wsl.s4; //SocketSendThread类 import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; public class SocketSendThread implementsRunnable { //将可复用的类进行封装 privateBufferedReader bufferedReader = null; privateDataOutputStream dataOutputStream = null; /** * 构造器 * @param socket */ publicSocketSendThread(Socket socket) { try{ //接受用户在控制台的输入 bufferedReader= new BufferedReader(new InputStreamReader(System.in)); //将客户端的信息发送给服务器 dataOutputStream= new DataOutputStream(socket.getOutputStream()); }catch (IOException e) { e.printStackTrace(); } } @Override publicvoid run() { //重复监听客户端的输入 while(true) { send(); } } /** * 第一件 获取用户控制台的输入的数据 * 第二件 将输入的数据发送给服务器 */ privatevoid send() { try{ //定义输入信息 Stringmsg = bufferedReader.readLine(); //判断用户输入的信息是否有效 if(msg != null && !"".equals(msg)) { dataOutputStream.writeUTF(msg); } }catch (IOException e) { e.printStackTrace(); } } }</span></span>
业务思想
网络编程是作为编程人员必傍身之计,其中想法是很重要的,也希望大家能够更快的进入网络编程的世界。