Skip to content

【Java】使用正则表达式采集整站小说数据 小说精品屋爬虫模块的设计与实现

201206030 edited this page Nov 16, 2020 · 1 revision

背景

开源小说漫画系统小说精品屋已经诞生了1年时间了,其间很多同学咨询过我数据抓取的原理,我这里抽出空余时间详细说明一下小说爬虫模块的设计与实现。

爬虫模块设计与实现(多爬虫源配置)

  1. 创建application-crawl.yml配置文件,配置不同网站的正则表达式规则。
#爬取的网站名称类型 1:笔趣岛 ,2:笔趣塔, 3:顶点,4:百书斋,5:笔趣阁,6: 笔趣窝,默认百书斋  更多网站解析中,敬请期待
biquta:
  crawlsource:
    index-url: https://m.biquta.la
    list-page-url: https://m.biquta.la/class/{0}/{1}.html
    book-url-pattern: href="/(\d+_\d+)/"
    score-pattern: <div\s+class="score">(\d+\.\d+)分</div>
    book-name-pattern: <p class="title">([^/]+)</p>
    author-pattern: 作者:([^/]+)<
    status-pattern: 状态:([^/]+)</li>
    cat-pattern: 类别:([^/]+)</li>
    update-time-pattern: 更新:(\d+-\d+-\d+\s\d+:\d+:\d+)</a>
    pic-pattern: <img src="([^>]+)"\s+onerror="this.src=
    intro-pattern: class="review">([^<]+)</p>
    catalog-url-pattern: <a\s+href="(/du/\d+_\d+/)">查看完整目录</a>
    catalog-pattern: <a\s+style=""\s+href="(/\d+_\d+/\d+\.html)">([^/]+)</a>
biqudao:
  crawlsource:
    index-url: https://m.biqudao.net
    list-page-url: https://m.biqudao.net/bqgeclass/{0}/{1}.html
    book-url-pattern: href="/(bqge\d+)/"
    score-pattern: <div\s+class="score">(\d+\.\d+)分</div>
    book-name-pattern: <p class="title">([^/]+)</p>
    author-pattern: <li class="author">作者:([^/]+)</li>
    status-pattern: 状态:([^/]+)</li>
    cat-pattern: 类别:([^/]+)</li>
    update-time-pattern: 更新:(\d+-\d+-\d+\s\d+:\d+:\d+)</a>
    pic-pattern: <img src="([^>]+)"\s+onerror="this.src=
    intro-pattern: class="review">([^<]+)</p>
    catalog-url-pattern: <a\s+href="(/bqge\d+/all\.html)">查看完整目录</a>
    catalog-pattern: <a[^/]+style[^/]+href="(/bqge\d+/\d+\.html)">([^/]+)</a>

dingdian:
  crawlsource:
    index-url: http://m.xdingdiann.com
    list-page-url: http://m.xdingdiann.com/sort/{0}/{1}.html
    book-url-pattern: href="/(ddk\d+)/"
    score-pattern: <div\s+class="score">(\d+\.\d+)分</div>
    book-name-pattern: <p class="title">([^/]+)</p>
    author-pattern: 作者:([^/]+)<
    status-pattern: 状态:([^/]+)</li>
    cat-pattern: 类别:([^/]+)</li>
    update-time-pattern: 更新:(\d+-\d+-\d+\s\d+:\d+:\d+)</a>
    pic-pattern: <img src="([^>]+)"\s+onerror="this.src=
    intro-pattern: class="review">([^/]+)</p>
    catalog-url-pattern: <a\s+href="(/ddk\d+/all.html)">查看完整目录</a>
    catalog-pattern: <a\s+style=""\s+href="(/ddk\d+/\d+\.html)">([^/]+)</a>


baishuzhai:
  crawlsource:
    index-url: https://m.baishuzhai.com
    list-page-url: https://m.baishuzhai.com/sort/{0}/{1}.html
    book-url-pattern: href="/(ibook/\d+/\d+)/"
    score-pattern: <div\s+class="score">(\d+\.\d+)分</div>
    book-name-pattern: <p class="title">([^/]+)</p>
    author-pattern: 作者:([^/]+)<
    status-pattern: 状态:([^/]+)</li>
    cat-pattern: 类别:([^/]+)</li>
    update-time-pattern: 更新:(\d+-\d+-\d+)</li>
    pic-pattern: <img src="([^>]+)"\s+onerror="this.src=
    intro-pattern: class="review">([^/]+)</p>
    catalog-url-pattern: <a\s+href="(/ibook/\d+/\d+/all\.html)">查看完整目录</a>
    catalog-pattern: <a\s+style=""\s+href="(/ibook/\d+/\d+/\d+\.html)">([^/]+)</a>

biquge:
  crawlsource:
    index-url: http://m.biquge.info
    list-page-url: http://m.biquge.info/paihangbang_lastupdate/{0}.html
    book-url-pattern: href="/(\d+_\d+)/"
    score-pattern: <i>(\d+)</i>
    book-name-pattern: <a\s+href="/(\d+_\d+)/">([^<]+)</a>
    author-pattern: 作者:([^<]+)<
    status-pattern: <p>状态:([^<]+)</p>
    cat-pattern: <a\s+href="/list/\d+_\d+\.html">([^<]+)</a>
    update-time-pattern: <p>更新:(\d+-\d+-\d+T\d+:\d+:\d+)</p>
    pic-pattern: <div\s+class="block_img2">\s*<img\s+src="([^"]+)"
    intro-pattern: class="review">([^/]+)</p>
    catalog-url-pattern: <a\s+href="(/ddk\d+/all.html)">查看完整目录</a>
    catalog-pattern: <dd>\s*<a\s+href="(\d+\.html)"\s+title="([^"]+)">([^<]+)</a>\s*</dd>
  1. 创建基础的爬虫源抽象类,用于接收网站通用配置参数和定义基础的解析入库函数。
/**
 * 爬虫源
 * @author 11797
 */
@Data
public abstract class BaseCrawlSource {

    /**
     * 采集的小说最低评分配置
     * */
    @Value("${books.lowestScore}")
    private Float lowestScore;

    /**
     * 解析数据
     * */
    public abstract void parse();


    /**
     * 更新书籍
     * */
    public abstract void update();
}
  1. 创建不同类型网站(Html,Json)的爬虫源抽象类,继承BaseCrawlSource,用于接收网站规则配置参数。
/**
 * html爬虫源
 * @author 11797
 */
@Data
public abstract class BaseHtmlCrawlSource extends BaseCrawlSource{

    /**
     * 首页url
     * */
    private String indexUrl;

    /**
     * 列表页url
     * */
    private String listPageUrl;

    /**
     * 书籍url Pattern
     * */
    private String bookUrlPattern;

    /**
     * 评分 Pattern
     * */
    private String scorePattern;

    /**
     * 书名 Pattern
     * */
    private String bookNamePattern;

    /**
     * 作者 Pattern
     * */
    private String authorPattern;

    /**
     * 状态 Pattern
     * */
    private String statusPattern;

    /**
     * 类别 Pattern
     * */
    private String catPattern;


    /**
     * 更新时间 Pattern
     * */
    private String updateTimePattern;


    /**
     * 封面 Pattern
     * */
    private String picPattern;


    /**
     * 简介 Pattern
     * */
    private String introPattern;

    /**
     * 完整目录页url Pattern
     * */
    private String catalogUrlPattern;

    /**
     * 目录 Pattern
     * */
    private String catalogPattern;


}
  1. 创建多个不同网站的爬虫源配置类,这里以百书斋爬虫源为例。
/**
 * 百书斋爬虫源配置类
 * @author 11797
 */
@Slf4j
@Configuration
public class CrawlBaishuzhaiConfig {


    /**
     * 必须加此@Primary注解,不然报错,表示有多个爬虫源配置类被加载时默认加载此配置类
     * 下一个爬虫源配置类则不需要添加
     * */
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "baishuzhai.crawlsource")
    @ConditionalOnProperty(prefix = "crawl.website",name = "type",havingValue = "4")
    public BaseHtmlCrawlSource dingdianCrawlSource() {
        return new BiquCrawlSource();
    }


}
  1. 通过配置开启默认爬虫源。
#爬取的网站名称类型 1:笔趣岛 ,2:笔趣塔,3:顶点小说 ,4:百书斋,6: 笔趣窝 更多网站解析中,敬请期待
crawl:
  website:
    type: 4
  1. 定义具体的爬虫源类BiquCrawlSource,继承抽象类BaseHtmlCrawlSource,实现源网站解析方法parse()和数据更新方法update()。解析方法解析小说基础属性,生成解析日志,更新方法通过解析日志采集小说所有数据进行新书入库和老书更新,代码过多,只贴出部分解析代码。
          
                //解析第一页小说的数据
                //小说页URI正则匹配
                Pattern bookUriPatten = compile(getBookUrlPattern());
                Matcher bookUriMatcher = bookUriPatten.matcher(bookListHtml);
                boolean bookUriFind = bookUriMatcher.find();

                //小说评分正则匹配
                Pattern scorePatten = compile(getScorePattern());
                Matcher scoreMatch = scorePatten.matcher(bookListHtml);
                boolean scoreFind = scoreMatch.find();

                //小说名正则匹配
                Pattern bookNamePatten = compile(getBookNamePattern());
                Matcher bookNameMatch = bookNamePatten.matcher(bookListHtml);
                boolean bookNameFind = bookNameMatch.find();

                while (bookUriFind && scoreFind && bookNameFind) {
                    try {
                        //小说基础信息能够匹配到
                        Float score = Float.parseFloat(scoreMatch.group(1));
                        if (score < getLowestScore()) {
                            //只采集指定评分以上的小说
                            continue;
                        }

                        //获取小说基础信息,生成采集日志
                        String bookUri = bookUriMatcher.group(1);
                        String bookUrl = getIndexUrl() + "/" + bookUri + "/";
                        String bookName = bookNameMatch.group(1);
                        bookService.addBookParseLog(bookUrl, bookName, score, (byte) 10);

                    } catch (Exception e) {
                        //小说解析出现异常,不做处理,继续下一本小说的解析
                        log.error(e.getMessage(), e);
                    } finally {
                        //跳到下一本小说的解析
                        bookUriMatcher.find();
                        bookUriFind = bookUriMatcher.find();
                        scoreFind = scoreMatch.find();
                        bookNameFind = bookNameMatch.find();
                    }
                }
           
  1. 监听程序启动,注入爬虫源,循环执行源网站解析方法parse()和数据更新方法update() 。

设计原理

为了实时(和源站小说最新章节更新时间保持一致)的采集小说最新更新章节,程序需要循环不断的获取源站最新更新小说分页列表信息,如果一本小说数据全部采集完再采集下一本,而一本小说所有数据采集完是秒级甚至是分钟级的,这样采集完一页是需要一定时间的,而源站最新更新列表是不断刷新的,这样就会因为采集到的分页数据不连贯而导致小说采集遗漏,故小说解析(毫秒级,不会导致采集到的分页数据不连贯)与小说更新操作(秒级或分钟级)分开来做。

项目仓库地址

https://github.com/201206030/fiction_house