Java爬虫-快速入门 HttpClient+JSoup详解 (附各种Demo)

Java爬虫-快速入门

HttpClient+JSoup详解 (附各种Demo)

写在前面:记录了学习数据挖掘以来的学习历程,先上之前的一些总结,随着学习的加深会慢慢更新。

Java爬虫-快速入门  目录

1.所需环境

2.HttpClient与Jsoup简介

3.为什么要和JSoup共同使用?

4.项目maven依赖

5.HttpClient的入门使用

(1)简介

(2)上一个简单的示例Demo:

6.JSoup的入门使用

(1)主要类的简介

(2)使用dom方法(遍历一个Document对象)来查找元素 -不推荐

(3)使用选择器语法来查找元素

(4)从元素抽取属性,文本和HTML

(5)这个示例是当时看完某教程后的练手Demo:

7.一个完整的Demo

(1)GetResult类,用HttpClient来抓取页面

(2)GetImg类,用Jsoup解析获取的页面

(3)测试单元test1.java


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:



    org.apache.httpcomponents
    httpclient
    4.5.6

 JSoup 1.8.3


    org.jsoup
    jsoup
    1.8.3

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.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 语法来查找 元素
#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 = "

An example link.

";
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();
    // "example"
String linkInnerH = link.html(); // "example"//取得链接内的html内容


tips:
上述方法是元素数据访问的核心办法。此外还其它一些方法可以使用:

Element.id()
Element.tagName()
Element.className() and Element.hasClass(String className)
这些访问器方法都有相应的setter方法来更改数据.

                                                                                                                                                       引用自CookBook-抽取元素

(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我这里就不班门弄斧了,也是正在学习,想要了解的转这里。 

 

你可能感兴趣的:(Java爬虫)