第一課 jsoup教程

jsoup是一款Java的HTML解析器别洪,主要用來對HTML解析蹬敲。官網(wǎng) 中文文檔

在爬蟲的時候,當(dāng)我們用HttpClient之類的框架笋妥,獲取到網(wǎng)頁源碼之后,需要從網(wǎng)頁源碼中取出我們想要的內(nèi)容窄潭,

就可以使用jsoup這類HTML解析器了春宣。可以非常輕松的實現(xiàn)。

雖然jsoup也支持從某個地址直接去爬取網(wǎng)頁源碼月帝,但是只支持HTTP躏惋,HTTPS協(xié)議,支持不夠豐富嚷辅。

所以簿姨,主要還是用來對HTML進(jìn)行解析。

◆其中簸搞,要被解析的HTML可以是一個HTML的字符串扁位,可以是一個URL,可以是一個文件趁俊。

org.jsoup.Jsoup把輸入的HTML轉(zhuǎn)換成一個org.jsoup.nodes.Document對象域仇,然后從Document對象中取出想要的元素。

org.jsoup.nodes.Document繼承了org.jsoup.nodes.Element寺擂,Element又繼承了org.jsoup.nodes.Node類暇务。里面提供了豐富的方法來獲取HTML的元素。

◇從URL獲取HTML來解析

Document doc = Jsoup.connect("http://www.baidu.com/").get();
String title = doc.title();

其中Jsoup.connect("xxx")方法返回一個org.jsoup.Connection對象怔软。
在Connection對象中般卑,我們可以執(zhí)行g(shù)et或者post來執(zhí)行請求。但是在執(zhí)行請求之前爽雄,
我們可以使用Connection對象來設(shè)置一些請求信息。比如:頭信息沐鼠,cookie挚瘟,請求等待時間,代理等等來模擬瀏覽器的行為饲梭。

Document doc = Jsoup.connect("http://example.com")
  .data("query", "Java")
  .userAgent("Mozilla")
  .cookie("auth", "token")
  .timeout(3000)
  .post();

◆獲得Document對象后乘盖,接下來就是解析Document對象,并從中獲取我們想要的元素了憔涉。

Document中提供了豐富的方法來獲取指定元素订框。

◇使用DOM的方式來取得

getElementById(String id):通過id來獲取
  getElementsByTag(String tagName):通過標(biāo)簽名字來獲取
  getElementsByClass(String className):通過類名來獲取
  getElementsByAttribute(String key):通過屬性名字來獲取
  getElementsByAttributeValue(String key, String value):通過指定的屬性名字,屬性值來獲取
  getAllElements():獲取所有元素

◇通過類似于css或jQuery的選擇器來查找元素

使用的是Element類的下記方法:

public Elements select(String cssQuery)

通過傳入一個類似于CSS或jQuery的選擇器字符串兜叨,來查找指定元素穿扳。

例子:

File input = new File("/tmp/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");

Elements links = doc.select("a[href]"); //帶有href屬性的a元素
Elements pngs = doc.select("img[src$=.png]");
  //擴(kuò)展名為.png的圖片

Element masthead = doc.select("div.masthead").first();
  //class等于masthead的div標(biāo)簽

Elements resultLinks = doc.select("h3.r > a"); //在h3元素之后的a元素

選擇器的更多語法(可以在org.jsoup.select.Selector中查看到更多關(guān)于選擇器的語法):

tagname: 通過標(biāo)簽查找元素,比如:a
  ns|tag: 通過標(biāo)簽在命名空間查找元素国旷,比如:可以用 fb|name 語法來查找 <fb:name> 元素
  #id: 通過ID查找元素矛物,比如:#logo
  .class: 通過class名稱查找元素,比如:.masthead
  [attribute]: 利用屬性查找元素跪但,比如:[href]
  [^attr]: 利用屬性名前綴來查找元素履羞,比如:可以用[^data-] 來查找?guī)в蠬TML5 Dataset屬性的元素
  [attr=value]: 利用屬性值來查找元素,比如:[width=500]
  [attr^=value], [attr$=value], [attr=value]: 利用匹配屬性值開頭、結(jié)尾或包含屬性值來查找元素忆首,比如:[href=/path/]
  [attr~=regex]: 利用屬性值匹配正則表達(dá)式來查找元素爱榔,比如: 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標(biāo)簽下所有直接子元素
  siblingA + siblingB: 查找在A元素之前第一個同級元素B揣钦,比如:div.head + div
  siblingA ~ siblingX: 查找A元素之前的同級X元素雳灾,比如:h1 ~ p
  el, el, el:多個選擇器組合,查找匹配任一選擇器的唯一元素冯凹,例如:div.masthead, div.logo

偽選擇器selectors
  :lt(n): 查找哪些元素的同級索引值(它的位置在DOM樹中是相對于它的父節(jié)點)小于n谎亩,比如:td:lt(3) 表示小于三列的元素
  :gt(n):查找哪些元素的同級索引值大于n,比如: div p:gt(2)表示哪些div中有包含2個以上的p元素
  :eq(n): 查找哪些元素的同級索引值與n相等宇姚,比如:form input:eq(1)表示包含一個input標(biāo)簽的Form元素
  :has(seletor): 查找匹配選擇器包含元素的元素匈庭,比如:div:has(p)表示哪些div包含了p元素
  :not(selector): 查找與選擇器不匹配的元素,比如: div:not(.logo) 表示不包含 class="logo" 元素的所有 div 列表
  :contains(text): 查找包含給定文本的元素浑劳,搜索不區(qū)分大不寫阱持,比如: p:contains(jsoup)
  :containsOwn(text): 查找直接包含給定文本的元素
  :matches(regex): 查找哪些元素的文本匹配指定的正則表達(dá)式,比如:div:matches((?i)login)
  :matchesOwn(regex): 查找自身包含文本匹配指定正則表達(dá)式的元素
注意 ∧а:上述偽選擇器索引是從0開始的衷咽,也就是說第一個元素索引值為0,第二個元素index為1等

◆通過上面的選擇器蒜绽,我們可以取得一個Elements對象镶骗,它繼承了ArrayList對象,里面放的全是Element對象躲雅。

接下來我們要做的就是從Element對象中鼎姊,取出我們真正需要的內(nèi)容。

通常有下面幾種方法:

◇Element.text()

這個方法用來取得一個元素中的文本相赁。

◇Element.html()或Node.outerHtml()

這個方法用來取得一個元素中的html內(nèi)容

◇Node.attr(String key)

獲得一個屬性的值相寇,例如取得超鏈接<a href="">中href的值

綜合實例:采集開源中國項目信息

package com.company;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

public class Main {

    public static void main(String[] args) throws IOException {
        // write your code here
        Set<String> setUrls = new HashSet<>();
        for(int i = 1; i <= 5; i++)
        {
            String strUrl = "https://www.oschina.net/project/list?company=0&sort=score&lang=0&recommend=false&p="+i;
            setUrls.add(strUrl);
        }

        Set<String> setProjUrls = new HashSet<>();
        for(String stringUrl : setUrls)
        {
            Document document = Jsoup.connect(stringUrl)
                    .userAgent("Mozilla/5.0 (Windows NT 6.1; rv:30.0) Gecko/20100101 Firefox/30.0")
                    .get();
            //  System.out.println(document);
            Elements elements = document.select("div.box.item");
            for(Element element : elements)
            {
                Elements eleUrl = element.select("div.box-aw a");
                String strPrjUrl = eleUrl.attr("href");
                setProjUrls.add(strPrjUrl);
                //  System.out.println(strPrjUrl);
                Elements eleTitle = eleUrl.select(".title");
                String strTitle = eleTitle.text();
                // System.out.println(strTitle);
                Elements eleSummary = eleUrl.select(".summary");
                String strSummary = eleSummary.text();
                //  System.out.println(strSummary);
            }
        }

        for(String stringUrl : setProjUrls)
        {
            Document document = Jsoup.connect(stringUrl)
                    .userAgent("Mozilla/5.0 (Windows NT 6.1; rv:30.0) Gecko/20100101 Firefox/30.0")
                    .get();
            Elements elements = document.select("div.box-aw a h1");

            String strTitle = elements.text();
            System.out.println("標(biāo)題:" + strTitle);

            Elements elementsSection = document.select("section.list");

            int nSize = elementsSection.get(0).children().size();

            if(nSize == 0)
                continue;

            Element elementProtocol = elementsSection.get(0).child(0);
            Elements elesPro = elementProtocol.select("span");
            String strPro = elesPro.text();
            System.out.println("開源協(xié)議:" + strPro);

            nSize--;
            if(nSize == 0)
                continue;

            Element elementLan = elementsSection.get(0).child(1);
            Elements elesLan = elementLan.select("span").get(0).children();
            StringBuilder strlan = new StringBuilder();
            for(Element ele : elesLan)
            {
                String strLanTemp = ele.text();
                if(strLanTemp.indexOf("查看源碼")>=0)
                    break;
                strlan.append(strLanTemp+",");
            }
            if(elesLan.size()>0)
            {
                String strLanguage = strlan.toString().substring(0,strlan.length()-1);
                System.out.println("開發(fā)語言:" + strLanguage);
            }

            nSize--;
            if(nSize == 0)
                continue;

            Element elementOS = elementsSection.get(0).child(2);
            Elements elesOS = elementOS.select("span");
            String strOS = elesOS.text();
            System.out.println("操作系統(tǒng):" + strOS);

            nSize--;
            if(nSize == 0)
                continue;

            Element elementAuthor = elementsSection.get(0).child(3);
            Elements elesAuthor = elementAuthor.select("a.link");
            String strAuthor= elesAuthor.text();
            System.out.println("軟件作者;" + strAuthor);

            System.out.println("---------------------");
        }

    }
}

爬取騰訊首頁全部圖片

package com.company;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;

public class meizi {
    /**
     * 下載圖片到指定目錄
     *
     * @param filePath 文件路徑
     * @param imgUrl   圖片URL
     */
    public static void downImages(String filePath, String imgUrl) {
        // 若指定文件夾沒有钮科,則先創(chuàng)建
        File dir = new File(filePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        // 截取圖片文件名
        String fileName = imgUrl.substring(imgUrl.lastIndexOf('/') + 1, imgUrl.length());

        try {
            // 文件名里面可能有中文或者空格裆赵,所以這里要進(jìn)行處理。但空格又會被URLEncoder轉(zhuǎn)義為加號
            String urlTail = URLEncoder.encode(fileName, "UTF-8");
            // 因此要將加號轉(zhuǎn)化為UTF-8格式的%20
            imgUrl = imgUrl.substring(0, imgUrl.lastIndexOf('/') + 1) + urlTail.replaceAll("\\+", "\\%20");

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // 寫出的路徑
        File file = new File(filePath + File.separator + fileName);

        try {
            // 獲取圖片URL
            URL url = new URL(imgUrl);
            // 獲得連接
            URLConnection connection = url.openConnection();
            // 設(shè)置10秒的相應(yīng)時間
            connection.setConnectTimeout(10 * 1000);
            // 獲得輸入流
            InputStream in = connection.getInputStream();
            // 獲得輸出流
            BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
            // 構(gòu)建緩沖區(qū)
            byte[] buf = new byte[1024];
            int size;
            // 寫入到文件
            while (-1 != (size = in.read(buf))) {
                out.write(buf, 0, size);
            }
            out.close();
            in.close();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        // 利用Jsoup獲得連接
        Connection connect = Jsoup.connect("http://www.qq.com");
        try {
            // 得到Document對象
            Document document = connect.get();
            // 查找所有img標(biāo)簽
            Elements imgs = document.getElementsByTag("img");
            System.out.println("共檢測到下列圖片URL:");
            System.out.println("開始下載");
            // 遍歷img標(biāo)簽并獲得src的屬性
            for (Element element : imgs) {
                //獲取每個img標(biāo)簽URL "abs:"表示絕對路徑
                String imgSrc = element.attr("abs:src");
                // 打印URL
                System.out.println(imgSrc);
                //下載圖片到本地
                meizi.downImages("d:/img", imgSrc);
            }
            System.out.println("下載完成");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

解析json(悟空問答網(wǎng)案例)

package com.company;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.jsoup.Connection;
import org.jsoup.Jsoup;

import java.io.IOException;

/**
 * Created by Administrator on 2018/8/8.
 */
public class hello {
    public static void main(String[] args) throws IOException {

        Connection.Response res = Jsoup.connect("https://www.wukong.com/wenda/web/nativefeed/brow/?concern_id=6300775428692904450&t=1533714730319&_signature=DKZ7mhAQV9JbkTPBachKdgyme4")
                .header("Accept", "*/*")
                .header("Accept-Encoding", "gzip, deflate")
                .header("Accept-Language","zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")
                .header("Content-Type", "application/json;charset=UTF-8")
                .header("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0")
                .timeout(10000).ignoreContentType(true).execute();//.get();
        String body = res.body();
        System.out.println(body);
        JSONObject jsonObject2 = JSON.parseObject(body);
        JSONArray jsonArray = jsonObject2.getJSONArray("data");

        //JSONArray jsonArray1 = JSONArray.parseArray(JSON_ARRAY_STR);//因為JSONArray繼承了JSON跺嗽,所以這樣也是可以的

        //遍歷方式1
        int size = jsonArray.size();
        for (int i = 0; i < size; i++){
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            if(jsonObject.containsKey("question"))
            {
                JSONObject jsonObject3 = jsonObject.getJSONObject("question");
                String qid = jsonObject3.getString("qid");
                System.out.println(qid);
            }

        }
    }
}

fastjson補(bǔ)充

json字符串-數(shù)組類型與JSONArray之間的轉(zhuǎn)換

/**
     * json字符串-數(shù)組類型與JSONArray之間的轉(zhuǎn)換
     */
    public static void testJSONStrToJSONArray(){

        JSONArray jsonArray = JSON.parseArray(JSON_ARRAY_STR);
        //JSONArray jsonArray1 = JSONArray.parseArray(JSON_ARRAY_STR);//因為JSONArray繼承了JSON战授,所以這樣也是可以的

        //遍歷方式1
        int size = jsonArray.size();
        for (int i = 0; i < size; i++){
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            System.out.println(jsonObject.getString("studentName")+":"+jsonObject.getInteger("studentAge"));
        }

        //遍歷方式2
        for (Object obj : jsonArray) {
            JSONObject jsonObject = (JSONObject) obj;
            System.out.println(jsonObject.getString("studentName")+":"+jsonObject.getInteger("studentAge"));
        }
    }

復(fù)雜json格式字符串與JSONObject之間的轉(zhuǎn)換

/**
     * 復(fù)雜json格式字符串與JSONObject之間的轉(zhuǎn)換
     */
    public static void testComplexJSONStrToJSONObject(){

        JSONObject jsonObject = JSON.parseObject(COMPLEX_JSON_STR);
        //JSONObject jsonObject1 = JSONObject.parseObject(COMPLEX_JSON_STR);//因為JSONObject繼承了JSON页藻,所以這樣也是可以的

        String teacherName = jsonObject.getString("teacherName");
        Integer teacherAge = jsonObject.getInteger("teacherAge");
        JSONObject course = jsonObject.getJSONObject("course");
        JSONArray students = jsonObject.getJSONArray("students");

    }

另一個實例(采集悟空問答某個問題的評論信息)

package com.company;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.*;

public class meizi {

    public static void main(String[] args) {
        // 利用Jsoup獲得連接
        Connection connect = Jsoup.connect("https://www.wukong.com/question/6586953004245582083/");
        try {
            // 得到Document對象
            Document document = connect.get();

            Elements elements = document.select(".question-name");
            System.out.println(elements.get(0).text());

            Elements elements2 = document.select(".answer-item");
            for(Element element : elements2)
            {
                Elements elements3  = element.select(".answer-user-avatar img");
                System.out.println(elements3.attr("abs:src"));

                elements3  = element.select(".answer-user-name");
                System.out.println(elements3.text());

                elements3  = element.select(".answer-user-tag");
                System.out.println(elements3.text());

                elements3  = element.select(".answer-text");
                System.out.println(elements3.text());
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

練習(xí)

爬取獵聘網(wǎng) java關(guān)鍵字 企業(yè)招聘信息(包含詳情)
https://www.liepin.com/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌湾笛,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件废境,死亡現(xiàn)場離奇詭異,居然都是意外死亡筒繁,警方通過查閱死者的電腦和手機(jī)噩凹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毡咏,“玉大人驮宴,你說我怎么就攤上這事∨荤裕” “怎么了堵泽?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恢总。 經(jīng)常有香客問我迎罗,道長,這世上最難降的妖魔是什么片仿? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任纹安,我火速辦了婚禮,結(jié)果婚禮上砂豌,老公的妹妹穿的比我還像新娘厢岂。我一直安慰自己,他們只是感情好奸鸯,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著可帽,像睡著了一般娄涩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上映跟,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天蓄拣,我揣著相機(jī)與錄音,去河邊找鬼努隙。 笑死球恤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荸镊。 我是一名探鬼主播咽斧,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼堪置,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了张惹?” 一聲冷哼從身側(cè)響起舀锨,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宛逗,沒想到半個月后坎匿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡雷激,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年替蔬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屎暇。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡承桥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恭垦,到底是詐尸還是另有隱情快毛,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布番挺,位于F島的核電站唠帝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏玄柏。R本人自食惡果不足惜襟衰,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粪摘。 院中可真熱鬧瀑晒,春花似錦、人聲如沸徘意。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椎咧。三九已至玖详,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勤讽,已是汗流浹背蟋座。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留脚牍,地道東北人向臀。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像诸狭,于是被迫代替她去往敵國和親券膀。 傳聞我的和親對象是個殘疾皇子君纫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容

  • Jsoup官方文檔中文版 解析和遍歷一個HTML文檔 一個文檔的對象模型 文檔由多個Elements和TextNo...
    大灰狼zz閱讀 7,095評論 0 4
  • jsoup是一款Java的HTML解析器,主要用來對HTML解析三娩。官網(wǎng) 中文文檔 在爬蟲的時候庵芭,當(dāng)我們用HttpC...
    數(shù)據(jù)萌新閱讀 83,342評論 1 50
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,761評論 1 92
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5雀监? 答:HTML5是最新的HTML標(biāo)準(zhǔn)双吆。 注意:講述HT...
    kismetajun閱讀 27,522評論 1 45
  • 名人里,我最敬佩的人是蘇東坡会前,不僅因為他是文學(xué)藝術(shù)天才好乐,更因為他是一枚資深吃貨。如果說在中國古代的文人墨客中找一個...
    儲能閱讀 380評論 0 1