前言
上一篇文章我們簡單的說了如何通過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)批評,非常感謝.