文適合有 Java 基礎(chǔ)知識的人群
本文作者:HelloGitHub-秦人
HelloGitHub 推出的《講解開源項目》系列,今天給大家?guī)硪豢铋_源 Java 版一款網(wǎng)頁元素解析框架——jsoup,通過程序自動獲取網(wǎng)頁數(shù)據(jù)。
項目源碼地址:https://github.com/jhy/jsoup
jsoup 是一款 Java 的 HTML 解析器。可直接解析某個 URL 地址的 HTML 文本內(nèi)容。它提供了一套很省力的 API,可通過 DOM、CSS 以及類似于 jQuery 選擇器的操作方法來取出和操作數(shù)據(jù)。
jsoup 主要功能:
將項目導(dǎo)入 idea 開發(fā)工具,會自動下載 maven 項目需要的依賴。源碼的項目結(jié)構(gòu)如下:
快速學(xué)習(xí)源碼是每個程序員必備的技能,我總結(jié)了以下幾點:
git clone https://github.com/jhy/jsoup
通過上面的方法,我們很快可知 example 目錄是測試代碼,那我們直接來運行。注:有些測試代碼需要稍微改造一下才可以運行。
例如,jsoup 的 Wikipedia 測試代碼:
public class Wikipedia {
public static void main(String[] args) throws IOException {
Document doc = Jsoup.connect("http://en.wikipedia.org/").get();
log(doc.title());
Elements newsHeadlines = doc.select("#mp-itn b a");
for (Element headline : newsHeadlines) {
log("%s\n\t%s", headline.attr("title"), headline.absUrl("href"));
}
}
private static void log(String msg, String... vals) {
System.out.println(String.format(msg, vals));
}
}
說明:上面代碼是獲取頁面(http://en.wikipedia.org/)包含(#mp-itn b a)選擇器的所有元素,并打印這些元素的 title , herf 屬性。維基百科 國內(nèi)無法訪問,所以上面這段代碼運行會報錯。
改造后可運行的代碼如下:
public static void main(String[] args) throws IOException {
Document doc = Jsoup.connect("https://www.baidu.com/").get();
Elements newsHeadlines = doc.select("a[href]");
for (Element headline : newsHeadlines) {
System.out.println("href: " +headline.absUrl("href") );
}
}
Jsoup 的工作原理,首先需要指定一個 URL,框架發(fā)送 HTTP 請求,然后獲取響應(yīng)頁面內(nèi)容,然后通過各種選擇器獲取頁面數(shù)據(jù)。整個工作流程如下圖:
以上面為例:
Document doc = Jsoup.connect("https://www.baidu.com/").get();
這行代碼就是發(fā)送 HTTP 請求,并獲取頁面響應(yīng)數(shù)據(jù)。
Elements newsHeadlines = doc.select("a[href]");
定義選擇器,獲取匹配選擇器的數(shù)據(jù)。
for (Element headline : newsHeadlines) {
System.out.println("href: " +headline.absUrl("href") );
}
這里對數(shù)據(jù)只做了一個簡單的數(shù)據(jù)打印,當(dāng)然這些數(shù)據(jù)可寫入文件或數(shù)據(jù)的。
獲取豆瓣讀書 -> 新書速遞中每本新書的基本信息。包括:書名、書圖片鏈接、作者、內(nèi)容簡介(詳情頁面)、作者簡介(詳情頁面)、當(dāng)當(dāng)網(wǎng)書的價格(詳情頁面),最后將獲取的數(shù)據(jù)保存到 Excel 文件。
目標(biāo)鏈接:https://book.douban.com/latest?icn=index-latestbook-all
項目引入 jsoup、lombok、easyexcel 三個庫。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>JsoupTest</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
</dependencies>
</project>
public class BookInfoUtils {
public static List<BookEntity> getBookInfoList(String url) throws IOException {
List<BookEntity> bookEntities=new ArrayList<>();
Document doc = Jsoup.connect(url).get();
Elements liDiv = doc.select("#content > div > div.article > ul > li");
for (Element li : liDiv) {
Elements urls = li.select("a[href]");
Elements imgUrl = li.select("a > img");
Elements bookName = li.select(" div > h2 > a");
Elements starsCount = li.select(" div > p.rating > span.font-small.color-lightgray");
Elements author = li.select("div > p.color-gray");
Elements description = li.select(" div > p.detail");
String bookDetailUrl = urls.get(0).attr("href");
BookDetailInfo detailInfo = getDetailInfo(bookDetailUrl);
BookEntity bookEntity = BookEntity.builder()
.detailPageUrl(bookDetailUrl)
.bookImgUrl(imgUrl.attr("src"))
.bookName(bookName.html())
.starsCount(starsCount.html())
.author(author.text())
.bookDetailInfo(detailInfo)
.description(description.html())
.build();
// System.out.println(bookEntity);
bookEntities.add(bookEntity);
}
return bookEntities;
}
/**
*
* @param detailUrl
* @return
* @throws IOException
*/
public static BookDetailInfo getDetailInfo(String detailUrl)throws IOException{
Document doc = Jsoup.connect(detailUrl).get();
Elements content = doc.select("body");
Elements price = content.select("#buyinfo-printed > ul.bs.current-version-list > li:nth-child(2) > div.cell.price-btn-wrapper > div.cell.impression_track_mod_buyinfo > div.cell.price-wrapper > a > span");
Elements author = content.select("#info > span:nth-child(1) > a");
BookDetailInfo bookDetailInfo = BookDetailInfo.builder()
.author(author.html())
.authorUrl(author.attr("href"))
.price(price.html())
.build();
return bookDetailInfo;
}
}
這里的重點是要獲取網(wǎng)頁對應(yīng)元素的選擇器。
例如:獲取 li.select("div > p.color-gray") 中 div > p.color-gray 是怎么知道的。
使用 chrome 的小伙伴應(yīng)該都猜到了。打開 chrome 瀏覽器 Debug 模式,Ctrl + Shift +C 選擇一個元素,然后在 html 右鍵選擇 Copy ->Copy selector,這樣就可以獲取當(dāng)前元素的選擇器。如下圖:
為了數(shù)據(jù)更好查看,我將通過 jsoup 抓取的數(shù)據(jù)存儲的 Excel 文件,這里我使用的 easyexcel 快速生成 Excel 文件。
Excel 表頭信息
@Data
@Builder
public class ColumnData {
@ExcelProperty("書名稱")
private String bookName;
@ExcelProperty("評分")
private String starsCount;
@ExcelProperty("作者")
private String author;
@ExcelProperty("封面圖片")
private String bookImgUrl;
@ExcelProperty("簡介")
private String description;
@ExcelProperty("單價")
private String price;
}
生成 Excel 文件
public class EasyExcelUtils {
public static void simpleWrite(List<BookEntity> bookEntityList) {
String fileName = "D:\\devEnv\\JsoupTest\\bookList" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName, ColumnData.class).sheet("書本詳情").doWrite(data(bookEntityList));
System.out.println("excel文件生成完畢...");
}
private static List<ColumnData> data(List<BookEntity> bookEntityList) {
List<ColumnData> list = new ArrayList<>();
bookEntityList.forEach(b -> {
ColumnData data = ColumnData.builder()
.bookName(b.getBookName())
.starsCount(b.getStarsCount())
.author(b.getBookDetailInfo().getAuthor())
.bookImgUrl(b.getBookImgUrl())
.description(b.getDescription())
.price(b.getBookDetailInfo().getPrice())
.build();
list.add(data);
});
return list;
}
}
最終的效果如下圖:
以上就是從想法到實踐,我們就在實戰(zhàn)中使用了 jsoup 的基本操作。
完整代碼地址:https://github.com/hellowHuaairen/JsoupTest
Java HTML Parser 庫:jsoup,把它當(dāng)成簡單的爬蟲用起來還是很方便的吧?
為什么會講爬蟲?大數(shù)據(jù),人工智能時代玩的就是數(shù)據(jù),數(shù)據(jù)很重要。作為懂點技術(shù)的我們,也需要掌握一種獲取網(wǎng)絡(luò)數(shù)據(jù)的技能。當(dāng)然也有一些工具 Fiddler、webscraper 等也可以抓取你想要的數(shù)據(jù)。
教程至此,你應(yīng)該也能對 jsoup 有一些感覺了吧。編程是不是也特別有意思呢?參考我上面的實戰(zhàn)案例,有好多網(wǎng)站可以實踐一下啦~歡迎在評論區(qū)曬你的實戰(zhàn)。
《Java Web開發(fā)實戰(zhàn)1200例》包括第一卷、第二卷共計1200個例子,每卷各計600個例子。
本書以開發(fā)人員在項目開發(fā)中經(jīng)常遇到的問題和必須掌握的技術(shù)為中心,介紹了應(yīng)用Java Web進行程序開發(fā)的各個方面的知識和技巧,主要包括Java Web編程基礎(chǔ)、文件操作管理、圖形圖像與多謀體技術(shù)、窗體應(yīng)用技術(shù)、JSP操作Word與Excel等。全書分6篇23章,共計600個實例和600個經(jīng)驗技巧。每個實例都是經(jīng)過精心篩選的,具有很強的實用性,其中一些實例是開發(fā)人員難于尋覓的解決方案。
本書適合Java Web的初學(xué)者,如高校學(xué)生、求職人員作為練習(xí)、速查、學(xué)習(xí)使用,也適合Java Web程序員參考、查閱。
支持書簽?zāi)夸洠奖悴殚?/p>
第一卷:
第二卷:
...
...
...
...
...
...
...
點贊+轉(zhuǎn)發(fā)+評論,關(guān)注后私信回復(fù)關(guān)鍵詞:實戰(zhàn),即可獲取鏈接!
領(lǐng)取PDF的小伙伴別忘記在評論區(qū)回應(yīng)下,感謝大家支持!
你是否也曾羨慕過有些 phython 大神有著如下的神操作:
他們就輕輕的執(zhí)行一串代碼,就能循環(huán)的抓取很多自己想要的數(shù)據(jù)。
其實不用太羨慕他們,因為不光 phython 能實現(xiàn),我們用 Java 同樣也能夠輕松實現(xiàn)。
閑話不多說,下面我們直接開始實戰(zhàn):
(1.1)我們用 IDEA(Eclipse同理) 創(chuàng)建一個全新的maven工程,我這里取名工程名 zyqok,各位隨意。
(1.2)在 pom.xml 里面加上 <dependencies>
(1.3)創(chuàng)建 Test 類,好了工程就已經(jīng)搭好了。
(2.1)什么是 httpclient ?
Httpclient 是 Apache 的一個子項目,它是一個為 Java 可以實現(xiàn)網(wǎng)絡(luò)請求的客戶端工具包。
簡單的說,他是一個 Jar 包,有了他,我們通過 Java 程序就可以實現(xiàn)網(wǎng)絡(luò)請求。
(2.2) 復(fù)制下面的 httpclient 依賴,加入到 pom.xml 文件中。
<!-- httpclient 核心包 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
(2.3)創(chuàng)建一個 HttpTool 的類,這個類我們專門用來實現(xiàn)網(wǎng)絡(luò)請求相關(guān)方法。
(2.4) 為了避免其他網(wǎng)站侵權(quán)問題,下面以我個人網(wǎng)站一個頁面為例(http://www.zyqok.cn/material/index),我們來抓取這個頁面上的所有圖片。
(2.5) 可以看得出,這是一個 get 請求,并且返回的是一個 Html 頁面。所以我們在 HttpTool 類中加入一個如下方法體:
/**
* 實現(xiàn)Get請求
* @param url 請求地址
* @return 頁面內(nèi)容
*/
public static String doGet(String url) {
return null;
}
(2.6)復(fù)制代碼,添加 get 實現(xiàn)方法:
// 構(gòu)建get請求
HttpGet get = new HttpGet(url);
// 創(chuàng)建客戶端
CloseableHttpClient client = HttpClients.createDefault();
try {
// 客戶端執(zhí)行請求,獲取響應(yīng)
HttpResponse response = client.execute(get);
// 獲取響應(yīng)的頁面內(nèi)容
InputStream in = response.getEntity().getContent();
StringBuilder sb = new StringBuilder();
byte[]b = new byte[102400];
int length;
while ((length = in.read(b)) != -1) {
sb.append(new String(b, 0, length, "utf-8"));
}
// 返回頁面內(nèi)容
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
(2.7)OK,網(wǎng)絡(luò)請求相關(guān)實現(xiàn)類我們已經(jīng)寫好了,我們接下來測試下,我們在 Test 類的 main 方法里加入如下代碼:
String html = HttpTool.doGet("http://www.zyqok.cn/material/index");
System.out.println(html);
(2.8)執(zhí)行程序,查看結(jié)果。可以看到我們確實已經(jīng)通過請求,獲取到網(wǎng)頁的返回內(nèi)容了。
在整個【2】的實現(xiàn)過程中,我們已經(jīng)拿到網(wǎng)頁返回的數(shù)據(jù),但我們要的是整個網(wǎng)頁中的圖片,并不是這種雜亂無章的網(wǎng)頁頁面數(shù)據(jù),那么我們該怎么辦呢?簡單,接下來我們需要用到另外一種技術(shù)了 ---- Jsoup。
(3.1)什么是 Jsoup 技術(shù)?
下面是度娘給出的一個官方解釋:Jsoup 是一款Java 的HTML解析器,可直接解析某個URL地址、HTML文本內(nèi)容。它提供了一套非常省力的API,可通過DOM,CSS以及類似于jQuery的操作方法來取出和操作數(shù)據(jù)(摘自百度)。
下面再用我個人語言簡單的總結(jié)下:Jsoup 技術(shù)就是用來處理各種 html 頁面 和 xml 數(shù)據(jù)。我們這里可以通過 Jsoup 來處理【2】中返回的 html 頁面。
(3.2)加入 Jsoup 依賴
我們在 pom.xml 加入如下依賴:
<!-- Jsoup 核心包 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
(3.3)當(dāng)然,使用 Jsoup 之前,我們需要對響應(yīng)的 HTML 頁面進行分析,分析主要作用是:如何定位篩選出我們需要的數(shù)據(jù)?
我們把【2】中獲取到的頁面響應(yīng)拷貝到 txt 文本中,然后可以發(fā)現(xiàn):每個圖片它都包含在一個 div 中,且該div 有一個名為 material-div 的 class。
(3.4)按照上面分析:首先我們要獲取到包含圖片的所有 div,于是我們修改main方法中代碼為如下:
String html = HttpTool.doGet("http://www.zyqok.cn/material/index");
// 將 html 頁面解析為 Document 對象
Document doc = Jsoup.parse(html);
// 獲取所有包含 class = material-div 的 div 元素
Elements elements = doc.select("div.material-div");
for(Element div: elements){
System.out.println(div.toString());
}
注意:doc.select() 括號中的參數(shù)為過濾條件,基本等同于 Jquery 的過濾條件,所以會Jquery的同學(xué),如何篩選條件基本就得心應(yīng)手的,當(dāng)然不會寫篩選條件的也不要怕,這里有一份 Jsoup 使用指南,閣下不妨收下(傳送門:Jsoup 官方使用指南)。
(3.5)我們執(zhí)行代碼,將輸出結(jié)果繼續(xù)拷貝到文本中。
可以看到,本次確實只有圖片相關(guān)的div元素了,但這并不是我們想要的最終結(jié)果,我們最終的結(jié)果是獲取到所有圖片。
所以我們還需要繼續(xù)分析:如何獲取所有圖片的鏈接和名字。
(3.6)由于每個圖片所在的div元素結(jié)構(gòu)都一樣,所以我們可以取隨機取一個div元素進行分析,于是我們可以取第一個div來進行分析,結(jié)構(gòu)如下:
<div align="center" style="padding: 10px;" class="material-div">
<div style="width: 80px; height: 80px; margin-bottom: 3px; display: flex; align-items: center; justify-content: center">
<img class="fangda image" src="https://zyqok.oss-cn-chengdu.aliyuncs.com/20200414220946131_大樹夕陽.jpg">
<input type="hidden" class="materialId" value="121">
</div>
<font style="font-size: 5px">大樹夕陽.jpg</font><br>
<font style="font-size: 5px">2020-04-14 22:09:46</font>
</div>
3.7)我們可以看到,整個結(jié)構(gòu)內(nèi),就一個 img 元素標(biāo)簽,于是我們可以取第1個img標(biāo)簽的 src 屬性為圖片鏈接;同理,我們?nèi)〉?個 font 元素的文本內(nèi)容為圖片名稱。
(3.8)于是我們可以修改循環(huán)中的代碼內(nèi)容如下:
// 獲取第1個 img 元素Element img = div.selectFirst("img");// 獲取第1個 font 元素Element font = div.selectFirst("font");// 獲取img元素src屬性,即為圖片鏈接String url = img.attr("src");// 獲取name元素文本,即為圖片名稱String name = font.text();System.out.println(name + ": " + url);
(3.9)我們執(zhí)行上面代碼,可以得出如下結(jié)果。
可以看到,這個頁面上的所有圖片地址和名稱已經(jīng)被我們成功抓下來了。
【4】獲取圖片到本地
在第【3】步中,我們獲取到的只是所有圖片的鏈接,并沒有將所有圖片下載到我們本地,那么接下來,我們要將這個圖片下載到我們本地才算完成。
(4.1)既然要下載到本地,我們首先在本地找個地方,用于存放這些圖片。
比如:我將這圖片全部下載到 D:\imgs(D 盤的 imgs 文件夾)中。
(4.2)我們在 HttpTool 類中增加保存圖片到本地的方法,代碼如下:
/**
* 保存圖片到本地
* @param src 圖片地址
* @param name 圖片名稱
*/
public static void saveImg(String src, String name) {
// 構(gòu)建get請求
HttpGet get = new HttpGet(src);
// 創(chuàng)建客戶端
CloseableHttpClient client = HttpClients.createDefault();
try {
// 客戶端執(zhí)行請求,獲取響應(yīng)
HttpResponse response = client.execute(get);
// 獲取響應(yīng)的頁面內(nèi)容
InputStream in = response.getEntity().getContent();
int length;
byte[] bytes = new byte[1024];
FileOutputStream fos = new FileOutputStream("D:\\imgs\\" + name);
while ((length = in.read(bytes)) != -1) {
fos.write(bytes, 0, length);
fos.flush();
}
in.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
(4.3)修改 Test 類 main 方法最終代碼如下:
public static void main(String args[]) throws Exception {
String html = HttpTool.doGet("http://www.zyqok.cn/material/index");
// 將 html 頁面解析為 Document 對象
Document doc = Jsoup.parse(html);
// 獲取所有包含 class = material-div 的 div 元素
Elements elements = doc.select("div.material-div");
for (int i = 0; i<elements.size(); i++) {
Element div = elements.get(i);
// 獲取第1個 img 元素
Element img = div.selectFirst("img");
// 獲取第1個 font 元素
Element font = div.selectFirst("font");
// 獲取img元素src屬性,即為圖片鏈接
String src = img.attr("src");
// 獲取name元素文本,即為圖片名稱
String name = font.text();
if (!name.contains(".")) {
name += ".jpg";
}
HttpTool.saveImg(src, i + name);
System.out.println("抓取第 " + i + " 張圖片成功! 圖片名稱 : " + name);
}
System.out.println("所有圖片抓取完成 !!");
}
(4.4)執(zhí)行代碼,打印如下圖,看到這個結(jié)果,是不是感覺有點文章開頭的展示味道了。
最后,我們只需要去本地文件夾下看看,所有圖片是否成功保存到了本地?如果有圖片,則我們就成功了。
(4.5)我們打開D盤imgs文件夾,可以看到網(wǎng)站上的圖片確實已經(jīng)全部保存到本地了。
通過我們 [批量抓取網(wǎng)絡(luò)圖片] 這一實戰(zhàn)案例,我們可以感受到:通過 Httopclient 和 Jsoup 這兩種技術(shù),不僅僅可以批量抓取數(shù)據(jù),其實還可以實現(xiàn)很多功能。
比如:網(wǎng)站登錄,分布式服務(wù)器之間的數(shù)據(jù)傳遞,三方平臺的API對接,有效數(shù)據(jù)的篩選和保存,數(shù)據(jù)的二次加工等等。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。