文適合有 Java 基礎知識的人群
本文作者:HelloGitHub-秦人
HelloGitHub 推出的《講解開源項目》系列,今天給大家帶來一款開源 Java 版一款網頁元素解析框架——jsoup,通過程序自動獲取網頁數據。
項目源碼地址:https://github.com/jhy/jsoup
jsoup 是一款 Java 的 HTML 解析器。可直接解析某個 URL 地址的 HTML 文本內容。它提供了一套很省力的 API,可通過 DOM、CSS 以及類似于 jQuery 選擇器的操作方法來取出和操作數據。
jsoup 主要功能:
將項目導入 idea 開發工具,會自動下載 maven 項目需要的依賴。源碼的項目結構如下:
快速學習源碼是每個程序員必備的技能,我總結了以下幾點:
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 屬性。維基百科 國內無法訪問,所以上面這段代碼運行會報錯。
改造后可運行的代碼如下:
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,框架發送 HTTP 請求,然后獲取響應頁面內容,然后通過各種選擇器獲取頁面數據。整個工作流程如下圖:
以上面為例:
Document doc = Jsoup.connect("https://www.baidu.com/").get();
這行代碼就是發送 HTTP 請求,并獲取頁面響應數據。
Elements newsHeadlines = doc.select("a[href]");
定義選擇器,獲取匹配選擇器的數據。
for (Element headline : newsHeadlines) {
System.out.println("href: " +headline.absUrl("href") );
}
這里對數據只做了一個簡單的數據打印,當然這些數據可寫入文件或數據的。
獲取豆瓣讀書 -> 新書速遞中每本新書的基本信息。包括:書名、書圖片鏈接、作者、內容簡介(詳情頁面)、作者簡介(詳情頁面)、當當網書的價格(詳情頁面),最后將獲取的數據保存到 Excel 文件。
目標鏈接: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;
}
}
這里的重點是要獲取網頁對應元素的選擇器。
例如:獲取 li.select("div > p.color-gray") 中 div > p.color-gray 是怎么知道的。
使用 chrome 的小伙伴應該都猜到了。打開 chrome 瀏覽器 Debug 模式,Ctrl + Shift +C 選擇一個元素,然后在 html 右鍵選擇 Copy ->Copy selector,這樣就可以獲取當前元素的選擇器。如下圖:
為了數據更好查看,我將通過 jsoup 抓取的數據存儲的 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;
}
}
最終的效果如下圖:
以上就是從想法到實踐,我們就在實戰中使用了 jsoup 的基本操作。
完整代碼地址:https://github.com/hellowHuaairen/JsoupTest
Java HTML Parser 庫:jsoup,把它當成簡單的爬蟲用起來還是很方便的吧?
為什么會講爬蟲?大數據,人工智能時代玩的就是數據,數據很重要。作為懂點技術的我們,也需要掌握一種獲取網絡數據的技能。當然也有一些工具 Fiddler、webscraper 等也可以抓取你想要的數據。
教程至此,你應該也能對 jsoup 有一些感覺了吧。編程是不是也特別有意思呢?參考我上面的實戰案例,有好多網站可以實踐一下啦~歡迎在評論區曬你的實戰。
soup
抓取網頁后,需要對網頁解析,可以使用字符串處理工具解析頁面,也可以使用正則表達式
jsoup 的作用:是一款 Java 的HTML 解析器,可直接解析某個URL地址、HTML文本內容。它提供了一套非常省力的API,可通過DOM,CSS以及類似于JQuery的操作方法來取出和操作數據
jsoup的主要功能如下:
1.從一個URL,文件或字符串中解析HTML;
2.使用DOM或CSS選擇器來查找、取出數據;
3.可操作HTML元素、屬性、文本;
創建練習類
解析URL
第一個參數是訪問的url,第二個參數是訪問的超時時間
使用標簽選擇器,獲取title標簽中的內容
輸出結果
讀取文件
準備一個簡易的HTML文件
獲取這個
讀取文件,獲取字符串,代碼及結果
使用dom方式遍歷文檔
解析文件獲取document對象
依據id獲取,這個是id的內容,我們獲取這個內容
編寫代碼,顯示結果
依據標簽獲取,我們獲取這個標簽的內容
代碼及結果
依據class獲取,獲取內容
代碼和結果
依據屬性,屬性內容
代碼和結果
接下來從元素中獲取數據
首先從元素中獲取ID
從元素中獲取className
文本
代碼及結果
如果內容是兩個class
那么代碼及結果
從元素中獲取屬性
代碼及結果
獲取元素的所有屬性
代碼及結果
從元素中獲取文本內容,這個之前有,代碼和結果
<!--SpringMVC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringData Jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--MySQL連接包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!--Jsoup-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.2</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
復制代碼
# MySQL配置
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
# JPA配置
spring.jpa.database=MySQL
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
復制代碼
@Entity
@Table(name = "item")
@Data
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//標準產品單位
private Long spu;
//庫存量單位
private Long sku;
//商品標題
private String title;
//商品價格
private Double price;
//商品圖片
private String pic;
//商品詳情地址
private String url;
//店鋪;
private String shop;
//創建時間
private Date created;
//更新時間
private Date updated;
}
復制代碼
public interface ItemDao extends JpaRepository<Item,Long> {
}
復制代碼
public interface ItemService {
/**
* 保存商品
*
* @param item
*/
void save(Item item);
/**
* 刪除所有商品
*/
void deleteAll();
}
@Service
public class ItemServiceImpl implements ItemService {
@Autowired
private ItemDao itemDao;
@Override
@Transactional
public void save(Item item) {
this.itemDao.save(item);
}
@Override
public void deleteAll() {
this.itemDao.deleteAll();
}
}
復制代碼
@Component
public class HttpUtils {
private static final String FILEPATH = "D:\\demo\\";
private PoolingHttpClientConnectionManager cm;
public HttpUtils() {
this.cm = new PoolingHttpClientConnectionManager();
//設置最大連接數
this.cm.setMaxTotal(100);
//設置每個主機的最大連接數
this.cm.setDefaultMaxPerRoute(10);
}
/**
* 根據請求地址下載頁面數據
*
* @param url
* @return 頁面數據
*/
public String doGetHtml(String url) {
//獲取HttpClient對象
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(this.cm).build();
//創建httpGet請求對象,設置url地址
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36");
//設置請求信息
httpGet.setConfig(this.getConfig());
CloseableHttpResponse response = null;
try {
//使用HttpClient發起請求,獲取響應
response = httpClient.execute(httpGet);
//解析響應,返回結果
if (response.getStatusLine().getStatusCode() == 200) {
//判斷響應體Entity是否不為空,如果不為空就可以使用EntityUtils
if (response.getEntity() != null) {
String content = EntityUtils.toString(response.getEntity(), "utf8");
return content;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//關閉response
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//返回空串
return "";
}
/**
* 下載圖片
*
* @param url
* @return 圖片名稱
*/
public String doGetImage(String url) {
//獲取HttpClient對象
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(this.cm).build();
//創建httpGet請求對象,設置url地址
HttpGet httpGet = new HttpGet(url);
//設置請求信息
httpGet.setConfig(this.getConfig());
CloseableHttpResponse response = null;
try {
//使用HttpClient發起請求,獲取響應
response = httpClient.execute(httpGet);
//解析響應,返回結果
if (response.getStatusLine().getStatusCode() == 200) {
//判斷響應體Entity是否不為空
if (response.getEntity() != null) {
//獲取圖片的后綴
String extName = url.substring(url.lastIndexOf("."));
//創建圖片名,重命名圖片
String picName = UUID.randomUUID() + extName;
//聲明OutPutStream
OutputStream outputStream = new FileOutputStream(new File(FILEPATH + picName));
response.getEntity().writeTo(outputStream);
//返回圖片名稱
return picName;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//關閉response
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//如果下載失敗,返回空串
return "";
}
/**
* 設置請求信息
*
* @return
*/
private RequestConfig getConfig() {
RequestConfig config = RequestConfig.custom()
//創建連接的最長時間
.setConnectTimeout(1000)
// 獲取連接的最長時間
.setConnectionRequestTimeout(500)
//數據傳輸的最長時間
.setSocketTimeout(10000)
.build();
return config;
}
}
復制代碼
SPU
SPU是商品信息聚合的最小單位,是一組可復用、易檢索的標準化信息的集合,該集合描述了一個產品的特性。
屬性值、特性相同的商品就可以稱為一個SPU。
如:某型號某配置某顏色的筆記本電腦就對應一個SPU,它有多種配置,或者多種顏色
SKU
SKU即庫存進出計量的單位, 可以是以件、盒、托盤等為單位。SKU是物理上不可分割的最小存貨單元。在使用時要根據不同業態,不同管理模式來處理。
如:某型號的筆記本電腦有多種配置,8G+512G筆記本電腦就是一個SKU。
爬取筆記本電腦搜索頁面。進行分頁操作,得到分頁請求地址:https://search.jd.com/search?keyword=%E7%94%B5%E8%84%91&wq=%E7%94%B5%E8%84%91&pvid=56a110735c6c491c91416c194aed4c5b&cid3=672&cid2=671&s=56&click=0&page=
所有商品由一個class=J_goodsList的div包裹。div中則是由ul標簽包裹的li標簽,每一個li標簽對應一個商品信息。
li標簽包含的需要的商品信息
@Component
public class ItemTask {
@Autowired
private HttpUtils httpUtils;
@Autowired
private ItemService itemService;
/**
* 使用定時任務抓取最新數據
*
* @throws Exception
*/
@Scheduled(fixedDelay = 50 * 1000)
public void itemTask() throws Exception {
// 每次執行前請客數據
itemService.deleteAll();
//聲明需要解析的初始地址
String url = "https://search.jd.com/search?keyword=%E7%94%B5%E8%84%91&wq=%E7%94%B5%E8%84%91&pvid=56a110735c6c491c91416c194aed4c5b&cid3=672&cid2=671&s=56&click=0&page=";
// 按照頁面對搜索結果進行遍歷解析,注意頁面是奇數
for (int i = 1; i < 10; i = i + 2) {
String html = httpUtils.doGetHtml(url + i);
// 解析頁面,獲取商品數據并存儲
this.parse(html);
}
System.out.println("商品數據抓取完成!");
}
/**
* 解析頁面,獲取商品數據并存儲
*
* @param html
* @throws Exception
*/
private void parse(String html) {
// 解析html獲取Document
Document doc = Jsoup.parse(html);
// 獲取spu信息
Elements spuEles = doc.select("div#J_goodsList > ul > li");
// 循環列表中的SPU信息
for (int i = 0; i < spuEles.size(); i++) {
Element element = spuEles.get(i);
//獲取spu
String strSpu = element.attr("data-spu");
if (strSpu == null || strSpu.equals("")) {
continue;
}
long spu = Long.parseLong(strSpu);
//獲取sku
long sku = Long.parseLong(element.attr("data-sku"));
Item item = new Item();
//設置商品的spu
item.setSpu(spu);
//設置商品的sku
item.setSku(sku);
//獲取商品的詳情的url
String itemUrl = "https://item.jd.com/" + sku + ".html";
item.setUrl(itemUrl);
// 獲取商品的圖片
String picUrl = "https:" + element.select("div.p-img").select("a").select("img").attr("data-lazy-img");
String picName = this.httpUtils.doGetImage(picUrl);
item.setPic(picName);
//獲取商品的價格
String strPrice = element.select("div.p-price").select("i").text();
item.setPrice(Double.parseDouble(strPrice));
//獲取商品的標題
String title = element.select("div.p-name").select("a").attr("title");
item.setTitle(title);
// 店鋪名稱
String shopName = element.select("div.p-shop a").text();
item.setShop(shopName);
item.setCreated(new Date());
item.setUpdated(item.getCreated());
//保存商品數據到數據庫中
this.itemService.save(item);
}
}
}
復制代碼
@SpringBootApplication
// 開啟定時任務
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
復制代碼
啟動項目,執行測試。查看數據庫與本地下載照片。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。