所要用到的jar包下载地址:
链接: https://pan.baidu.com/s/1E6562c2c7RVkSQvSBl9BbA 提取码: igfh
概念
Extensible Markup Language 可扩展标记语言
可扩展:标签都是自定义的。 意思就是说,无论是自闭标签还是围堵标签,名字可以自定义.
XML的应用
一. 当作配置文件
虽然JSON也同样有配置文件的功能,但是还不能取代XML,因此XML目前的主要作用是存储配置文件.
二.在网络中传输
JSON已经可以替代XML的网络传输功能,在使用传输时,优先使用JSON替代.
何谓传输网络功能?就是能兼容不同语言间的文件传输,比如把C语言的数据传输到JAVA语言.
xml与html的区别
一. xml标签都是任意自定义的,html标签是必须按照对应标签去定义。
二. xml的语法严格,html语法松散
XML的语法非常严谨,一个空格一个字符都不能搞错,因此要注意格式
三. xml是存储数据的,html是展示数据
XML的DOM
创建XML文件和其语法
创建XML文件
右键单击src模块目录,选择’new’ → 选择’XML Configuration File’ → 选择’JSP Tag Library Descriptor’ → 自定义XML文件名并点确定创建 → 将其格式由’tid’格式更改为’xm’格式.
XML文件的基本格式
1.xml第一行必须定义为文档声明
推荐的固定格式:<?xml version="1.0" encoding="UTF-8"?>
2. xml文档中有且仅有一个根标签
也就是说,所有标签都必须呆在一个大标签里头,且XML文档中不能有第二个大标签.
3. 标签属性值必须使用引号(单双都可)引起来
4. 标签必须正确关闭 即:如果是围堵标签,前后的标签名必须一致,且不能少前标签后或后标签
5. xml文档中,标签名称有大小写区分 推荐全部标签使用小写
6.标签名称可以包含字母、数字以及其他的字符
7.标签名称不能以数字或者标点符号为开头
8.标签名称不能以字母 xml(或者 XML、Xml 等等)开头
9.标签名称不能包含空格
XML文档头的详细定义格式
xml的文档头此前提到必须定义在第一行,且推荐的固定格式为**:<?xml version="1.0" encoding="UTF-8"?>**
但是,在一些其它需求下,XML文档头的属性列表格式还是会有所不同
一.version:版本号
这是必须的格式,虽然XML出了1.1版本,但是不能够向下兼容,因此主流的推荐版本为1.0
二.encoding:编码方式
告知解析引擎当前文档使用的字符集,默认值:ISO-8859-1 推荐为:UTF-8
三.standalone:是否为独立的XML文档
取值为yes和no,yes表示不依赖其他文件,no表示依赖其他文件
作用有两个
第一个作用:用于引入CSS文件,为XML文档美化
第二个作用(常用):引入约束文档,以对XML文件的标签格其属性进行特定的规则和格式约束.
标签与标签元素的相关注意
一.标签属性’ID’的注意事项
每个标签的ID属性值必须是唯一的,不能和其它标签的ID属性值相同.
二.CDATA区的作用
在该区域中的数据会被原样展示 即:用于解析展示一些代码,但是却不想这个代码被执行,要原汁原味地以文本形式展示.
格式: <![CDATA[ 数据 ]]>
XML中’约束’作用与应用
用于规定XML内容,如标签的格式,标签的顺序等等.
出于个人的定位,我们只需要看的懂约束语句就可以了,以方便在日后更改XML文档时,能够看得懂约束内容,依照其约束的规则修改XML文档即可
XML分为DTD约束和Schema约束
XML约束之DTD约束
详细参考:https://www.w3school.com.cn/dtd/index.asp
DTD是一个相对简单约束技术,目前用的不多.
文件后缀为’.dtd’
XML文档内部的DTD约束
DTD约束可以写在XML文档内部
格式:<!DOCTYPE 自定义目标根标签名 [元素声明体]>
列如:
<!DOCTYPE dtdtest [
<!ELEMENT dtdtest (userinfo,userinfo,address)>
<!ELEMENT userinfo (name,age,sex)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT sex (#PCDATA)>
<!ELEMENT address (nametwo,guojia)>
<!ELEMENT nametwo (#PCDATA)>
<!ELEMENT guojia (#PCDATA)>
]>
XML文档外部的DTD约束
DTD约束可以引用网络上的DTD格式文件,也可以引用本地硬盘中的DTD格式文件.
一.DTD约束文档的定义格式
当你要自己写一个dtd约束文件时,要省略<!DOCTYPE 自定义目标根标签名 []>
这样的声明格式
如:
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT student (name,age,sex,info)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT sex (#PCDATA)>
<!ELEMENT info (id,time)>
<!ELEMENT id (#PCDATA)>
<!ELEMENT time (#PCDATA)>
二.本地dtd引用格式:<!DOCTYPE 自定义目标根标签名 SYSTEM "dtd文件的位置">
列如:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE student SYSTEM "dtdfile.dtd">
<student>
<name>刘玉飞</name>
<age>15</age>
<sex>女</sex>
<info>
<id>10001</id>
<time>2020-1-2</time>
</info>
</student>
三.网络dtd引用格式:<!DOCTYPE 根标签名 PUBLIC "自定义dtd文件名字" "dtd文件的位置URL">
DTD的常用约束格式和语法
更多参考:https://www.w3school.com.cn/dtd/dtd_elements.asp
在该语法文档中规定了,dtdtest根标签内必须有address和userinfo子标签.
且userinfo子标签内必须要右name,age,sex三个标签.
address子标签内必须要右nametwo,guojia两个标签,并且的上下顺序也不能搞混.
XML约束之Schema约束
详细参考:https://www.w3school.com.cn/schema/index.asp
Schema是一个相对复杂的约束技术,因为它的语法比较难.
文件后缀为’.xsd’
引入.xsd约束文件
参考:https://www.w3school.com.cn/schema/schema_howto.asp
引入步骤一 在XML中填写约束目标的根标签名
格式:<约束目标的根标签名> </约束目标的根标签名)
引入步骤二 引入xsi前缀文件
格式:xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
注意:将其写入到步骤一的标签内
引入步骤三 引入xsd文件命名空间
格式:xsi:schemaLocation="自定义前缀名 xsd文件名.xsd"
列如:
xsi:schemaLocation="http://www.itcast.cn/xml student.xsd"
注意:将其写入到步骤一的标签内
注意:此处的’自定义前缀’,是为了以后对某个标签引用约束时所用的’前缀’,目的是为了省力气.
引入步骤四 为每一个xsd约束声明一个前缀,作为标识
格式:xmlns="自定义前缀名"
列如:
xmlns="http://www.itcast.cn/xml"
注意:将其写入到步骤一的标签内
Schema约束的格式和语法
参考:https://www.w3school.com.cn/schema/schema_simple.asp
解析的概念
所谓解析,就是获取html文件(也可以是代码,或者URL网页)或者xml文档的指定元素.
当需要使用爬虫,或者读取配置信息时,就需要用到解析.
解析的方式
1. DOM:将标记语言文档一次性加载进内存,在内存中形成一颗dom树
* 优点:操作方便,可以对文档进行CRUD的增删改查操作
* 缺点:占内存
2. SAX:逐行读取,基于事件驱动的。
* 优点:不占内存。
* 缺点:只能读取,不能增删改
这里,我们都用DOM的方式进行解析!
常见的解析器:
是的,解析时,还有解析器的区分,有的解析器只能以SAX方式解析,有的解析器只能以DOM的方式解析.
1.JAXP:sun公司提供的解析器,支持dom和sax两种思想
2.DOM4J:一款非常优秀的解析器
3. Jsoup(推荐):jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址,也就是网页的HTML文本内容,这意味着可以爬虫。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
而我们用的就是这一种,Jsoup通过DOM方式解析
- PULL:Android操作系统内置的解析器,sax方式的。
Jsoup解析器的应用
在使用jsoup解析器时,我们需要先将其XML文档/HTML文件实例化为一个Document对象.
因此,我们需要一个Jsoup的jay包,包名为’jsoup-1.11.2.jar’.
只有用以下几种方式实例化Document对象后,才可以进行很多增删改查的操作.
将文档实例化为DOM对象的几种方式
方式一:通过Document对象解析xml文档 并将其xml文档实例化为对象返回
分为两个步骤
注意:该方式也可以应用到HTML文档
步骤一:通过反射形式,获取xml文档的file路径.
格式:String 自定义String路径名 = 调用类的类名.class.getClassLoader().getResource("XML文档名.xml").getPath();
列如:
String fileStr = JsoupAnalyze.class.getClassLoader().getResource("XMLFile.xml").getPath();
注意:1.file文档名不能含有中文,不然会报错
步骤二: 将获取到的XML文档File对象实例化为Document对象
格式:Document 自定义xml文档实例化对象名 = Jsoup.parse(new File(自定义String路径名),"xml文档所使用的编码");
列如:
Document documentObj = Jsoup.parse(new File(fileStr),"utf-8" );
方式二:通过Documen解析字符串类型的网页代码,并将其代码实例化为对象返回
格式:Document 自定义代码实例化对象名 = Jsoup.parse(string字符串类类型的代码);
方式三:通过Document对象解析网页,并将其网页文件实例化为对象返回
格式:Document 自定义网页实例化对象名 Jsoup.connect("url链接").get();
列如:
Document documentUrlObj = Jsoup.connect("https://www.kuaidaili.com/free/inha/3/").get();
创建模拟浏览器的爬虫式DOM对象
在爬虫时,一些网站有防爬虫机制,此时我们在通过url链接获取dom对象的同时,为其设置协议头了
格式:Document 自定义代码实例化对象名 = Jsoup.connect("URL链接").timeout(自定义最大连接等待延时).header("自定义请求头的协议名", "自定义其请求头协议名对应的参数")get();
列如:
在这里插入代码片
三种选择器的应用
意义
选择器的目的是为了能够对Document对象所获取到的标签元素进行更方便,更细致的增删改查工作
选择器分为三种:
一.通过传统Element的DOM查询(仅用于简单查询时才推荐)
二.通过selector选择器查询(语法较Xpath稍微复杂一点,但可以直接进行格式调用)
三.通过Xpath选择器查询(语法简单,格式调用时需要转换为Element对象)
接下来详细介绍
传统Element的DOM查询
使用该选择器前,需要将 XML/HTML/页面代码 实例化为Document对象
传统的Element的DOM能够在解析器jay包’jsoup-1.11.2.jar’的基础上直接进行增删改查工作,但是不能通过一些语法进行更细致地查询
创建
创建格式一 根据id属性值获取唯一的element对象
格式:Element 自定义BOM实例化标签结果对象名 = 自定义xml/网页/代码实例化对象名.getElementById("id值");
列如:
Element idObj = documentObj.getElementById("two");
创建格式二 根据标签名称获取获取标签对象,以集合形式返回
格式:Elements 自定义BOM实例化标签结果对象名 = 自定义xml/网页/代码实例化对象名.getElementsByTag("目标标签名");
列如:
Elements byTag = documentObj.getElementsByTag("name");
创建格式三 根据属性名称获取标签对象,以集合形式返回
格式:Elements 自定义BOM实例化标签结果对象名 = 自定义xml/网页/代码实例化对象名.getElementsByAttribute("属性名称");
列如:
Elements attributeObj = documentObj.getElementsByAttribute("id");
创建格式四 根据对应的属性名,和其属性对应的值获取标签对象,以集合形式返回
格式:Elements 自定义BOM实例化标签结果对象名 = documentObj.getElementsByAttributeValue("属性名","属性名对应的值");
列如:
Elements attributeValue = documentObj.getElementsByAttributeValue("id","add" );
方法
方法一 String attr(String key):根据属性名称获取其对应的属性值
非集合格式:String 自定义属性值结果变量 = 自定义BOM实例化标签结果对象名.attr("属性名");
集合格式:String 自定义属性值结果变量 = 自定义BOM实例化标签结果对象名.get(索引值).attr("属性名");
方法二 String text():获取标签体的不包括代码在内的内容 以字符串形式返回
非集合格式:String 自定义文本结果变量 = 自定义BOM实例化标签结果对象名.text();
集合格式:String 自定义文本结果变量 = 自定义BOM实例化标签结果对象名.get(索引值).text();
方法三 String html():获取标签体包括代码在内的文本 并以字符串返回
非集合格式:String 自定代码体文本结果变量 = 自定义BOM实例化标签结果对象名.html();
集合格式:String 自定代码体文本结果变量 = 自定义BOM实例化标签结果对象名.get(索引值).html();
selector选择器查询
使用该选择器前,需要将 XML/HTML/页面代码 实例化为Document对象
selector选择器同样能够在jay包’jsoup-1.11.2.jar’的基础上直接工作,它可以使用一些语法进行更细致的增删改查,但语法对我来将稍微有点难懂,跟CSS的选择器语法一样.
官方语法参考:https://www.open-open.com/jsoup/selector-syntax.htm
创建
格式:Elements 自定义selector实例化标签结果对象名 = 自定义xml/网页/代码实例化对象名.select("选择器语句的表达式");
列如:
Elements select = documentObj.select("#liuyufeiage");//语法:根据id值获取对应元素
Elements select = documentObj.select("#liuyufeiage");//语法:根据id值获取对应元素
方法
同DOM查询的方法一致
Xpath选择器查询
使用该选择器前,需要将 XML/HTML/页面代码 实例化为Document对象
Xpath选择器需要在jay包’JsoupXpath-0.3.2.jar’的基础上进行工作,同样能细致地进行增删改查,语法也相对简单.
但是如果要进行’格式调用’(如text,html方法)则需要转换为Element对象才行.
Xpath往往多用于爬虫,反正我自己就是用这个爬免费代理的
语法参考:https://www.w3school.com.cn/xpath/xpath_syntax.asp
创建
步骤一 将Document对象实例化为JXDocument对象
格式:JXDocument 自定义Xpath选择器对象名 = new JXDocument(自定义网页/代码/XML文档的实例化对象名);
列如:
JXDocument jxdObj = new JXDocument(documentUrlObj);
步骤二 通过将其Xpath选择器对象实例化为结果对象,并填入语法参数,来获得结果
格式:List<JXNode> 自定义结果接收对象名 = 自定义Xpath选择器对象名.selN("Xpath选择器的语法");
列如:
List<JXNode> resultOfIp = jxdObj.selN("//td[@data-title='IP']");//获取端口
List<JXNode> resultOfPort = jxdObj.selN("//td[@data-title='PORT']");//获取IP地址
方法(转换为Element)
getElement(); 转为Element对象
如果你想要使用text,html等方法,则需要将结果接收对象转换为Element对象才行.
以下就是该方法的格式
Xpath没有类似于Element的’text()‘这种方法,因此不能仅仅取标签中的’文本元素’.
解决这种办法的途径就是将其转换为Element,再通过element的方法’text()‘获取’文本元素’
格式:Element 自定义Element转换结果接收对象名 = 自定义结果接收对象名.get(索引值).getElement();
列如:
Element elementOfIp = resultOfIp.get(i).getElement();
爬虫IP代理的示例
02点45分 今天暂且先搁置注册功能,先实现登录和代理抓取功能.
注册功能需要用到sql中表数据的’增’,目测用JdbcTemplate实现
03点38分 当使用for循环爬虫时,i数值为1的第一遍可以,第二遍则出现了HttpStatusException: HTTP error fetching URL. Status=503这个错误.
百度了下,应该是网站设置了防止爬虫的原因. 参考这个网站的解决方法:http://www.it1352.com/871098.html
目前先不鼓捣了 该网站内解决方法的内容如下:
//这将使您退出Http 503
Document document = Jsoup.connect(https://kissanime.to/ AnimeList /)
.userAgent(”Mozilla / 5.0(Macintosh; Intel Mac OS X 10.11; rv:49.0)Gecko / 20100101 Firefox / 49.0)。ignoreHttpErrors(true).followRedirects(true).timeout(100000 ).ignoreContentType(真)获得();
System.out.println(document.body());
}
16点37分 找到解决方案 首先,方案一就是设置协议头,在定义目标url链接的同时,通过Jsoup的’header’方法设置各项协议头和其参数,然后再用’get’方法返回一个dom对象.
参考网址:http://www.mamicode.com/info-detail-2300171.html
自己的代码如下:domObj = Jsoup.connect(url).timeout(5000)
.header(“Accept”, “text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3”)
.header(“Accept-Encoding”, “gzip, deflate, br”)
.header(“Accept-Language”, “zh-CN,zh;q=0.9”)
.header(“User-Agent”, “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36”).get();
然后发现还是不行,自己尝试刷新代理页面,发现如果小于一秒内刷新将会报错,所以想到需要给用于爬虫的方法设置延时.百度了下延时的方法,需要用到"Thread.sleep();",
此前在学习多线程中就用到过该方法,实际上他不仅仅是用于多线程的延时,也可以用于单一的某个方法的延时.因为’Thread.sleep’方法需要处理异常,所以我的代码如下:
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}19点36分 完工,但是注册有两小Bug,
1.当你要注册用户名是已经存在了的话,将会直接跳转到系统给出的页面,而不是我所指定的虚拟路径.
2.另外一点,在做约束时,对于密码(同为varchar类型)的非空约束不起效,空密码注册的话没有问题,而对用户名的非空约束则起效,不知道是为何.
package cn.proxyweb.web;
import cn.wanghaomiao.xpath.exception.XpathSyntaxErrorException;
import cn.wanghaomiao.xpath.model.JXDocument;
import cn.wanghaomiao.xpath.model.JXNode;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@WebServlet(name = "ServletProxy",value = "/proxy")
public class ServletProxy extends HttpServlet {
// 代理网址:http://www.kuaidaili.com/free/inha/1/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
String nameResult = (String) request.getAttribute("nameInfo");
String proxyResult = (String) request.getAttribute("proxyInfo");
response.setContentType("text/html;charset=utf-8");//设置编码
PrintWriter stringOutObj = response.getWriter();
stringOutObj.write("登陆成功!欢迎您!"+nameResult+",您要抓取的代理最大页数为"+proxyResult+"页");
stringOutObj.write("<br>");//换行
//定义一个字符串对象用于接收结果
String strForResult = new String();
for (int i = 1; i <= Integer.parseInt(proxyResult); i++) {
try {
Thread.sleep(1000);//设置调用'爬虫方法'的延时,以免触发代理网页的反爬虫机制.
} catch (InterruptedException e) {
e.printStackTrace();
}
String url = "http://www.kuaidaili.com/free/inha/"+i+"/";
strForResult += getProxyInfo(url);
}
System.out.println("======以下为循环完毕的所有结果=========");
System.out.println("未进行换行符号处理的内容"+strForResult);
strForResult+="以上为所有代理的获取结果";
// 将其中文本内容中的逗号','全部替换成'<br>'以实现换行效果.
// String replaceAll(String regex, String replacement) 指定一个字符,将其字符串中的该字符全部替换为另外一个字符
// 进行替换,并返回一个新的字符串
String replaceResult = strForResult.replaceAll(",", "<br>");
System.out.println("进行了换行符号处理的内容"+replaceResult);
//将处理后的结果输出到网页
stringOutObj.write("已经进行了文本格式的处理,这将能让个个代理后面都有一个换行符号,达到了美观和方便使用的目的.<br>以下为您所获取到的所有代理<br>"+replaceResult);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//该方法用于通过jsoup抓取网页代理
}
public String getProxyInfo(String url){
Document domObj = null;
JXDocument jxdObj = null;
Element eleObjForIp = null;
Element eleObjForPort = null;
List<JXNode> htmlResultForIp = null;
List<JXNode> htmlResultForPort = null;
String str = null;
try {
// domObj = Jsoup.connect(url).get();
domObj = Jsoup.connect(url).timeout(5000)
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
.header("Accept-Encoding", "gzip, deflate, br")
.header("Accept-Language", "zh-CN,zh;q=0.9")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36").get();
jxdObj = new JXDocument(domObj);
System.out.println("你好"+"当前页面网址为:"+url);
//通过xpath语法抓取指定的内容 列如://title[@lang='eng'] 选取所有 title 元素(标签),且这些元素拥有值为 eng 的 lang 属性。
htmlResultForIp = jxdObj.selN("//td[@data-title='IP']"); // 抓取IP data-title="IP"
htmlResultForPort = jxdObj.selN("//td[@data-title='PORT']"); // 抓取端口 data-title="PORT"
System.out.println(htmlResultForIp);
System.out.println(htmlResultForPort);
System.out.println("-----------------");
//将其存放抓取结果的集合元素转换为Element对象,以便获取纯文本内容
for (int i = 0; i < htmlResultForIp.size(); i++) {
eleObjForIp = htmlResultForIp.get(i).getElement();
eleObjForPort = htmlResultForPort.get(i).getElement();
/* //通过text方法将其转为纯文本内容,并添加到字符串
proxyParam.add(eleObjForIp.text()+":"+eleObjForPort.text());*/
if (i==0){
str = eleObjForIp.text()+":"+eleObjForPort.text()+",";
}else {
str += eleObjForIp.text()+":"+eleObjForPort.text()+",";
}
}
System.out.println(str);
return str;
} catch (Exception e) {
e.printStackTrace();
return str+"出现异常";
}
}
}