項(xiàng)目地址: cockroach
求個(gè)位好心人,各位哥哥姐姐順手甩幾個(gè) star 給我吧?。?!
簡(jiǎn)介
cockroach[小強(qiáng)] 當(dāng)時(shí)不知道為啥選了這么個(gè)名字,又長(zhǎng)又難記,導(dǎo)致編碼的過(guò)程中因?yàn)閱卧~的拼寫(xiě)問(wèn)題耽誤了好長(zhǎng)時(shí)間。
這個(gè)項(xiàng)目算是我的又一個(gè)坑吧,算起來(lái)挖的坑多了去了,多一個(gè)不多少一個(gè)不少。
一個(gè)小巧、靈活、健壯的爬蟲(chóng)框架,暫且叫做框架吧。
簡(jiǎn)單到什么程度呢,幾句話就可以創(chuàng)建一個(gè)爬蟲(chóng)。
依賴
- java8 程序中用到了一些 java8 的新特性
- maven
下面就逐點(diǎn)介紹一下:
小巧
cd cockroach
git install
然后新建一個(gè) maven 項(xiàng)目,在 pom 文件中引入
<dependency>
<groupId>com.zhangyingwei.cockroach</groupId>
<artifactId>cockroach</artifactId>
<version>1.0-Alpha</version>
</dependency>
在項(xiàng)目中新建一個(gè)測(cè)試類 App.java 并新建 main 方法。
實(shí)例
public static void main(String[] args){
CockroachConfig config = new CockroachConfig()
.setAppName("我是一個(gè)小強(qiáng)")
.setThread(2); //爬蟲(chóng)線程數(shù)
CockroachContext context = new CockroachContext(config);
TaskQueue queue = TaskQueue.of();
context.start(queue);
// 以上就是一個(gè)完整的爬蟲(chóng),下邊的代碼相當(dāng)于一個(gè)生產(chǎn)者,往隊(duì)列里邊寫(xiě)任務(wù),一旦寫(xiě)入任務(wù),爬蟲(chóng)就會(huì)對(duì)任務(wù)進(jìn)行爬取
new Thread(() -> {
int i = 0;
while(true){
i++;
try {
Thread.sleep(1000);
String url = "http://www.xicidaili.com/wt/"+i;
System.out.println(url);
queue.push(new Task(url));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i > 1000) {
break;
}
}
}).start();
}
靈活
那靈活又體現(xiàn)在什么方面呢
- 可以自定義 http 客戶端(可選,默認(rèn)使用 okhttp3)
- 可以自定義結(jié)果的處理 (可選,默認(rèn)使用打印處理器)
自定義 http 客戶端
首先我們嘗試一下自定義客戶端
public class SelfHttpClient implements HttpClient {
public HttpClient setProxy(HttpProxy proxy){
//設(shè)置代理實(shí)現(xiàn)方法
}
public TaskResponse doGet(Task task) throws Exception{
// get 請(qǐng)求實(shí)現(xiàn)方法
}
public HttpClient proxy(){
// 應(yīng)用代理到 http 客戶端 方法
}
public TaskResponse doPost(Task task) throws Exception{
// post 請(qǐng)求實(shí)現(xiàn)方法
}
public HttpClient setCookie(String cookie){
// 設(shè)置 cookie 實(shí)現(xiàn)方法
}
public HttpClient setHttpHeader(Map<String, String> httpHeader){
// 設(shè)置 header 實(shí)現(xiàn)方法
}
}
應(yīng)用自定義 http 客戶端到爬蟲(chóng)
CockroachConfig config = new CockroachConfig()
.setAppName("我是一個(gè)小強(qiáng)")
.setThread(2) //爬蟲(chóng)線程數(shù)
.setHttpClient(SelfHttpClient.class)
自定義結(jié)果處理類
自定義結(jié)果處理類
public class SelfStore implements IStore {
@Override
public void store(TaskResponse response) {
System.out.println(response.getContent());
}
}
這里簡(jiǎn)單的將結(jié)果打印了出來(lái),在實(shí)際應(yīng)用中,我們可以保存到數(shù)據(jù)庫(kù)或者保存到文件中等等。值得一說(shuō)的是,如果結(jié)果是 html 網(wǎng)頁(yè)文本的話,我們還提供了 select("css選擇器") 來(lái)對(duì)結(jié)果文本進(jìn)行處理。
應(yīng)用自定義 store 客戶端到爬蟲(chóng)
CockroachConfig config = new CockroachConfig()
.setAppName("我是一個(gè)小強(qiáng)")
.setThread(2) //爬蟲(chóng)線程數(shù)
.setHttpClient(SelfHttpClient.class)
.setStore(SelfStore.class);
自定義錯(cuò)誤處理類
當(dāng) http 請(qǐng)求網(wǎng)頁(yè)出現(xiàn)錯(cuò)誤的時(shí)候會(huì)統(tǒng)一定位到錯(cuò)誤處理類,如果沒(méi)有自定義錯(cuò)誤處理類,系統(tǒng)會(huì)默認(rèn)使用 DefaultTaskErrorHandler ,此處理類會(huì)吧錯(cuò)誤信息打印出來(lái)。具體實(shí)現(xiàn)代碼如下。
public class DefaultTaskErrorHandler implements ITaskErrorHandler {
private Logger logger = Logger.getLogger(DefaultTaskErrorHandler.class);
@Override
public void error(Task task,String message) {
logger.info("task error: "+message);
}
}
如果需要自定義錯(cuò)誤處理類,可以仿照以上代碼,實(shí)現(xiàn) ITaskErrorHandler 接口,在 error 方法中實(shí)現(xiàn)自己的處理邏輯。
在自定義錯(cuò)誤處理類之后,我們需要把自定義類應(yīng)用到爬蟲(chóng)。
CockroachConfig config = new CockroachConfig()
.setAppName("我是一個(gè)小強(qiáng)")
.setThread(2) //爬蟲(chóng)線程數(shù)
.setHttpClient(SelfHttpClient.class)
.setStore(SelfStore.class)
.setTaskErrorHandler(SelfTaskErrorHandler.class);
健壯
說(shuō)到健壯,這里主要體現(xiàn)在以下幾個(gè)方面:
應(yīng)對(duì)IP封鎖
這里我們使用動(dòng)態(tài)代理來(lái)解決這個(gè)問(wèn)題。
動(dòng)態(tài)代理的使用
CockroachConfig config = new CockroachConfig()
.setAppName("我是一個(gè)小強(qiáng)")
.setThread(2) //爬蟲(chóng)線程數(shù)
.setHttpClient(SelfHttpClient.class)
.setProxys("100.100.100.100:8888,101.101.101.101:8888")
如上所示,我們可以設(shè)置若干個(gè)代理 ip,最終將所有代理 ip 生成一個(gè)代理池,在爬蟲(chóng)請(qǐng)求之前,我們會(huì)從代理池中隨機(jī)抽取一個(gè) ip 做代理。
應(yīng)對(duì) http 請(qǐng)求中的 user-agent 問(wèn)題
程序中實(shí)現(xiàn)了一個(gè) user-agent 池,每次請(qǐng)求都會(huì)隨機(jī)取出一個(gè) user-agent 使用,目前在程序中集成了 17 種 user-agent,后續(xù)會(huì)考慮把這塊開(kāi)放出來(lái)到配置中,自定義配置(有沒(méi)有意義呢?)。
程序中的異常處理問(wèn)題
目前在異常處理這塊,本身也不是非常擅長(zhǎng),已經(jīng)盡力把異??刂圃谝粋€(gè)可控的范圍內(nèi),程序中定義了很多自定義異常,這里沒(méi)有什么發(fā)言權(quán),就不細(xì)說(shuō)了,各位要是有意見(jiàn)建議,歡迎拍磚。
所謂深度爬取
程序中并沒(méi)有現(xiàn)成的深度爬取實(shí)現(xiàn),是因?yàn)橐话闱闆r下我并不覺(jué)得深度爬取有什么卵用,但是也不是沒(méi)有為深度爬取留出來(lái)一席之地。我們可以自己提取出頁(yè)面中的鏈接并加入到任務(wù)隊(duì)列中。以達(dá)到深度爬取的效果。
public class DemoStore implements IStore {
private String id = NameUtils.name(DemoStore.class);
public DemoStore() throws IOException {}
@Override
public void store(TaskResponse response) throws IOException {
List<String> urls = response.select("a").stream().map(element -> element.attr("href")).collect(Collectors.toList());
try {
response.getQueue().push(urls);
} catch (Exception e) {
e.printStackTrace();
}
}
}
關(guān)于分布式,我有話說(shuō)
現(xiàn)在網(wǎng)上是個(gè)爬蟲(chóng)就要搞一下分布式,這令我很不爽。
實(shí)際上我看過(guò)幾個(gè)所謂的分布式爬蟲(chóng)源碼,他們所謂的分布式,連偽分布式都算不上?。?!使用個(gè) redis 做消息中間件就分布式了嗎? 這就是所謂的分布式??這根本就不是分布式,本來(lái)我也準(zhǔn)備使用 redis 做消息中間件來(lái)裝個(gè)分布式的 B,但是寫(xiě)了一半忽然覺(jué)得有點(diǎn)惡心,遂刪除了代碼,還程序一個(gè)清靜,也還我自己一個(gè)安心。
分布式這個(gè)坑肯定是要挖的?。?!
所以,我的分布式將會(huì)包括:
- 分布式消息中間件(有可能會(huì)使用 redis 或者自己實(shí)現(xiàn)一個(gè); 為了還程序一個(gè)清靜,最有可能會(huì)自己實(shí)現(xiàn)一個(gè))
- 分布式任務(wù)調(diào)度
- 分布式分布式容錯(cuò)機(jī)制
- 分布式事務(wù)
- 狀態(tài)監(jiān)控
所以,這個(gè)坑是越來(lái)越大了么??我靠,有點(diǎn)怕怕
聯(lián)系方式
- 郵箱: zhangyw001@gmail.com
- 微信: fengche361