Java爬蟲使用Selenium+Autoit自動化爬取複雜頁面

前言:

最近玩爬蟲的時候,遇到一個國外的圖片網站,具體哪個就不說了,這個站很有意思,即使拿到了圖片的鏈接,用httpclient下載都不行,不是User-Agent的原因,不知道圖片服務器的後端有什麼校驗,沒辦法了,只能用Selenium上了,js逆向成本太高了(其實是我不擅長0.0)

這個站用的:

下面進入正題:

 既然拿到圖片url也不能用httpclient下載了,那我直接下載整個網頁怎麼樣?下載整個網頁,圖片不就也保存下來了嗎?

想到這個辦法後我立馬動手了起來,保存網頁可以鼠標右鍵另存爲,也可以鍵盤Ctrl+S

當然是鍵盤Ctrl+S 更簡單啦

一、怎麼在Selenium裏面調用鍵盤事件Ctrl+S呢?

我試過new 一個Action,調用sendKeys方法,不管用;然後考慮用awt包下的Robot類;

聽名字也知道是一個機器人類,裏面有很多模擬鍵盤和鼠標操作的API

二、Ctrl+S之後文件名怎麼修改?怎麼點擊保存?

這就要用到有個AutoIt的工具,這工具可以模擬windows環境下的鍵鼠操作,但是比Robot類更靈活,Robot類的鼠標移動只能通過像素點來確定移動位置,而AutoIt可以根據預先寫好的腳步,自動定位到Windows窗口的按鈕、輸入框等等

下載地址:https://www.autoitscript.com/site/autoit/downloads/

安裝完成之後先打開AutoIt Windows Info

打開之後:

打開一個網頁的另存爲的窗口,點擊拖動Finder Tool

點擊右邊control面板可以看到,窗口的一些信息,id,class這些等會都要用到;

同理,點擊拖動Finder Tool到保存按鈕那裏,也能看到對應按鈕的信息

然後打開第二個工具上面截圖中的 SciTE Script Editor來編輯腳本:

這個工具很強,有很多用法,想具體學習可以參考:https://www.jb51.net/shouce/autoit/?tdsourcetag=s_pctim_aiomsg

下面是我用的腳本:

;該腳本的語法是:  ;分號代表註釋
;ControlFocus ( "title", "窗口文本", controlID)   設置輸入焦點到指定窗口的某個控件上
;WinWait ( "title題" , "窗口文本" , 超時時間 )  暫停腳本的執行直至指定窗口存在(出現)爲止
;ControlSetText ( "title", "窗口文本", controlID, "新文本" )   修改指定控件的文本
;Sleep ( 延遲 )   使腳本暫停指定時間段
;ControlClick ( "title", "窗口文本", 控件ID , 按鈕 , 點擊次數 )   向指定控件發送鼠標點擊命令
;其中,title即AutoIt Window Info識別出的Title字段,controlID即AutoIt Window Info識別
;出的Class和Instance的拼接,如上圖拼接後的結果應爲:Button1



ControlFocus("另存爲","","")
;暫停腳本的執行直至指定窗口存在(出現)爲止
WinWait("[CLASS:#32770]","",10)
;第二步:填充文件名地址,其中$CmdLine[1]代表exe執行時的動態參數,
;例如 Runtime.getRuntime().exec("D:\\saveAs.exe "+文件名),這樣就可以動態改變文件名
ControlSetText("另存爲","","Edit1",$CmdLine[1])
;延時函數
;Sleep(2000)
;第三步:點擊保存按鈕,進行下載,title:另存爲,"text"寫成空,controlId:寫成Button2
;Button2是剛剛拖動Finder Tool獲取到的“保存”按鈕的id,可能每個人不一樣
ControlClick("另存爲","","Button2")

點擊保存之後,放到D盤,然後鼠標右鍵點擊compile script完成編譯之後,會在D盤生成一個文件名一樣的exe可執行文件,等會就要調用這個文件來執行腳本

三、下面就是java代碼了

@Autowired
    private WebDriverPool webDriverPool;

    public void getPic() throws Exception {
        // 這裏用隊列實現的一個WebDriverPool,也可以自己new一個,但是比較耗資源
        ChromeDriver chromeDriver = webDriverPool.get();
        ChromeDriver openDetailDriver = webDriverPool.get();
        for (int i = 1; i < 200; i++) {
            // 打開網址主頁
            chromeDriver.get("https://***.com/page/" + i);
            Thread.sleep(1000);
            List<WebElement> elements = chromeDriver.findElements(By.xpath("//article[@class='item-list']"));
            for (WebElement element : elements) {
                WebElement alabel = element.findElement(By.xpath(".//div[@class='post-thumbnail']/a"));
                WebElement titleElement = element.findElement(By.xpath(".//h2[@class='post-box-title']/a"));
                String pictureDetailPage = alabel.getAttribute("href");
                String picTitle = titleElement.getText();
                log.info("圖片首頁地址:{}", pictureDetailPage);
                openHomePage(openDetailDriver, false, pictureDetailPage, picTitle);
            }
        }
        webDriverPool.closeAll();
    }

    /**
     * 打開每個圖片的主頁
     *
     * @param url 主頁地址
     * @throws Exception
     */
    public void openHomePage(ChromeDriver chromeDriver, boolean recursive, String url, String picTitle) throws Exception {
        chromeDriver.get(url);
        Thread.sleep(5000);
        Robot robot = new Robot();
        while (true) {
            try {
                // 等待頁面加載完成
                WebDriverWait webDriverWait = new WebDriverWait(chromeDriver, 10);
                webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.id("fukie2")));
                log.info("加載完畢");
                break;
            } catch (Exception e) {
                log.info("打開頁面失敗,刷新");
                chromeDriver.navigate().refresh();
                Thread.sleep(10000);
            }
        }
        robot.keyPress(KeyEvent.VK_CONTROL);
        robot.keyPress(KeyEvent.VK_S);
        robot.keyRelease(KeyEvent.VK_S);
        robot.keyRelease(KeyEvent.VK_CONTROL);
        Thread.sleep(1000);
        // 調用autoIt動態修改文件名,避免文件名重複
        Runtime.getRuntime().exec("D:\\saveAs.exe " + picTitle + random.nextInt(10000));
        Thread.sleep(1000);
        Thread.sleep(10000);
        if (!recursive) {
            List<WebElement> pages = chromeDriver.findElements(By.xpath("//div[@id='fukie2']/div[2]/a"));
            for (int i = 1; i <= pages.size(); i++) {
                log.info("當前下載第{}頁", i + 1);
                openHomePage(chromeDriver, true, url + "" + (i + 1), picTitle);
            }
            log.info("下載完當前頁,關閉");
        }
    }

在SpringBoot項目中使用awt包下的類,需要在啓動類加一行代碼:

        System.setProperty("java.awt.headless", "false");

運行代碼,你會看到每一頁都被下載下來了,下載下來的網頁裏面包含了html、css、js、jpg,最後只需要過濾提取圖片就行了。這樣開發效率高,但是也有問題,如果需要多線程同時打開多個chrome下載的話,Robot類的鍵盤操作是需要聚焦的,也就是說如果採用多線程爬取,就需要在多個chrome中切換窗口才能高性能的下載,如果有高人有更好的解決辦法的話,請不吝賜教

以上是全文,如有不正確的地方,歡迎交流

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