上一節(爬蟲系列(0):項目搭建)
網絡爬蟲的都是通過多線程,多任務邏輯實現的,在springboot框架中已封裝線程池(ThreadPoolTaskExecutor),我們只需要使用就是了。
這一節我們主要實現多線程抓取網頁連接信息,并將信息存儲在隊列里面。
在pom中引入新包,具體如下:
<dependency>
<!-- common工具包 -->
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<!-- java處理HTML的工具包 -->
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<!-- lombok工具包,簡化編碼 -->
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
為了簡化編碼,這里引入了lombok,在使用時候IDE需要安裝lombok插件,否則會提示編譯錯誤。
springboot的配置文件都是在application.properties(.yml)統一管理的,在這里,我們也把爬蟲相關的配置通過@ConfigurationProperties注解來實現。直接上代碼:
package mobi.huanyuan.spider.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 爬蟲配置.
*
* @author Jonathan L.(xingbing.lai@gmail.com)
* @version 1.0.0 -- Datetime: 2020/2/18 11:10
*/
@Data
@ConfigurationProperties(prefix = "huanyuan.spider")
public class SpiderConfig {
/**
* 爬取頁面最大深度
*/
public int maxDepth = 2;
/**
* 下載頁面線程數
*/
public int minerHtmlThreadNum = 2;
//=================================================
// 線程池配置
//=================================================
/**
* 核心線程池大小
*/
private int corePoolSize = 4;
/**
* 最大可創建的線程數
*/
private int maxPoolSize = 100;
/**
* 隊列最大長度
*/
private int queueCapacity = 1000;
/**
* 線程池維護線程所允許的空閑時間
*/
private int keepAliveSeconds = 300;
}
然后,需要修改這些配置,只需要修改application.properties(.yml)里邊即可:
幻猿簡易爬蟲配置
線程池使用springboot已有的,配置也在上邊配置管理里邊有,這里只初始化配置即可:
package mobi.huanyuan.spider.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 線程池配置.
*
* @author Jonathan L.(xingbing.lai@gmail.com)
* @version 1.0.0 -- Datetime: 2020/2/18 11:35
*/
@Configuration
public class ThreadPoolConfig {
@Autowired
private SpiderConfig spiderConfig;
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(spiderConfig.getMaxPoolSize());
executor.setCorePoolSize(spiderConfig.getCorePoolSize());
executor.setQueueCapacity(spiderConfig.getQueueCapacity());
executor.setKeepAliveSeconds(spiderConfig.getKeepAliveSeconds());
// 線程池對拒絕任務(無線程可用)的處理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
這一節我們主要是抓取URL并保存進隊列,所以涉及到的隊列有待抓取隊列和待分析隊列(下一節分析時候用,這里只做存儲),此外,為了防止重復抓取同一個URL,這里還需要加一個Set集合,將已訪問過的地址做個記錄。
package mobi.huanyuan.spider;
import lombok.Getter;
import mobi.huanyuan.spider.bean.SpiderHtml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
/**
* 爬蟲訪問隊列.
*
* @author Jonathan L.(xingbing.lai@gmail.com)
* @version 1.0.0 -- Datetime: 2020/2/18 10:54
*/
public class SpiderQueue {
private static Logger logger = LoggerFactory.getLogger(SpiderQueue.class);
/**
* Set集合 保證每一個URL只訪問一次
*/
private static volatile Set<String> urlSet = new HashSet<>();
/**
* 待訪問隊列<br>
* 爬取頁面線程從這里取數據
*/
private static volatile Queue<SpiderHtml> unVisited = new LinkedList<>();
/**
* 等待提取URL的分析頁面隊列<br>
* 解析頁面線程從這里取數據
*/
private static volatile Queue<SpiderHtml> waitingMine = new LinkedList<>();
/**
* 添加到URL隊列
*
* @param url
*/
public synchronized static void addUrlSet(String url) {
urlSet.add(url);
}
/**
* 獲得URL隊列大小
*
* @return
*/
public static int getUrlSetSize() {
return urlSet.size();
}
/**
* 添加到待訪問隊列,每個URL只訪問一次
*
* @param spiderHtml
*/
public synchronized static void addUnVisited(SpiderHtml spiderHtml) {
if (null != spiderHtml && !urlSet.contains(spiderHtml.getUrl())) {
logger.info("添加到待訪問隊列[{}] 當前第[{}]層 當前線程[{}]", spiderHtml.getUrl(), spiderHtml.getDepth(), Thread.currentThread().getName());
unVisited.add(spiderHtml);
}
}
/**
* 待訪問出隊列
*
* @return
*/
public synchronized static SpiderHtml unVisitedPoll() {
return unVisited.poll();
}
/**
* 添加到等待提取URL的分析頁面隊列
*
* @param html
*/
public synchronized static void addWaitingMine(SpiderHtml html) {
waitingMine.add(html);
}
/**
* 等待提取URL的分析頁面出隊列
*
* @return
*/
public synchronized static SpiderHtml waitingMinePoll() {
return waitingMine.poll();
}
/**
* 等待提取URL的分析頁面隊列大小
* @return
*/
public static int waitingMineSize() {
return waitingMine.size();
}
}
直接上代碼:
package mobi.huanyuan.spider.runable;
import mobi.huanyuan.spider.SpiderQueue;
import mobi.huanyuan.spider.bean.SpiderHtml;
import mobi.huanyuan.spider.config.SpiderConfig;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 抓取頁面任務.
*
* @author Jonathan L.(xingbing.lai@gmail.com)
* @version 1.0.0 -- Datetime: 2020/2/18 11:43
*/
public class SpiderHtmlRunnable implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(SpiderHtmlRunnable.class);
private static boolean done = false;
private SpiderConfig config;
public SpiderHtmlRunnable(SpiderConfig config) {
this.config = config;
}
@Override
public void run() {
while (!SpiderHtmlRunnable.done) {
done = true;
minerHtml();
done = false;
}
}
public synchronized void minerHtml() {
SpiderHtml minerUrl = SpiderQueue.unVisitedPoll(); // 待訪問出隊列。
try {
//判斷當前頁面爬取深度
if (null == minerUrl || StringUtils.isBlank(minerUrl.getUrl()) || minerUrl.getDepth() > config.getMaxDepth()) {
return;
}
//判斷爬取頁面URL是否包含http
if (!minerUrl.getUrl().startsWith("http")) {
logger.info("當前爬取URL[{}]沒有http", minerUrl.getUrl());
return;
}
logger.info("當前爬取頁面[{}]爬取深度[{}] 當前線程 [{}]", minerUrl.getUrl(), minerUrl.getDepth(), Thread.currentThread().getName());
Connection conn = Jsoup.connect(minerUrl.getUrl());
conn.header("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13");//配置模擬瀏覽器
Document doc = conn.get();
String page = doc.html();
SpiderHtml spiderHtml = new SpiderHtml();
spiderHtml.setUrl(minerUrl.getUrl());
spiderHtml.setHtml(page);
spiderHtml.setDepth(minerUrl.getDepth());
System.out.println(spiderHtml.getUrl());
// TODO: 添加到繼續爬取隊列
SpiderQueue.addWaitingMine(spiderHtml);
} catch (Exception e) {
logger.info("爬取頁面失敗 URL [{}]", minerUrl.getUrl());
logger.info("Error info [{}]", e.getMessage());
}
}
}
這里就是個Runnable任務,主要目標就是拉去URL數據,然后封裝成SpiderHtml對象存放在待分析隊列里邊。 這里用到了jsoup--一個java對HTML分析操作的工具包,不清楚的可以去搜索看看,之后章節涉及到分析的部分也會用到。
package mobi.huanyuan.spider.bean;
import lombok.Data;
import java.io.Serializable;
/**
* 頁面信息類.
*
* @author Jonathan L.(xingbing.lai@gmail.com)
* @version 1.0.0 -- Datetime: 2020/2/18 11:02
*/
@Data
public class SpiderHtml implements Serializable {
/**
* 頁面URL
*/
private String url;
/**
* 頁面信息
*/
private String html;
/**
* 爬取深度
*/
private int depth;
}
package mobi.huanyuan.spider;
import mobi.huanyuan.spider.bean.SpiderHtml;
import mobi.huanyuan.spider.config.SpiderConfig;
import mobi.huanyuan.spider.runable.SpiderHtmlRunnable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 爬蟲.
*
* @author Jonathan L.(xingbing.lai@gmail.com)
* @version 1.0.0 -- Datetime: 2020/2/18 11:23
*/
@Component
public class Spider {
private static Logger logger = LoggerFactory.getLogger(Spider.class);
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Autowired
private SpiderConfig spiderConfig;
public void start(SpiderHtml spiderHtml) {
//程序啟動,將第一個起始頁面放入待訪問隊列。
SpiderQueue.addUnVisited(spiderHtml);
//將URL 添加到URL隊列 保證每個URL只訪問一次
SpiderQueue.addUrlSet(spiderHtml.getUrl());
//download
for (int i = 0; i < spiderConfig.getMinerHtmlThreadNum(); i++) {
SpiderHtmlRunnable minerHtml = new SpiderHtmlRunnable(spiderConfig);
threadPoolTaskExecutor.execute(minerHtml);
}
// TODO: 監控爬取完畢之后停線程池,關閉程序
try {
TimeUnit.SECONDS.sleep(20);
logger.info("待分析URL隊列大小: {}", SpiderQueue.waitingMineSize());
// 關閉線程池
threadPoolTaskExecutor.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在"// TODO:"之后的代碼邏輯這里是臨時的,等后邊章節完善之后,這里就慢慢去掉。
要跑起這一節的代碼,需要在springboot項目main方法中加入如下代碼:
ConfigurableApplicationContext context = SpringApplication.run(SpiderApplication.class, args);
Spider spider = context.getBean(Spider.class);
SpiderHtml startPage = new SpiderHtml();
startPage.setUrl("$URL");
startPage.setDepth(2);
spider.start(startPage);
$URL就是需要抓取的網頁地址。
springboot項目啟動后,停止需要手動停止,目前沒有處理抓取完自動停止運行的邏輯。 運行結果如下圖:
幻猿簡易爬蟲運行結果
最后,這個章節完成之后整個項目的結構如下圖:
幻猿簡易爬蟲項目結構
程序界的老猿,自媒體界的新寵 じ☆ve
程序界的老猿,自媒體界的新寵 じ☆ve
聯系方式:1405368512@qq.com
發送HTTP請求:首先,你需要向目標網頁發送HTTP請求以獲取其HTML內容。這可以通過Java的內置庫java.net.HttpURLConnection或者使用更高級的庫如Apache Http Client OkHttp等來完成。
·讀取響應內容:一旦你發送了請求并收到了響應,你需要讀取響應的內容,這通常是HTML格式的字符串。
·解析HTML:然后,你需要解析HTML字符串以提取所需的信息,這可以通過正則表達式來完成。但通常建議使用專門的HTML解析庫,如Jsoup。Jsoup提供了一種非常方便的方式來解析HTML文檔,并可以通過類似于CSS或jQuery的選擇器語法來提取和操作數據。
·如果你需要處理更復雜的網頁或進行更高級的網頁抓取和解析任務,你可能還需要考慮使用如Selenium這樣的瀏覽器自動化工具來模擬真實的瀏覽器行為。但是請注意,頻繁或大規模地抓取網頁可能會違反網站的使用條款甚至可能構成法律問題。
一款Java 的HTML解析器, 可直接解析某個URL地址、HTML文本內容。它提供了一套非常省力的API,可通過DOM,CSS以及類似于jQuery的操作方法來取出和操作數據。
爬取成功
溫馨提示:需要爬取網易新聞全部資源 需要二次爬取文章鏈接(爬第一次的鏈接后再爬取獲取作者 時間 圖片) 如果是網易自己的文章可成功爬取 對于網易導入的外部資源鏈接無法處理(網頁結構無法預判)
2022-09-29 20:27:48.321 INFO 11176 --- [ main] com.demo.article.utils.HtmlParseUtil : 文章Article(pkId=null, articleName=助力中阿全面戰略伙伴關系譜寫新篇章, articleAuthor=央視網, gmtCreate=2022-09-29T19:02:45, articleUrl=https://www.163.com/news/article/HIF2VTOA000189FH.html, articleShowPic=https://nimg.ws.126.net/?url=http%3A%2F%2Fcms-bucket.ws.126.net%2F2022%2F0929%2F12707880j00riyxc1001wc000m800e1c.jpg&thumbnail=660x2147483647&quality=80&type=jpg)
2022-09-29 20:27:48.321 INFO 11176 --- [ main] com.demo.article.utils.HtmlParseUtil : 文章Article(pkId=null, articleName=“構筑中華民族共有精神家園”, articleAuthor=中國 新聞網, gmtCreate=2022-09-29T17:07:17, articleUrl=https://www.163.com/news/article/HIESCG7A000189FH.html, articleShowPic=https://nimg.ws.126.net/?url=http%3A%2F%2Fcms-bucket.ws.126.net%2F2022%2F0929%2F5e65fcc1j00riyrzn0039c000p0018gc.jpg&thumbnail=660x2147483647&quality=80&type=jpg)
2022-09-29 20:27:48.321 INFO 11176 --- [ main] com.demo.article.utils.HtmlParseUtil : 文章Article(pkId=null, articleName=片倉鳳美:通過花藝與中國年輕人分享快樂, articleAuthor=人民網, gmtCreate=2022-09-29T17:41:59, articleUrl=https://www.163.com/news/article/HIEUC25B000189FH.html, articleShowPic=https://static.ws.126.net/163/f2e/product/post_nodejs/static/logo.png)
2022-09-29 20:27:48.321 INFO 11176 --- [ main] com.demo.article.utils.HtmlParseUtil : 文章Article(pkId=null, articleName=節前市場探物價:糧油價格穩定 蔬菜雞蛋價格回落, articleAuthor=新華社客戶端, gmtCreate=2022-09-29T16:38:36, articleUrl=https://www.163.com/news/article/HIEQO06H000189FH.html, articleShowPic=https://nimg.ws.126.net/?url=http%3A%2F%2Fcms-bucket.ws.126.net%2F2022%2F0929%2F069a37c3j00riyqnw0013c000b4008cc.jpg&thumbnail=660x2147483647&quality=80&type=jpg)
2022-09-29 20:27:48.321 INFO 11176 --- [ main] com.demo.article.utils.HtmlParseUtil : 文章Article(pkId=null, articleName=好鄰居金不換(國際論壇), articleAuthor=人民網, gmtCreate=2022-09-29T06:08:21, articleUrl=https://www.163.com/dy/article/HIDMLUTN0514R9M0.html, articleShowPic=)
爬取網頁資源
為了更好地理解 快速運用在自己的項目中
專門拍攝了一期視頻講解
B站視頻
[組長讓我用java定時爬取網頁資源?6分鐘學完開造(附源碼)]
源碼的分享
視頻簡介中(Gitee)
制作不易 望大家給個三連支持呀
*請認真填寫需求信息,我們會在24小時內與您取得聯系。