在之前的系列文章中介紹了如何使用httpclient抓取頁面html以及如何用jsoup分析html原始檔內容得到我們想要的資料,但是有時候通過這兩種方式不能正常抓取到我們想要的資料,比如看如下例子。
1.需求場景:
想要抓取股票的最新價格,頁面F12資訊如下:
按照前面的方式,爬取的程式碼如下:
/**
* @description: 爬取股票的最新股價
* @author: JAVA開發老菜鳥
* @date: 2021-10-16 21:47
*/
public class StockPriceSpider {
Logger logger = LoggerFactory.getLogger(this.getClass());
public static void main(String[] args) {
StockPriceSpider stockPriceSpider = new StockPriceSpider();
String html = stockPriceSpider.httpClientProcess();
stockPriceSpider.jsoupProcess(html);
}
private String httpClientProcess() {
String html = "";
String uri = "http://quote.eastmoney.com/sh600036.html";
//1.生成httpclient,相當於該開啟一個瀏覽器
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
//2.建立get請求,相當於在瀏覽器位址列輸入 網址
HttpGet request = new HttpGet(uri);
try {
request.setHeader("user-agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36");
request.setHeader("accept", "application/json, text/javascript, */*; q=0.01");
// HttpHost proxy = new HttpHost("3.211.17.212", 80);
// RequestConfig config = RequestConfig.custom().setProxy(proxy).build();
// request.setConfig(config);
//3.執行get請求,相當於在輸入位址列後敲Enter鍵
response = httpClient.execute(request);
//4.判斷響應狀態為200,進行處理
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
//5.獲取響應內容
HttpEntity httpEntity = response.getEntity();
html = EntityUtils.toString(httpEntity, "utf-8");
logger.info("訪問{} 成功,返回頁面資料{}", uri, html);
} else {
//如果返回狀態不是200,比如404(頁面不存在)等,根據情況做處理,這裡略
logger.info("訪問{},返回狀態不是200", uri);
logger.info(EntityUtils.toString(response.getEntity(), "utf-8"));
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//6.關閉
HttpClientUtils.closeQuietly(response);
HttpClientUtils.closeQuietly(httpClient);
}
return html;
}
private void jsoupProcess(String html) {
Document document = Jsoup.parse(html);
Element price = document.getElementById("price9");
logger.info("股價為:>>> {}", price.text());
}
}
執行結果:
納尼,股價為"-" ?不可能。
之所以爬不到正確的結果,是因為這個值在網站上是通過非同步載入渲染的,因此不能正常獲取。
2.java爬取非同步載入的資料的方法
那如何爬取非同步載入的資料呢?通常有兩種做法:
2.1內建瀏覽器核心
內建瀏覽器就是在抓取的程式中啟動一個瀏覽器核心,使我們獲取到 js 渲染後的頁面就和靜態頁面一樣。常用的核心有
- Selenium
- PhantomJs
- HtmlUnit
這裡我選了Selenium,它是一個模擬瀏覽器,是進行自動化測試的工具,它提供一組 API 可以與真實的瀏覽器核心互動。當然,爬蟲也可以用它。
具體做法如下:
- 引入pom依賴
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
-
配置對應瀏覽器的驅動
要使用selenium,需要下載瀏覽器的驅動,根據不同的瀏覽器要下載的驅動程式也不一樣,下載地址為:https://npm.taobao.org/mirrors/chromedriver/
我用的是谷歌瀏覽器,因此下載了對應版本的windows和linux驅動。
下載後需要配置進java環境變數裡面,指定驅動的目錄:
System.getProperties().setProperty("webdriver.chrome.driver", "F:/download/chromedriver_win32_1/chromedriver.exe");
-
程式碼實現:
Logger logger = LoggerFactory.getLogger(this.getClass()); public static void main(String[] args) { StockPriceSpider stockPriceSpider = new StockPriceSpider(); stockPriceSpider.seleniumProcess(); } private void seleniumProcess() { String uri = "http://quote.eastmoney.com/sh600036.html"; // 設定 chromedirver 的存放位置 System.getProperties().setProperty("webdriver.chrome.driver", "F:/download/chromedriver_win32_1/chromedriver.exe"); // 設定瀏覽器引數 ChromeOptions chromeOptions = new ChromeOptions(); chromeOptions.addArguments("--no-sandbox");//禁用沙箱 chromeOptions.addArguments("--disable-dev-shm-usage");//禁用開發者shm chromeOptions.addArguments("--headless"); //無頭瀏覽器,這樣不會開啟瀏覽器視窗 WebDriver webDriver = new ChromeDriver(chromeOptions); webDriver.get(uri); WebElement webElements = webDriver.findElement(By.id("price9")); String stockPrice = webElements.getText(); logger.info("最新股價為 >>> {}", stockPrice); webDriver.close(); }
執行結果:
爬取成功!
2.2反向解析法
反向解析法就是通過F12查詢到 Ajax 非同步獲取資料的連結,直接呼叫該連結得到json結果,然後直接解析json結果獲取想要的資料。
這個方法的關鍵就在於找到這個Ajax連結。這種方式我沒有去研究,感興趣的可以百度下。這裡略。
3.結束語
以上即為如何通過selenium-java爬取非同步載入的資料的方法。通過本方法,我寫了一個小工具:
持倉市值通知系統,他會每日根據自己的持倉配置,自動計算賬戶總市值,並郵件通知到指定郵箱。
用到的技術如下:
相關程式碼已經上傳到我的碼雲,感興趣可以看下。