Java利用WebCollector抓取網(wǎng)站列表和圖片

前言

上一篇文章我們簡單的說了如何通過WebCollector抓取到內(nèi)容,但是這并不能滿足我們的工作需求,在工作過程中我們通常會抓取某個網(wǎng)頁的列表下的詳情頁數(shù)據(jù),這樣我們就不能單純的只從某個列表頁面抓取數(shù)據(jù)了,我們需要跳轉(zhuǎn)到詳情頁進行數(shù)據(jù)的二次抓取.好了,廢話不多說,我們開始上代碼說明如何操作.


抓取列表信息

假定我們就抓取騷棟主頁中的所有展示文章詳情內(nèi)容.如下圖所示.



第一步,我們不忙創(chuàng)建爬蟲,我們先分析我們所需要爬取網(wǎng)站的結(jié)構(gòu).根據(jù)我們需要Web頁面的URL地址的特點來寫出正確的正則表達式.如下所示.

http://www.reibang.com/p/700e01a938ce

我寫的一個匹配文章URL正則表達式如下所示.

"http://www.reibang.com/p/.*"



第二步,正則表達式寫好之后,我們需要分析我們抓取的網(wǎng)站的結(jié)構(gòu)以及標簽.以谷歌瀏覽器為例.開啟開發(fā)者模式的控制臺.(F12鍵 Mac的 commond +alt +i,這里不多啰嗦了),假定我們需要抓取文章的標題和內(nèi)容.我們先選擇"選擇工具"(快捷鍵:commond +shift +c),然后點擊標題,這時候在控制臺就會出現(xiàn)所屬標簽信息了.如下所示.

我們發(fā)現(xiàn),標題的標簽h1的Class是title,如下所示.

標題已經(jīng)準備好了,接下來我們看內(nèi)容,還是如上步驟操作之后,我們發(fā)現(xiàn)內(nèi)容在很多標簽內(nèi),這時候我們只需要獲取父類標簽中所有內(nèi)容即可.

對了,首頁的列表頁面也是如上搞.如下所示,所有的超鏈接都是a標簽.



第三步,我們還是創(chuàng)建好一個爬蟲類JianshuCrawler繼承于AbstractCrawler,然后在初始化方法中配置好我們的正則表達式以及我們需要爬取的網(wǎng)站種子等等一些其他配置.如下所示.

    private final static String crawlPath = "/Users/luying/data/db/sports";
    private final static String seed = "http://www.reibang.com/u/e39da354ce50";
    private final static String regexRuleString = "http://www.reibang.com/p/.*";

    public JianshuCrawler() {
        super(crawlPath, false);
        CrawlDatum datum = new CrawlDatum(seed).meta("depth", "2");
        addSeed(datum);
        this.addRegex(regexRuleString);
        setThreads(2);
    }

在visit方法中我們需要做兩種處理,一是爬取文章列表,二是爬取文章詳情頁內(nèi)容.所以我們需要拿詳情頁URL的正則表達式來區(qū)分文章詳情頁和列表首頁,結(jié)構(gòu)如下所示.

    @Override
    public void visit(Page page, CrawlDatums next) {
        
        if (page.matchUrl(regexRuleString)) {
            
            //詳情頁會進入這個模塊
        } else {
            
            //列表首頁會進入這個模塊
        }
    }

通過第二步的分析,我們得知列表頁面需要把所有超鏈接符合正則表達式的a標簽添加到抓取序列中來.具體操作如下所示.

            Elements aBodys = page.select("a");
            for (int i = 0; i < aBodys.size(); i++) {
                Element aElement = aBodys.get(i);
                logger.debug("url=" + aElement.attr("abs:href"));
                String regEx = regexRuleString;
                if (aElement.attr("abs:href").matches(regEx)) {
                    CrawlDatum datum = new CrawlDatum(aElement.attr("abs:href")).meta("depth", "1").meta("refer",
                            page.url());
                    next.add(datum);
                } else {
                    System.out.println("URL不匹配!!");
                }

            }

對于詳情頁面的邏輯,我們需要抓取對應(yīng)元素的內(nèi)容,我們可以根據(jù)class的名稱(形式示例:div.xxx)也可以根據(jù)id名稱(形式示例:div[id = xxx]),這里沒有id,所以我們直接使用class的名稱了,代碼如下所示.


            String title = page.select("h1.title").text();
            String content = page.select("div.show-content").text();

我們創(chuàng)建一個main函數(shù),創(chuàng)建我們的爬蟲對象.然后,我們需要設(shè)置抓取深度,因為我們這里是只需要一次跳轉(zhuǎn)頁面,所以我們的抓取深度為2.最后運行這個類.如下所示.

    public static void main(String[] args) {
        
        JianshuCrawler crawler = new JianshuCrawler();
        crawler.start(2);
    }

這樣我們在控制臺會得到我們想要的數(shù)據(jù),當然了,數(shù)據(jù)的處理這里就不過多說明了.如下圖所示.

該爬蟲類全部代碼如下所示.

public class JianshuCrawler extends AbstractCrawler {

    private static Logger logger = LoggerFactory.getLogger(JianshuCrawler.class);

    private final static String crawlPath = "/Users/luying/data/db/sports";
    private final static String seed = "http://www.reibang.com/u/e39da354ce50";
    private final static String regexRuleString = "http://www.reibang.com/p/.*";

    public JianshuCrawler() {
        super(crawlPath, false);
        CrawlDatum datum = new CrawlDatum(seed).meta("depth", "2");
        addSeed(datum);
        this.addRegex(regexRuleString);
        setThreads(2);
    }

    @Override
    public void visit(Page page, CrawlDatums next) {

        if (page.matchUrl(regexRuleString)) {

            String title = page.select("h1.title").text();
            String content = page.select("div.show-content").text();
            
            System.out.println("標題 " + title);
            System.out.println("內(nèi)容 " + content);

        } else {
            
            Elements aBodys = page.select("a");
            for (int i = 0; i < aBodys.size(); i++) {
                Element aElement = aBodys.get(i);
                logger.debug("url=" + aElement.attr("abs:href"));
                String regEx = regexRuleString;
                if (aElement.attr("abs:href").matches(regEx)) {
                    CrawlDatum datum = new CrawlDatum(aElement.attr("abs:href")).meta("depth", "1").meta("refer",
                            page.url());
                    next.add(datum);
                } else {
                    System.out.println("URL不匹配!!");
                }
            }
        }
    }

    public static void main(String[] args) {
        JianshuCrawler crawler = new JianshuCrawler();
        crawler.start(2);
    }

}


抓取圖片信息

抓取圖片就比較簡單了假定我們還是抓取騷棟主頁中的所有展示的圖片.如下所示.

爬蟲類基本的步驟就不不過多解釋了,這里說一下visit方面中的處理,我們需要網(wǎng)頁中標簽的屬性來進行判斷,只有html屬性和圖片屬性的標簽才有可能是圖片標簽.所以我們首先獲取標簽名稱如下所示.

        String contentType = page.response().contentType();

然后判斷標簽所包含的名稱.進行不同的操作,如下所示.

        if (contentType == null) {
            return;
        } else if (contentType.contains("html")) {
            // 如果是網(wǎng)頁毒涧,則抽取其中包含圖片的URL榜贴,放入后續(xù)任務(wù)

        } else if (contentType.startsWith("image")) {
            // 如果是圖片劣砍,直接下載

        }

標簽為網(wǎng)頁屬性的處理我們需要拿出里面的圖片地址鏈接準備進行下一步的處理.代碼如下所示.

            Elements imgs = page.select("img[src]");
            for (Element img : imgs) {
                String imgSrc = img.attr("abs:src");
                next.add(imgSrc);
            }

如果是圖片標簽,我們直接拿去圖片的byte數(shù)據(jù),然后存儲即可.代碼如下所示.

            String extensionName = contentType.split("/")[1];
            String imageFileName = imageId.incrementAndGet() + "." + extensionName;
            File imageFile = new File(downloadDir, imageFileName);
            try {
                FileUtils.write(imageFile, page.content());
                System.out.println("保存圖片 " + page.url() + " 到 " + imageFile.getAbsolutePath());
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }

由于,可能標簽類型為網(wǎng)頁,也就是說我們需要跳轉(zhuǎn)到下一級頁面中.這時候,我們在main函數(shù)中創(chuàng)建對象并需要把爬取深度設(shè)置為2.

    public static void main(String[] args) throws Exception {
        JianshuImageCrawler crawler = new JianshuImageCrawler();
        crawler.start(2);
    }

圖片的存儲過程就不過多解釋了.,通過運行我們可以在對應(yīng)的存儲路徑下找到我們的圖片.如下所示.

整體代碼如下所示.

package com.infosports.yuqingmanagement.crawler.impl;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;

import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import cn.edu.hfut.dmic.webcollector.model.CrawlDatum;
import cn.edu.hfut.dmic.webcollector.model.CrawlDatums;
import cn.edu.hfut.dmic.webcollector.model.Page;
import cn.edu.hfut.dmic.webcollector.plugin.berkeley.BreadthCrawler;
import cn.edu.hfut.dmic.webcollector.util.FileUtils;
import cn.edu.hfut.dmic.webcollector.util.RegexRule;

public class JianshuImageCrawler extends BreadthCrawler {

    // 用于保存圖片的文件夾
    File downloadDir;

    // 原子性int,用于生成圖片文件名
    AtomicInteger imageId;

    private final static String crawlPath = "/Users/luying/data/db/jianshu";
    private final static String downPath = "/Users/luying/data/db/jianshuImage";

    private final static String seed = "http://www.reibang.com/u/e39da354ce50";

    RegexRule regexRule = new RegexRule();

    public JianshuImageCrawler() {
        super(crawlPath, false);

        downloadDir = new File(downPath);
        if (!downloadDir.exists()) {
            downloadDir.mkdirs();
        }
        computeImageId();
        CrawlDatum datum = new CrawlDatum(seed).meta("depth", "2");
        addSeed(datum);
        regexRule.addRule("http://.*");
    }

    @Override
    public void visit(Page page, CrawlDatums next) {

        String contentType = page.response().contentType();
        if (contentType == null) {
            return;
        } else if (contentType.contains("html")) {
            // 如果是網(wǎng)頁带膀,則抽取其中包含圖片的URL志珍,放入后續(xù)任務(wù)
            Elements imgs = page.select("img[src]");
            for (Element img : imgs) {
                String imgSrc = img.attr("abs:src");
                next.add(imgSrc);
            }

        } else if (contentType.startsWith("image")) {
            // 如果是圖片,直接下載
            String extensionName = contentType.split("/")[1];
            String imageFileName = imageId.incrementAndGet() + "." + extensionName;
            File imageFile = new File(downloadDir, imageFileName);
            try {
                FileUtils.write(imageFile, page.content());
                System.out.println("保存圖片 " + page.url() + " 到 " + imageFile.getAbsolutePath());
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }

    }

    public void computeImageId() {
        int maxId = -1;
        for (File imageFile : downloadDir.listFiles()) {
            String fileName = imageFile.getName();
            String idStr = fileName.split("\\.")[0];
            int id = Integer.valueOf(idStr);
            if (id > maxId) {
                maxId = id;
            }
        }
        imageId = new AtomicInteger(maxId);
    }

    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        JianshuImageCrawler crawler = new JianshuImageCrawler();
        crawler.start(2);
    }


總結(jié)

這篇博客寫到這就到了尾聲了,但是分享技術(shù)點過程卻是沒有結(jié)束.當然了,畢竟初學(xué)Java不久所以文章很多概念都可能模糊不清,所以如果有錯誤,歡迎指導(dǎo)批評,非常感謝.



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垛叨,一起剝皮案震驚了整個濱河市咙冗,隨后出現(xiàn)的幾起案子拙毫,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妄均,死亡現(xiàn)場離奇詭異,居然都是意外死亡撤摸,警方通過查閱死者的電腦和手機徽千,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來珍手,“玉大人办铡,你說我怎么就攤上這事×找” “怎么了寡具?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長稚补。 經(jīng)常有香客問我童叠,道長,這世上最難降的妖魔是什么课幕? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任厦坛,我火速辦了婚禮,結(jié)果婚禮上乍惊,老公的妹妹穿的比我還像新娘杜秸。我一直安慰自己,他們只是感情好润绎,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布撬碟。 她就那樣靜靜地躺著,像睡著了一般莉撇。 火紅的嫁衣襯著肌膚如雪呢蛤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天稼钩,我揣著相機與錄音顾稀,去河邊找鬼。 笑死坝撑,一個胖子當著我的面吹牛静秆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巡李,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼抚笔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了侨拦?” 一聲冷哼從身側(cè)響起殊橙,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后膨蛮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叠纹,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年敞葛,在試婚紗的時候發(fā)現(xiàn)自己被綠了誉察。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡惹谐,死狀恐怖持偏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氨肌,我是刑警寧澤鸿秆,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站怎囚,受9級特大地震影響卿叽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桩了,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一附帽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧井誉,春花似錦蕉扮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至在岂,卻和暖如春奔则,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔽午。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工易茬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人及老。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓抽莱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親骄恶。 傳聞我的和親對象是個殘疾皇子食铐,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容