Java爬虫-快速入门
HttpClient+JSoup详解 (附各种Demo)
写在前面:记录了学习数据挖掘以来的学习历程,先上之前的一些总结,随着学习的加深会慢慢更新。
Java爬虫-快速入门 目录
(2)使用dom方法(遍历一个Document对象)来查找元素 -不推荐
(1)GetResult类,用HttpClient来抓取页面
1.所需环境
我使用的环境是IDEA+Maven创建的maven project。其他方法亦可。
关于IDEA的下载安装及使用,可以参考这里:https://www.cnblogs.com/demingblog/p/5817125.html
关于Maven的下载安装,官网下载地址:http://maven.apache.org/download.cgi
关于Maven的相关配置请参考:https://blog.csdn.net/cs4380/article/details/79158268
2.HttpClient与Jsoup简介
1.什么是HttpClient?
HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
2.为什么使用HttpClient?
HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。
它的主要功能有:
(1) 实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
(2) 支持自动转向
(3) 支持 HTTPS 协议
(4) 支持代理服务器等
3.什么是JSoup?
jsoup是一款Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
它的主要功能有:
(1) 从一个URL,文件或字符串中解析HTML;
(2) 使用DOM或CSS选择器来查找、取出数据;
(3) 可操作HTML元素、属性、文本;
3.为什么要和JSoup共同使用?
httpClient 属于专业的抓取网页的库,可以设置代理,抓取失败可以重试抓取
在我的实际使用中,单独用jsoup也可以直接抓取网页,但是在抓取上,jsoup比较弱,API简单,功能也简单,主要是扩展htmlparser的功能吧,解析html。测试过程中jsoup抓取页面经常报错(time out等等)。
因此,我们可以用httpclient抓取网页,再用Jsoup.parse解析页面。
4.项目maven依赖
HttpClient 4.5.6:
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
JSoup 1.8.3
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.8.3</version>
</dependency>
5.HttpClient的入门使用
(1)简介
使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。
1. 创建HttpClient对象。
2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
6. 释放连接。无论执行方法是否成功,都必须释放连接
(2)上一个简单的示例Demo:
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class test {
//声明需要爬取的网址
static String URL="http://www.baidu.com";
//主函数入口
public static void main(String args[]){
//建立一个新的请求客户端
CloseableHttpClient httpClient=HttpClients.createDefault();
//使用HttpGet的方式请求网址
HttpGet httpGet = new HttpGet(URL);
//获取网址的返回结果
CloseableHttpResponse response=null;
try {
response=httpClient.execute(httpGet);
} catch (IOException e) {
e.printStackTrace();
}
//获取返回结果中的实体
HttpEntity entity = response.getEntity();
//将返回的实体输出
try {
System.out.println(EntityUtils.toString(entity));
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.JSoup的入门使用
我学习JSoup的过程中,这个作者的博客专栏提供了很大的帮助:专栏:使用JSOUP实现网络爬虫
同时,可以参考jsoup Cookbook(中文版),也非常的棒。
这里就根据CookBook来对jsoup的使用进行归纳
(1)主要类的简介
JSoup API中有6
个包提供用于开发jsoup应用程序的类和接口。包中有很多类。
我们常用到的主要类:Jsoup、Document、Element/Elements
1.Document类:
是Jsoup的HTML文档对象模型,它由很多节点组成。
Document将html文档(String类型)解析为很多的Element和TextNode对象,其中TextNode继承自Node对象
继承链:
Document extends Element extends Node
TextNode extends Node
2.Element/Elements类:
Element类是Node的直接子类,它表示由一个标签名,多个属性和子节点组成的html元素。从这个元素中,你可以提取数据,可以遍历节点树,可以操纵html。
注:Node是节点的抽象模型。Elements, Documents, Comments等都是节点的实例。
Elements对象类似一个由多个Element对象组成的集合,有一接口为List<Element>,可以使用Element.select()方法去得到Elements 对象。
注:判断Elements对象是否为空需要.isEmpty()方法,而Element可以用==null
注:Elements.select()不能得到Element对象
3.Jsoup类:
通常用来建立连接,获取响应或发送响应
一个简单示例:
Document doc = Jsoup.connect(url).timeout(2000).get();
(2)使用dom方法(遍历一个Document对象)来查找元素 -不推荐
Elements这个对象提供了一系列类似于DOM的方法来查找元素,抽取并处理其中的数据。
1.查找元素
getElementById(String id)
getElementsByTag(String tag)
getElementsByClass(String className)
getElementsByAttribute(String key) (and related methods)
Element siblings: siblingElements(), firstElementSibling(), lastElementSibling(); nextElementSibling(), previousElementSibling()
Graph: parent(), children(), child(int index)
2.元素数据
attr(String key)获取属性attr(String key, String value)设置属性
attributes()获取所有属性
id(), className() and classNames()
text()获取文本内容text(String value) 设置文本内容
html()获取元素内HTMLhtml(String value)设置元素内的HTML内容
outerHtml()获取元素外HTML内容
data()获取数据内容(例如:script和style标签)
tag() and tagName()
3.操作HTML和文本
append(String html), prepend(String html)
appendText(String text), prependText(String text)
appendElement(String tagName), prependElement(String tagName)
html(String value)引用自CookBook-dom
(3)使用选择器语法来查找元素
jsoup与其他解析器的区别就是可以使用类似jquery的选择器语法来搜索及过滤出所需的元素
这里我们还要介绍最重要的选择器语法
jsoup elements对象支持类似于CSS (或jquery)的选择器语法,来实现非常强大和灵活的查找功能。.
这个select 方法在Document, Element,或Elements对象中都可以使用。且是上下文相关的,因此可实现指定元素的过滤,或者链式选择访问。
Select方法将返回一个Elements集合,并提供一组方法来抽取和处理结果。
1.Selector选择器概述
tagname: 通过标签查找元素,比如:a
ns|tag: 通过标签在命名空间查找元素,比如:可以用 fb|name 语法来查找 <fb:name> 元素
#id: 通过ID查找元素,比如:#logo
.class: 通过class名称查找元素,比如:.masthead
[attribute]: 利用属性查找元素,比如:[href]
[^attr]: 利用属性名前缀来查找元素,比如:可以用[^data-] 来查找带有HTML5 Dataset属性的元素
[attr=value]: 利用属性值来查找元素,比如:[width=500]
[attr^=value], [attr$=value], [attr*=value]: 利用匹配属性值开头、结尾或包含属性值来查找元素,比如:[href*=/path/]
[attr~=regex]: 利用属性值匹配正则表达式来查找元素,比如: img[src~=(?i)\.(png|jpe?g)]
*: 这个符号将匹配所有元素
Selector选择器组合使用
el#id: 元素+ID,比如: div#logo
el.class: 元素+class,比如: div.masthead
el[attr]: 元素+class,比如: a[href]
任意组合,比如:a[href].highlight
ancestor child: 查找某个元素下子元素,比如:可以用.body p 查找在"body"元素下的所有 p元素
parent > child: 查找某个父元素下的直接子元素,比如:可以用div.content > p 查找 p 元素,也可以用body > * 查找body标签下所有直接子元素
siblingA + siblingB: 查找在A元素之前第一个同级元素B,比如:div.head + div
siblingA ~ siblingX: 查找A元素之前的同级X元素,比如:h1 ~ p
el, el, el:多个选择器组合,查找匹配任一选择器的唯一元素,例如:div.masthead, div.logo
2.伪选择器selectors
:lt(n): 查找哪些元素的同级索引值(它的位置在DOM树中是相对于它的父节点)小于n,比如:td:lt(3) 表示小于三列的元素
:gt(n):查找哪些元素的同级索引值大于n,比如: div p:gt(2)表示哪些div中有包含2个以上的p元素
:eq(n): 查找哪些元素的同级索引值与n相等,比如:form input:eq(1)表示包含一个input标签的Form元素
:has(seletor): 查找匹配选择器包含元素的元素,比如:div:has(p)表示哪些div包含了p元素
:not(selector): 查找与选择器不匹配的元素,比如: div:not(.logo) 表示不包含 class=logo 元素的所有 div 列表
:contains(text): 查找包含给定文本的元素,搜索不区分大不写,比如: p:contains(jsoup)
:containsOwn(text): 查找直接包含给定文本的元素
:matches(regex): 查找哪些元素的文本匹配指定的正则表达式,比如:div:matches((?i)login)
:matchesOwn(regex): 查找自身包含文本匹配指定正则表达式的元素
注意:上述伪选择器索引是从0开始的,也就是说第一个元素索引值为0,第二个元素index为1等*/引用自CookBook-选择器
select()的使用还可以参考这一篇博文,写的也很好。
(4)从元素抽取属性,文本和HTML
在解析获得一个Document实例对象,并查找到一些元素之后,如何取得在这些元素中的数据呢?
方法
要取得一个属性的值,可以使用Node.attr(String key) 方法
对于一个元素中的文本,可以使用Element.text()方法
对于要取得元素或属性中的HTML内容,可以使用Element.html(), 或 Node.outerHtml()方法
示例:
String html = "<p>An <a href='http://example.com/'><b>example</b></a> link.</p>";
Document doc = Jsoup.parse(html);//解析HTML字符串返回一个Document实现
Element link = doc.select("a").first();//查找第一个a元素String text = doc.body().text(); // "An example link"//取得字符串中的文本
String linkHref = link.attr("href"); // "http://example.com/"//取得链接地址
String linkText = link.text(); // "example""//取得链接地址中的文本String linkOuterH = link.outerHtml();
// "<a href="http://example.com"><b>example</b></a>"
String linkInnerH = link.html(); // "<b>example</b>"//取得链接内的html内容
tips:
上述方法是元素数据访问的核心办法。此外还其它一些方法可以使用:Element.id()
Element.tagName()
Element.className() and Element.hasClass(String className)
这些访问器方法都有相应的setter方法来更改数据.
(5)这个示例是当时看完某教程后的练手Demo:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
class test2{
public static void main(String args[]){
//设置需要爬取的网页,这里为方便起见就直接用Jsoup自带的api来爬取网页了
//这个网页是桂林电子科技大的信息科技学院的学院新闻版块页面
String url = "http://www.guit.edu.cn/xwzx/mtxk.htm";
//声明Document类,来存储爬取到的html文档
Document doc = null;
try {
doc = Jsoup.connect(url).timeout(2000).get();
//调用Jsoup类中的connect()方法,url为需要爬取的页面
//timeout()来设置超时时间,get()方法来获取响应页面
} catch (IOException e) {
e.printStackTrace();
}
//System.out.println(doc);//测试用
//使用select选择器
Elements elements = doc.select(".box-list").select(".oh").select("a");
//System.out.println(elements);//测试用
for(Element e:elements){
if(e.text().length()>8){
//逐条输出新闻信息
System.out.println(e.text());
}
}
}
}
7.一个完整的Demo
当时写这个Demo时,这篇博文给了我很大帮助。我对其中的代码加以完善和详细注释,附在下面。
(1)GetResult类,用HttpClient来抓取页面
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
public class GetResult {
public static String getResult(String url) throws Exception {
//这里用了try-with-resource语法,在try()括号中的资源会在try语句块执行完之后自动释放
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(new HttpGetConfig(url)))
{
String result = EntityUtils.toString(response.getEntity());
return result;
} catch (Exception e) {
System.out.println("获取失败");
return "";
}
//所以不需要再finally中释放资源。
}
}
//内部类,继承HttpGet,为了设置请求超时的参数
class HttpGetConfig extends HttpGet {
public HttpGetConfig(String url) {
super(url);
setDefaulConfig();
}
private void setDefaulConfig() {
this.setConfig(RequestConfig.custom()
.setConnectionRequestTimeout(10000)
.setConnectTimeout(10000)
.setSocketTimeout(10000).build());
this.setHeader("User-Agent", "spider");
}
}
(2)GetImg类,用Jsoup解析获取的页面
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
public class GetImg {
public class GetImg {
public GetImg(String url) throws Exception{
//获取工具类GetResult返回的html,并用Jsoup解析
String result = GetResult.getResult(url);
Document document = Jsoup.parse(result);
//若HTML文档包含相对URLs路径,需要将这些相对路径转换成绝对路径的URLs
document.setBaseUri(url);//指定base URI
//获取所有的img元素
Elements elements = document.select("img");
int i=1;
for (Element e : elements) {
//获取每个src的绝对路径
String src = e.absUrl("src");
URL urlSource = new URL(src);
URLConnection urlConnection = urlSource.openConnection();
//设置图片名字
String imageName = src.substring(src.lastIndexOf("/") + 1,i++);
//控制台输出图片的src
System.out.println(e.absUrl("src"));
//通过URLConnection得到一个流,将图片写到流中,并且新建文件保存
InputStream in = urlConnection.getInputStream();
OutputStream out = new FileOutputStream(new File("E:\\IDEA\\imgs\\", imageName));
byte[] buf = new byte[1024];
int l = 0;
while ((l = in.read(buf)) != -1) {
out.write(buf, 0, l);
}
}
}
}
想要了解更多关于URL的处理,可以查看CookBook-URL
(3)测试单元test1.java
import org.junit.Test;
/**
* 2018-9-8 单元测试
* @author ljx
*/
public class test1 {
@Test
public void testGetResult() throws Exception{
GetImg getImg = new GetImg("https://www.bilibili.com/");
}
}
关于JUnit我这里就不班门弄斧了,也是正在学习,想要了解的转这里。