酷壳(coolshell.cn)镜像站建设经验分享之二——整站内容爬取

酷壳(coolshell.cn)镜像站建设经验分享之二——整站内容爬取

朱治龙
2024-02-28 / 0 评论 / 24 阅读 / 正在检测是否收录...

本系列文章生成的镜像站已上传到 Gitee Pages 托管平台,可通过如下地址查看完成后的效果:https://jhlxge.gitee.io/coolshell-mirror/
2024年6月5日更新 :Gitee Pages服务已停止提供服务,可通过本机托管的如下地址访问:https://coolshell-mirror.work.zhuzhilong.com/
Gitee Pages 停服

在前面的数据准备章节咱们已经在 magic-boot 基础工程上整合了 jsoup,而 jsoup 正是 java 领域数据爬取及 html 解析的神器,对于 html 的处理如同 JS 中的 jQuery 一般神一样的存在,而且一些 API 跟 jQuery 也比较接近,使用起来不得不说那叫一个丝滑。废话少说,咱们开始 show me the code。

01采集列表

通过分析酷壳首页的内容列表,我们发现一共有74页,并且页面URL规则是:https://coolshell.cn/page/${页码},经过验证该规则也适用于第一页,那我们就先把所有列表页的html采集过来,存储到 crawler_list 表,爬取代码如下:

import cn.hutool.core.util.StrUtil
import org.jsoup.Jsoup
import cn.hutool.core.date.DateUtil
import cn.hutool.core.thread.ThreadUtil
import log
var START_PAGE = 1
var END_PAGE = 74
var BASE_URL = 'https://coolshell.cn/'
var currentPage = START_PAGE
var timer = DateUtil.timer(); // 定义计时器,用于记录时间
var MAX_RETRY_COUNT = 100
var currentRetryCount = 0;
while(currentPage <= END_PAGE) {
    var pageURL = currentPage === 1 ? BASE_URL : BASE_URL + "page/" + currentPage
    try {
        var doc = Jsoup.connect(pageURL).timeout(30000).userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36").get();
        var html = doc.outerHtml();
        log.info(pageURL)
        if (StrUtil.isNotBlank(html)) {
            db['MDC'].table('crawler_list').insert({
                siteId:1,
                pageUrl: pageURL,
                pageHtml: html,
                successFlag:'Y'
            })
        }
        currentPage++
        currentRetryCount = 0
    } catch(e) {
        // 暂停1秒,避免请求间隔太短,导致 Matomo 负载过高拿不到数据
        ThreadUtil.sleep(1000);
        currentRetryCount++;
        if (currentRetryCount == MAX_RETRY_COUNT) {
            exit 0, `爬取第${currentPage}页数据失败,页面URL:${pageURL}`;
        }
    }
    
}
log.info(`Cost Time: ${timer.intervalPretty()}.`);

所有列表页数据爬取完后,crawler_list表的数据如下:
爬取完后crawler_list表的数据

分析列表页获取文章详情页的URL列表

得到列表数据后我们需要分析列表html,得到每页中博客页面的URL,下面是代码:

import cn.hutool.core.util.StrUtil
import org.jsoup.Jsoup
import cn.hutool.core.date.DateUtil
import cn.hutool.json.JSONUtil
import log
var timer = DateUtil.timer(); // 定义计时器,用于记录时间
var pageList = db['MDC'].select('select id,page_html from crawler_list')
for (pageItem in pageList) {
    var pageHtml = pageItem.pageHtml
    var doc = Jsoup.parse(pageHtml)
    var links = doc.select('.entry-title a')
    log.info(links.toString())
    var linkArr = []
    for (linkItem in links) {
        var linkURL = linkItem.attr('href')
        var linkTitle = linkItem.text()
        linkArr.push({
            linkURL,
            linkTitle
        })
        db['MDC'].table('crawler_article').insert({
            siteId:1,
            pageUrl: linkURL,
            articleTitle:linkTitle
        })
    }
    var updateMap = {
        id: pageItem.id,
        targetLinks: JSONUtil.toJsonStr(linkArr)
    }
    db['MDC'].table('crawler_list').primary('id').update(updateMap)
}
log.info(`Cost Time: ${timer.intervalPretty()}.`);

爬取文章详情页 html

分析完成后,在 crawler_article 表会得到所有文章详情页的URL,然后我们遍历 crawler_article 表的记录,逐个URL爬取回每篇博客内容的 html,以下是爬取代码:

import cn.hutool.core.util.StrUtil
import org.jsoup.Jsoup
import cn.hutool.core.date.DateUtil
import cn.hutool.json.JSONUtil
import cn.hutool.core.thread.ThreadUtil
import log
var timer = DateUtil.timer(); // 定义计时器,用于记录时间
var pageList = db['MDC'].select('select id,page_url from crawler_article where page_html is null')
var MAX_RETRY_COUNT = 100

for (item in pageList) {
    var currentRetryCount = 0;
    var needCrawler = true
    var pageURL = item.pageUrl
    // 由于爬取过程中经常会出现超时未响应的情况,这里使用while增加了重试机制
    while(needCrawler) {
        try {
            var doc = Jsoup.connect(pageURL).timeout(30000).userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36").get();
            var html = doc.outerHtml();
            if(StrUtil.isNotBlank(html)) {
                var updateMap = {
                    id: item.id,
                    pageHtml: html,
                    crawlerTime:DateUtil.now()
                }
                db['MDC'].table('crawler_article').primary('id').update(updateMap)
            }
            needCrawler = false
            ThreadUtil.sleep(2000);
        } catch(e) {
            log.error(e.getMessage())
            currentRetryCount++
            log.info(`采集 ${pageURL} 失败,已重试 ${currentRetryCount} 次。`)
            if (currentRetryCount == MAX_RETRY_COUNT) {
                exit 0, '重试次数过多';
            }
        }   
    }
}

log.info(`Cost Time: ${timer.intervalPretty()}.`);

需要说明一下,由于coolshell.cn 服务器在国外,爬取过程有些不稳定,所以我加入了重试机制。

至此,我们需要的博客内容已经爬取完毕。接下来的章节,我将分析爬取的html,得到我们需要的博客内容数据。

0

评论 (0)

取消