爬蟲框架WebMagic
架構解析
WebMagic
的設計目標是儘量的模塊化,並體現爬蟲的功能特點。這部分提供非常簡單、靈活的API,在基本不改變開發模式的情況下,編寫一個爬蟲。
WebMagic
的結構分爲Downloader
、PageProcessor
、Scheduler
、Pipeline
四大組件,並由Spider將它們彼此組織起來。這四大組件對應爬蟲生命週期中的下載、處理、管理和持久化等功能。而Spider則將這幾個組件組織起來,讓它們可以互相交互,流程化的執行,可以認爲Spider是一個大的容器,它也是WebMagic
邏輯的核心。
WenMagic組件:
-
Downloader
Downloader負責從互聯網上下載頁面,以便後續處理。WebMagic默認使用了ApacheHttpClient作爲下載工具。
-
PageProcesser
PageProcessor負責解析頁面,抽取有用信息,以及發現新的鏈接。WebMagic使用
Jsoup
作爲HTML解析工具,並基於其開發瞭解析XPath
的工具Xsoup
。在這四個組件中,PageProcessor對於每個站點每個頁面都不一樣,是需要使用者定製的部分。
-
Scheduler
Scheduler負責管理待抓取的URL,以及一些去重的工作。WebMagic默認提供了JDK的內存隊列來管理URL,並用集合來進行去重。也支持使用
Redis
進行分佈式管理。 -
Pipeline
Pipeline負責抽取結果的處理,包括計算、持久化到文件、數據庫等。WebMagic默認提供了“輸出到控制檯”和“保存到文件”兩種結果處理方案。
API
Spider API
方法 | 說明 | 示例 |
---|---|---|
create(PageProcessor) | 創建Spider | Spider.create(new GithubRepoProcessor()) |
addUrl(String…) | 添加初始的URL | Spider.addUrl(“http://webmagic.io/docs/”) |
thread(n) | 開啓n個線程 | Spider.thread(5) |
run() | 啓動,會阻塞當前線程執行 | Spider.run() |
start()/runAsync() | 異步啓動,當前線程繼續執行 | Spider.start() |
stop() | 停止爬蟲 | Spider.stop() |
addPipeline(Pipeline) | 添加一個Pipeline,一個Spider可以有多個Pipeline | Spider .addPipeline(new ConsolePipeline()) |
setScheduler(Scheduler) | 設置Scheduler,一個Spider只能有一個Scheduler | Spider.setScheduler(new RedisScheduler()) |
setDownloader(Downloader) | 設置Downloader,一個Spider只能有一個Downloader | Spider.setDownloader(new SeleniumDownloader()) |
get(String) | 同步調用,並直接取得結果 | ResultItems result = Spider.get(“http://webmagic.io/docs/”) |
getAll(String…) | 同步調用,並直接取得一堆結果 | List results = Spider.getAll(“http://webmagic.io/docs/”,“http://webmagic.io/xxx”) |
同時Spider的其他組件(Downloader、Scheduler、Pipeline)都可以通過set方法來進行設置。
Site API
方法 | 說明 | 示例 |
---|---|---|
setCharset(String) | 設置編碼 | site.setCharset(“utf-8”) |
setUserAgent(String) | 設置UserAgent | site.setUserAgent(“Spider”) |
setTimeOut(int) | 設置超時時間, 單位是毫秒 | site.setTimeOut(3000) |
setRetryTimes(int) | 設置重試次數 | site.setRetryTimes(3) |
setCycleRetryTimes(int) | 設置循環重試次數 | site.setCycleRetryTimes(3) |
addCookie(String,String) | 添加一條cookie | site.addCookie(“dotcomt_user”,“code4craft”) |
setDomain(String) | 設置域名,需設置域名後,addCookie纔可生效 | site.setDomain(“github.com”) |
addHeader(String,String) | 添加一條addHeader | site.addHeader(“Referer”,“https://github.com”) |
setHttpProxy(HttpHost) | 設置Http代理 | site.setHttpProxy(new HttpHost(“127.0.0.1”,8080)) |
setSleepTime | 間隔時間設置 | site.setSleepTime(100) |
PageProcessor
爬取頁面全部內容
需求:編寫爬蟲程序,爬取csdn
中博客的內容 https://blog.csdn.net/
-
創建工程,引入依賴
<?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>com.xushuai</groupId> <artifactId>webmagic_demo</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-core</artifactId> <version>0.7.3</version> </dependency> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-extension</artifactId> <version>0.7.3</version> </dependency> </dependencies> </project>
-
實現頁面爬取
package com.xushuai.magic.spider; import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.Spider; import us.codecraft.webmagic.pipeline.ConsolePipeline; import us.codecraft.webmagic.processor.PageProcessor; /** * Spider Class Demo */ public class PageProcessorDemo1 implements PageProcessor { public void process(Page page) { System.out.println(page.getHtml().toString()); } public Site getSite() { return Site.me().setSleepTime(100).setRetryTimes(3); } public static void main(String[] args) { Spider.create(new PageProcessorDemo1()) // 添加爬取的主網站 .addUrl("https://www.csdn.net/") .run(); } }
Page代表了從
Downloader
下載到的一個頁面——可能是HTML,也可能是JSON或者 其他文本格式的內容。Page是WebMagic
抽取過程的核心對象,它提供一些方法可供抽取、結果保存等。Site用於定義站點本身的一些配置信息,例如編碼、HTTP頭、超時時間、重試策略等、代理等,都可以通過設置Site對象來進行配置。
爬取指定內容
使用xpath
來抓去網頁指定部分內容
page.getHtml().xpath("//*[@id=\"nav\"]/div/div/ul/li[5]/a");
添加目標地址
添加目標地址,將目標地址中所有的鏈接添加到待爬取列表
page.addTargetRequests(page.getHtml().links().all());
目標地址正則匹配
需求:只提取博客的文章詳細頁內容,並提取標題
page.addTargetRequests(page.getHtml()
.links().regex("https://blog.csdn.net/[a-z 0-9-]+/article/details/[0-9]{8}").all());
Pipeline
ConsolePipeline 控制檯輸出(省略)
FilePipeline 文件輸出
public static void main(String[] args) {
Spider.create(new PageProcessorDemo1())
// 添加爬取的主網站
.addUrl("https://www.csdn.net/")
// 添加控制檯輸出管道
.addPipeline(new ConsolePipeline())
// 添加文件輸出管道
.addPipeline(new FilePipeline("F:/data"))
.run();
}
JsonFilePipeline Json輸出
public static void main(String[] args) {
Spider.create(new PageProcessorDemo1())
// 添加爬取的主網站
.addUrl("https://www.csdn.net/")
// 添加控制檯輸出管道
.addPipeline(new ConsolePipeline())
// 添加文件輸出管道
.addPipeline(new FilePipeline("F:/data"))
// 添加Json輸出管道
.addPipeline(new JsonFilePipeline("F:/json"))
.run();
}
Custom Pipeline 自定義輸出
-
編寫自定義管道類
package com.xushuai.magic.pipeline; import us.codecraft.webmagic.ResultItems; import us.codecraft.webmagic.Task; import us.codecraft.webmagic.pipeline.Pipeline; /** * 自定義輸出管道 */ public class CustomPipeline implements Pipeline { public void process(ResultItems resultItems, Task task) { System.out.println(resultItems.get("title")); } }
-
添加自定義管道
public static void main(String[] args) { Spider.create(new PageProcessorDemo1()) // 添加爬取的主網站 .addUrl("https://www.csdn.net/") // 添加控制檯輸出管道 .addPipeline(new ConsolePipeline()) // 添加文件輸出管道 .addPipeline(new FilePipeline("F:/data")) // 添加Json輸出管道 .addPipeline(new JsonFilePipeline("F:/json")) // 添加自定義管道 .addPipeline(new CustomPipeline()) .run(); }
Scheduler
Scheduler(URL管理)
最基本的功能是實現對已經爬取的URL進行標示。可以實現URL的增量去重。
目前Scheduler
主要有三種實現方式:
- 內存隊列:QueueScheduler
- 文件隊列:FileCacheQueueScheduler
- Redis隊列:RedisScheduler
內存隊列
public static void main(String[] args) {
Spider.create(new PageProcessorDemo1())
// 添加爬取的主網站
.addUrl("https://www.csdn.net/")
// 添加內存隊列
.setScheduler(new QueueScheduler())
.run();
}
文件隊列
使用文件保存抓取URL,可以在關閉程序並下次啓動時,從之前抓取到的URL繼續抓取。
public static void main(String[] args) {
Spider.create(new PageProcessorDemo1())
// 添加爬取的主網站
.addUrl("https://www.csdn.net/")
// 添加文件隊列
.setScheduler(new FileCacheQueueScheduler("F:/scheduler"))
.run();
}
Redis隊列
使用Redis保存抓取隊列,可進行多臺機器同時合作抓取。
public static void main(String[] args) {
Spider.create(new PageProcessorDemo1())
// 添加爬取的主網站
.addUrl("https://www.csdn.net/")
// 添加Redis隊列
.setScheduler(new RedisScheduler("192.168.136.104"))
.run();
}
十次方文章數據爬取
需求:每日某時間段從CSDN播客中爬取文檔,存入文章數據庫中。
準備工作
-
CSDN中各個頻道的地址
頻道名稱 地址 資訊 https://blog.csdn.net/nav/news 人工智能 https://blog.csdn.net/nav/ai 區塊鏈 https://blog.csdn.net/nav/blockchain 數據庫 https://blog.csdn.net/nav/db 前端 https://blog.csdn.net/nav/web 編程語言 https://blog.csdn.net/nav/lang -
向數據庫
tensquare_article
中的tb_channel
表中添加記錄
文章爬取微服務
創建Module
-
pom.xml
<?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"> <parent> <artifactId>tensquare_parent</artifactId> <groupId>com.tensquare</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>tensquare_article_crawler</artifactId> <dependencies> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-core</artifactId> <version>0.7.3</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-extension</artifactId> <version>0.7.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.tensquare</groupId> <artifactId>tensquare_common</artifactId> <version>${tensquare.version}</version> </dependency> </dependencies> </project>
-
application.yml
server: port: 9014 spring: application: name: tensquare-crawler datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.136.104:3306/tensquare_article?characterEncoding=UTF8 username: root password: 123456 jpa: database: mysql show-sql: true redis: host: 192.168.136.104
-
啓動類
package com.tensquare.crawler; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; import us.codecraft.webmagic.scheduler.RedisScheduler; import util.IdWorker; @EnableScheduling @SpringBootApplication public class CrawlerApplication { @Value("${spring.redis.host}") private String REDIS_HOST; public static void main(String[] args) { SpringApplication.run(CrawlerApplication.class, args); } @Bean public IdWorker idWorker() { return new IdWorker(1, 11); } @Bean public RedisScheduler redisScheduler() { return new RedisScheduler(REDIS_HOST); } }
-
複製文章實體類以及數據訪問接口(省略)
爬取類(PageProcessor)
package com.tensquare.crawler.processor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;
/**
* 文章爬取類
*/
@Component
public class ArticleProcessor implements PageProcessor {
@Override
public void process(Page page) {
// 添加爬取的頁面
page.addTargetRequests(page.getHtml()
.links().regex("https://blog.csdn.net/[a-z 0-9-]+/article/details/[0-9]{8}").all());
// 獲取標題以及內容
String title = page.getHtml().xpath("//*[@id=\"mainBox\"]/main/div[1]/div/div/div[1]/h1/text()").get();
String content = page.getHtml().xpath("//*[@id=\"article_content\"]").get();
if (StringUtils.isNotBlank(title) && StringUtils.isNotBlank(content)) {
page.putField("title", title);
page.putField("content", content);
} else {
page.setSkip(true);
}
}
@Override
public Site getSite() {
return Site.me().setRetryTimes(100).setSleepTime(100);
}
}
入庫類(Pipeline)
package com.tensquare.crawler.pipeline;
import com.tensquare.crawler.dao.ArticleDao;
import com.tensquare.crawler.pojo.Article;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;
import util.IdWorker;
@Component
public class ArticlePipeline implements Pipeline {
@Autowired
private ArticleDao articleDao;
@Autowired
private IdWorker idWorker;
private String channelId;
public void setChannelId(String channelId) {
this.channelId = channelId;
}
@Override
public void process(ResultItems resultItems, Task task) {
// 取出爬取類中的title和content
String title = resultItems.get("title");
String content = resultItems.get("content");
// 構造文章對象
Article article = new Article();
article.setChannelid(channelId);
article.setId(idWorker.nextId().toString());
article.setTitle(title);
article.setContent(content);
// 保存
articleDao.save(article);
}
}
任務(Task)
package com.tensquare.crawler.task;
import com.tensquare.crawler.pipeline.ArticlePipeline;
import com.tensquare.crawler.processor.ArticleProcessor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.scheduler.RedisScheduler;
@Slf4j
@Component
public class ArticleCrawlerTask {
@Autowired
private ArticlePipeline articlePipeline;
@Autowired
private RedisScheduler redisScheduler;
@Autowired
private ArticleProcessor articleProcessor;
@Scheduled(cron = "0 0 0 * * *")
public void aiTask() {
log.info("開始爬取AI文章");
articlePipeline.setChannelId("ai");
Spider spider = Spider.create(articleProcessor);
spider.addUrl("https://blog.csdn.net/nav/ai/")
.addPipeline(articlePipeline)
.setScheduler(redisScheduler)
.start();
}
@Scheduled(cron = "0 0 1 * * *")
public void blockChainTask() {
log.info("開始爬取區塊鏈文章");
articlePipeline.setChannelId("blockchain");
Spider spider = Spider.create(articleProcessor);
spider.addUrl("https://blog.csdn.net/nav/blockchain/")
.addPipeline(articlePipeline)
.setScheduler(redisScheduler)
.run();
}
@Scheduled(cron = "0 0 2 * * *")
public void dbTask() {
log.info("開始爬取區數據庫文章");
articlePipeline.setChannelId("db");
Spider spider = Spider.create(articleProcessor);
spider.addUrl("https://blog.csdn.net/nav/db/")
.addPipeline(articlePipeline)
.setScheduler(redisScheduler)
.run();
}
@Scheduled(cron = "0 0 3 * * *")
public void langTask() {
log.info("開始爬取編程語言文章");
articlePipeline.setChannelId("lang");
Spider spider = Spider.create(articleProcessor);
spider.addUrl("https://blog.csdn.net/nav/lang/")
.addPipeline(articlePipeline)
.setScheduler(redisScheduler)
.run();
}
@Scheduled(cron = "0 0 4 * * *")
public void newsTask() {
log.info("開始爬取資訊文章");
articlePipeline.setChannelId("news");
Spider spider = Spider.create(articleProcessor);
spider.addUrl("https://blog.csdn.net/nav/news/")
.addPipeline(articlePipeline)
.setScheduler(redisScheduler)
.run();
}
@Scheduled(cron = "0 0 5 * * *")
public void webTask() {
log.info("開始爬取前端文章");
articlePipeline.setChannelId("web");
Spider spider = Spider.create(articleProcessor);
spider.addUrl("https://blog.csdn.net/nav/web/")
.addPipeline(articlePipeline)
.setScheduler(redisScheduler)
.run();
}
}
注意:addUrl(url)
中添加的路徑一定要以/
結尾。
十次方用戶數據爬取
從csdn中爬取用戶暱稱和頭像,存到用戶表,頭像圖片存儲到本地。
用戶爬取微服務
創建Module
-
pom.xml(省略,與文章數據爬取微服務一致)
-
application.yml
server: port: 9015 spring: application: name: tensquare-user-crawler datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.136.104:3306/tensquare_user?characterEncoding=UTF8 username: root password: 123456 jpa: database: mysql show-sql: true redis: host: 192.168.136.104
-
啓動類
package com.tensquare.crawler; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; import us.codecraft.webmagic.scheduler.RedisScheduler; import util.IdWorker; @EnableScheduling @SpringBootApplication public class UserCrawlerApplication { @Value("${spring.redis.host}") private String REDIS_HOST; public static void main(String[] args) { SpringApplication.run(UserCrawlerApplication.class, args); } @Bean public IdWorker idWorker() { return new IdWorker(1, 11); } @Bean public RedisScheduler redisScheduler() { return new RedisScheduler(REDIS_HOST); } }
-
複製用戶實體類以及數據訪問接口(省略)
下載工具類
在tensquare_common
中添加下載工具類
package util;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
/**
* 下載工具類
*/
public class DownloadUtil {
/**
* 下載
*
* @param urlStr
* @param filename
* @param savePath
* @throws IOException
*/
public static void download(String urlStr, String filename, String
savePath) throws IOException {
URL url = new URL(urlStr);
//打開url連接
URLConnection connection = url.openConnection();
//請求超時時間
connection.setConnectTimeout(5000);
//輸入流
InputStream in = connection.getInputStream();
//緩衝數據
byte[] bytes = new byte[1024];
//數據長度
int len;
//文件
File file = new File(savePath);
if (!file.exists())
file.mkdirs();
OutputStream out = new
FileOutputStream(file.getPath() + "\\" + filename);
//先讀到bytes中
while ((len = in.read(bytes)) != -1){
//再從bytes中寫入文件
out.write(bytes, 0, len);
}
//關閉IO
out.close();
in.close();
}
}
爬取類(PageProcessor)
package com.tensquare.crawler.processor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;
@Component
public class UserProcessor implements PageProcessor {
@Override
public void process(Page page) {
// 添加爬取的頁面
page.addTargetRequests(page.getHtml().links().regex("https://blog.csdn.net/[a-z 0-9-]+/article/details/[0-9]{8}").all());
// 暱稱和頭像
String nickname = page.getHtml().xpath("//*[@id=\"uid\"]/text()").get();
String image = page.getHtml().xpath("//*[@id=\"asideProfile\"]/div[1]/div[1]/a/img[1]").get();
// 保存
if (StringUtils.isNotBlank(nickname) && StringUtils.isNotBlank(image)) {
page.putField("nickname", nickname);
page.putField("image", image);
} else {
page.setSkip(true);
}
}
@Override
public Site getSite() {
return Site.me().setRetryTimes(3000).setSleepTime(100);
}
}
入庫類(Pipeline)
package com.tensquare.crawler.pipeline;
import com.tensquare.crawler.dao.UserDao;
import com.tensquare.crawler.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;
import util.DownloadUtil;
import util.IdWorker;
import java.io.IOException;
@Slf4j
@Component
public class UserPipeline implements Pipeline {
@Autowired
private UserDao userDao;
@Autowired
private IdWorker idWorker;
@Override
public void process(ResultItems resultItems, Task task) {
// 取出nickname和image
String nickname = resultItems.get("nickname").toString();
String image = resultItems.get("image").toString();
User user = new User();
user.setId(idWorker.nextId().toString());
user.setNickname(nickname);
String fileName = image.substring(image.lastIndexOf("/") + 1, image.lastIndexOf(" ") - 1);
user.setAvatar(fileName);
userDao.save(user);
// 下載圖片
try {
String url = image.substring(image.indexOf("https://"), image.lastIndexOf(" ") - 1);
DownloadUtil.download(url, fileName, "E:/userImage");
} catch (IOException e) {
log.error("下載文件發生異常!e = ", e);
}
}
}
任務類(Task)
package com.tensquare.crawler.task;
import com.tensquare.crawler.pipeline.UserPipeline;
import com.tensquare.crawler.processor.UserProcessor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.scheduler.RedisScheduler;
/**
* 用戶數據爬取類
*/
@Slf4j
@Component
public class UserCrawlerTask {
@Autowired
private UserProcessor userProcessor;
@Autowired
private UserPipeline userPipeline;
@Autowired
private RedisScheduler redisScheduler;
@Scheduled(cron = "0 0 6 * * *")
public void userTask () {
log.info("開始爬取用戶數據");
Spider spider = Spider.create(userProcessor);
spider.addUrl("https://blog.csdn.net/")
.addPipeline(userPipeline)
.setScheduler(redisScheduler)
.start();
}
}
注意:addUrl
方法以/
結尾
爬取效果
文章數據
用戶數據
用戶數據
用戶頭像