Java 爬蟲趕上數據異步加載,試試這兩種辦法!

這是 Java 爬蟲系列博文的第三篇,在上一篇 Java 爬蟲遇到須要登陸的網站,該怎麼辦? 中,咱們簡單的講解了爬蟲時遇到登陸問題的解決辦法,在這篇文章中咱們一塊兒來聊一聊爬蟲時遇到數據異步加載的問題,這也是爬蟲中常見的問題。html

如今不少都是先後端分離項目,這會使得數據異步加載問題更加突出,因此你在爬蟲時遇到這類問題沒必要驚訝,沒必要慌張。對於這類問題的解決辦法整體來講有如下兩種:java

一、內置一個瀏覽器內核git

內置瀏覽器就是在抓取的程序中,啓動一個瀏覽器內核,使咱們獲取到 js 渲染後的頁面,這樣咱們就跟採集靜態頁面同樣了。這種工具經常使用的有如下三種:github

  • Selenium
  • HtmlUnit
  • PhantomJs

這些工具都能幫助咱們解決數據異步加載的問題,可是他們都存在缺陷,那就是效率不高並且不穩定。web

二、反向解析法正則表達式

什麼是反向解析法呢?咱們 js 渲染頁面的數據是經過 Ajax 的方式從後端獲取的,咱們只須要找到對應的 Ajax 請求鏈接就 OK,這樣咱們就獲取到了咱們須要的數據,反向解析法的好處就是這種方式獲取的數據都是 json 格式的數據,解析起來也比較方便,另外一個好處就是相對頁面來講,接口的變化機率更小。一樣它有兩個不足之處,一個是在 Ajax 時你須要有耐心有技巧,由於你須要在一大推請求中找到你想要的,另外一個不足的地方就是對 JavaScript 渲染的頁面一籌莫展。chrome

上面就是異步數據加載的兩種解決辦法,爲了加深你們的理解和在項目中如何使用,我以採集網易要聞爲例,網易新聞地址:https://news.163.com/ 。利用上訴的兩種方式來獲取網易要聞的新聞列表。網易要聞以下:npm

內置瀏覽器 Selenium 方式

Selenium 是一個模擬瀏覽器,進行自動化測試的工具,它提供一組 API 能夠與真實的瀏覽器內核交互。在自動化測試上使用的比較多,爬蟲時解決異步加載也常用它,咱們要在項目中使用 Selenium ,須要作兩件事:json

  • 一、引入 Selenium 的依賴包,在 pom.xml 中添加
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>3.141.59</version>
</dependency>
  • 二、下載對應的 driver,例如我下載的 chromedriver,下載地址爲:https://npm.taobao.org/mirrors/chromedriver/,下載後,須要將 driver 的位置寫到 Java 的環境變量裏,例如我直接放在項目下,因此個人代碼爲:
System.getProperties().setProperty("webdriver.chrome.driver", "chromedriver.exe");

完成上面兩步以後,咱們就能夠來編寫使用 Selenium 採集網易要聞啦。具體代碼以下:後端

/**
 * selenium 解決數據異步加載問題
 * https://npm.taobao.org/mirrors/chromedriver/
 *
 * @param url
 */
public void selenium(String url) {
    // 設置 chromedirver 的存放位置
    System.getProperties().setProperty("webdriver.chrome.driver", "chromedriver.exe");
    // 設置無頭瀏覽器,這樣就不會彈出瀏覽器窗口
    ChromeOptions chromeOptions = new ChromeOptions();
    chromeOptions.addArguments("--headless");

    WebDriver webDriver = new ChromeDriver(chromeOptions);
    webDriver.get(url);
    // 獲取到要聞新聞列表
    List<WebElement> webElements = webDriver.findElements(By.xpath("//div[@class='news_title']/h3/a"));
    for (WebElement webElement : webElements) {
        // 提取新聞鏈接
        String article_url = webElement.getAttribute("href");
        // 提取新聞標題
        String title = webElement.getText();
        if (article_url.contains("https://news.163.com/")) {
            System.out.println("文章標題:" + title + " ,文章連接:" + article_url);
        }
    }
    webDriver.close();
}

運行該方法,獲得結果以下:

咱們使用 Selenium 已經正確的提取到了網易要聞的列表新聞。

反向解析法

反向解析法就是獲取到 Ajax 異步獲取數據的連接,直接獲取到新聞數據。若是沒有技巧的話,查找 Ajax 的過程將很是痛苦,由於一個頁面加載的連接太多了,看看網易要聞的 network:

有幾百條的請求,該如何查找到是哪條請求獲取的要聞數據呢?你不嫌麻煩的話,能夠一個一個的去點,確定可以查找到的,另外一種快捷的辦法是利用 network 的搜索功能,若是你不知道搜索按鈕,我在上圖已經圈出來啦,咱們在要聞中隨便複製一個新聞標題,而後檢索一下,就能夠獲取到結果,以下圖所示:

這樣咱們就快速的獲取到了要聞數據的請求連接,連接爲:https://temp.163.com/special/00804KVA/cm_yaowen.js?callback=data_callback,訪問該連接,查看該連接返回的數據,以下圖所示:

從數據咱們能夠看出,咱們須要的數據都在這裏啦,因此咱們只須要解析這段數據接能夠啦,要從這段數據中解析出新聞標題和新聞連接,有兩種方式,一種是正則表達式,另外一種是將該數據轉成 json 或者 list。這裏我選擇第二種方式,利用 fastjson 將返回的數據轉換成 JSONArray 。因此咱們是要引入 fastjson ,在 pom.xml 中引入 fastjson 依賴:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.59</version>
</dependency>

除了引入 fastjson 依賴外,咱們在轉換前還須要對數據進行簡單的處理,由於如今的數據並不符合 list 的格式,咱們須要去掉 data_callback( 和最後面的 )。具體反向解析獲取網易要聞的代碼以下:

/**
 * 使用反向解析法 解決數據異步加載的問題
 *
 * @param url
 */
public void httpclientMethod(String url) throws IOException {

    CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpGet httpGet = new HttpGet(url);
    CloseableHttpResponse response = httpclient.execute(httpGet);
    if (response.getStatusLine().getStatusCode() == 200) {
        HttpEntity entity = response.getEntity();
        String body = EntityUtils.toString(entity, "GBK");
        // 先替換掉最前面的 data_callback(
        body = body.replace("data_callback(", "");
        // 過濾掉最後面一個 )右括號
        body = body.substring(0, body.lastIndexOf(")"));
        // 將 body 轉換成 JSONArray
        JSONArray jsonArray = JSON.parseArray(body);
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject data = jsonArray.getJSONObject(i);
            System.out.println("文章標題:" + data.getString("title") + " ,文章連接:" + data.getString("docurl"));
        }
    } else {
        System.out.println("處理失敗!!!返回狀態碼:" + response.getStatusLine().getStatusCode());
    }

}

編寫 main 方法,執行上面的方法,須要注意的地方是:這時候傳入的連接爲https://temp.163.com/special/00804KVA/cm_yaowen.js?callback=data_callback 而不是 https://news.163.com/。獲得以下結果:

兩種方法都成功的獲取到了網易要聞異步加載的新聞列表,對於這兩種方法的選取,我我的的傾向是使用反向解析法,由於它的性能和穩定是都要比內置瀏覽器內核靠譜,可是對於一些使用 JavaScript 片斷渲染的頁面,內置瀏覽器又更加靠譜。因此根據具體狀況選擇吧。

但願這篇文章對你有所幫助,下一篇是關於 爬蟲IP 被封的問題。若是你對爬蟲感興趣,不妨關注一波,相互學習,相互進步

源代碼:源代碼

文章不足之處,望你們多多指點,共同窗習,共同進步

最後

打個小廣告,歡迎掃碼關注微信公衆號:「平頭哥的技術博文」,一塊兒進步吧。
平頭哥的技術博文