Java使用Jsoup和Selenium抓取西瓜小視頻

最近在家裏無聊每天刷頭條,看到一個很可愛的小姐姐,突然蹦出一個主意,就是想把它這些視頻全部搞下來存到本地。網上搜了一下,發現這些視頻其實是來自西瓜視頻,根據用戶名搜索就找到了。剛好會一點爬蟲,這下就好辦了。

跟Python的requests和bs4一樣,Java也有HttpClient和Jsoup分別用於發送請求和解析網頁。因爲Jsoup同時也具備發送請求的功能,並且本例也不涉及複雜的請求,所以這裏僅使用Jsoup即可。

研究了一下:首先點到"TA的小視頻"選項卡(小視頻首頁),下面會有視頻列表,每個標籤裏面包含了視頻詳情頁的地址,然後進到視頻詳情頁,這個頁面內容加載一會之後會把視頻的實際地址暴露出來,最後根據這個地址去把視頻下載就OK了。

很快,寫了一段代碼:首先請求小視頻首頁的地址得到頁面內容, 然後把所有視頻標籤解析得到各自的詳情頁地址,接着遍歷這些地址進行請求得到各自的詳情頁內容,最後解析拿到視頻實際地址就可以下載了。

跑了一下,很可惜,遇到了兩個難題

  1. 不管是視頻列表頁還是視頻詳情頁,直接請求拿到的頁面內容都是一堆js代碼:它們的頁面內容都是js加載生成的。
  2. 視頻列表頁剛進去只加載一部分,滾動條下拉纔會繼續加載更多內容。

遇到這種情況,讓你不得不使用Selenium了。其實,Jsoup+Selenium是一對黃金搭檔,唯一不好的就是Selenium也具備一點解析網頁的功能使得作用有點重疊(強迫症覺得)。

到此,完整思路如下

  • 首先使用Selenium打開小視頻首頁,然後一直下拉滾動條直到視頻列表全部加載出來,接着把網頁內容交給Jsoup解析,得到所有小視頻的詳情頁地址。
  • 使用Selenium分別打開每個小視頻詳情頁(多線程並行),等到加載完成視頻實際地址暴露出來之後,就可以使用Jsoup去下載視頻了。

成果展示

項目展示

1、pom文件,引入Jsoup和Selenium的依賴。

<?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>cn.zhh</groupId>
    <artifactId>crawl-xigua-video</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.12.1</version>
        </dependency>

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-chrome-driver</artifactId>
            <version>3.141.59</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2、Java代碼,配置小視頻首頁地址和下載目錄,把瀏覽器驅動放在指定位置。

package cn.zhh;

import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.openqa.selenium.Keys;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.interactions.Actions;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 主類
 * 修改“小視頻首頁”和“存放目錄”之後運行main函數即可
 *
 * @author Zhou Huanghua
 */
@SuppressWarnings("all")
public class Application {

    /**
     * 小視頻首頁,按需修改
     */
    private static final String MAIN_PAGE_URL = "https://www.ixigua.com/home/3276166340814919/hotsoon/";

    /**
     * 存放目錄,按需修改
     */
    private static final String FILE_SAVE_DIR = "C:/Users/SI-GZ-1766/Desktop/MP4/";

    /**
     * 線程池,按需修改並行數量。實際開發請自定義避免OOM
     */
    private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    /**
     * 谷歌瀏覽器參數
     */
    private static final ChromeOptions CHROME_OPTIONS = new ChromeOptions();

    static {
        // 驅動位置
        System.setProperty("webdriver.chrome.driver", "src/main/resources/static/chromedriver.exe");
        // 避免被瀏覽器檢測識別
        CHROME_OPTIONS.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));
    }

    /**
     * main函數
     *
     * @param args 運行參數
     * @throws InterruptedException 睡眠中斷異常
     */
    public static void main(String[] args) throws InterruptedException {
        // 獲取小視頻列表的div元素,批量處理
        Document mainDoc = Jsoup.parse(getMainPageSource());
        Elements divItems = mainDoc.select("div[class=\"BU-CardB UserDetail__main__list-item\"]");
        // 這裏使用CountDownLatch關閉線程池,只是避免執行完一直沒退出
        CountDownLatch countDownLatch = new CountDownLatch(divItems.size());
        divItems.forEach(item ->
                EXECUTOR.execute(() -> {
                    try {
                        Application.handleItem(item);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    countDownLatch.countDown();
                })
        );
        countDownLatch.await();
        EXECUTOR.shutdown();
        System.exit(0);
    }

    /**
     * 獲取首頁內容
     *
     * @return 首頁內容
     * @throws InterruptedException 睡眠中斷異常
     */
    private static String getMainPageSource() throws InterruptedException {
        ChromeDriver driver = new ChromeDriver(CHROME_OPTIONS);
        try {
            driver.get(MAIN_PAGE_URL);
            long waitTime = Double.valueOf(Math.max(3, Math.random() * 5) * 1000).longValue();
            TimeUnit.MILLISECONDS.sleep(waitTime);
            long timeout = 30_000;
            // 循環下拉,直到全部加載完成或者超時
            do {
                new Actions(driver).sendKeys(Keys.END).perform();
                TimeUnit.MILLISECONDS.sleep(waitTime);
                timeout -= waitTime;
            } while (!driver.getPageSource().contains("已經到底部,沒有新的內容啦")
                    && timeout > 0);
            return driver.getPageSource();
        } finally {
            driver.close();
        }
    }

    /**
     * 處理每個小視頻
     *
     * @param div 小視頻div標籤元素
     * @throws Exception 各種異常
     */
    private static void handleItem(Element div) throws Exception {
        String href = div.getElementsByTag("a").first().attr("href");
        String src = getVideoUrl("https://www.ixigua.com" + href);
        // 有些blob開頭的(可能還有其它)暫不處理
        if (src.startsWith("//")) {
            Connection.Response response = Jsoup.connect("https:" + src)
                    // 解決org.jsoup.UnsupportedMimeTypeException: Unhandled content type. Must be text/*, application/xml, or application/xhtml+xml. Mimetype=video/mp4, URL=
                    .ignoreContentType(true)
                    // The default maximum is 1MB.
                    .maxBodySize(100 * 1024 * 1024)
                    .execute();
            Files.write(Paths.get(FILE_SAVE_DIR, href.substring(1) + ".mp4"), response.bodyAsBytes());
        } else {
            System.out.println("無法解析的src:[" + src + "]");
        }
    }

    /**
     * 獲取小視頻實際鏈接
     *
     * @param itemUrl 小視頻詳情頁
     * @return 小視頻實際鏈接
     * @throws InterruptedException 睡眠中斷異常
     */
    private static String getVideoUrl(String itemUrl) throws InterruptedException {
        ChromeDriver driver = new ChromeDriver(CHROME_OPTIONS);
        try {
            driver.get(itemUrl);
            long waitTime = Double.valueOf(Math.max(5, Math.random() * 10) * 1000).longValue();
            long timeout = 50_000;
            Element v;
            /**
             * 循環等待,直到鏈接出來
             * ※這裏可以考慮瀏覽器驅動自帶的顯式等待()和隱士等待
             */
            do {
                TimeUnit.MILLISECONDS.sleep(waitTime);
                timeout -= waitTime;
            } while ((Objects.isNull(v = Jsoup.parse(driver.getPageSource()).getElementById("vs"))
                        || Objects.isNull(v = v.getElementsByTag("video").first()))
                    && timeout > 0);

            return v.attr("src");
        } finally {
            driver.close();
        }
    }
}

完整項目地址:https://github.com/zhouhuanghua/crawl-xigua-video

發佈了109 篇原創文章 · 獲贊 104 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章