• 软件工程实践2020_个人作业 —— 技术博客(webmagic的使用说明)


    Part.01 Webmagic介绍

    webmagic是一个开源的Java垂直爬虫框架,目标是简化爬虫的开发流程,让开发者专注于逻辑功能的开发
    WebMagic项目代码分为核心和扩展两部分

    • 核心部分(webmagic-core)是一个精简的、模块化的爬虫实现,而扩展部分则包括一些便利的、实用性的功能。WebMagic的架构设计参照了Scrapy,目标是尽量的模块化,并体现爬虫的功能特点。这部分提供非常简单、灵活的API,在基本不改变开发模式的情况下,编写一个爬虫
    • 扩展部分(webmagic-extension)提供一些便捷的功能,例如注解模式编写爬虫等。同时内置了一些常用的组件,便于爬虫开发

    Part.02 Webmagic设计原理

    WebMagic的结构分为Downloader、PageProcessor、Scheduler、Pipeline四大组件,并由Spider将它们彼此组织起来。
    WebMagic总体架构图如下:

    技术

    WebMagic的四个组件

    • Downloader:Downloader负责从互联网上下载页面,以便后续处理。WebMagic默认使用了Apache HttpClient作为下载工具。
    • PageProcessor:PageProcessor负责解析页面,抽取有用信息,以及发现新的链接。WebMagic使用Jsoup作为HTML解析工具,并基于其开发了解析XPath的工具Xsoup。在这四个组件中,PageProcessor对于每个站点每个页面都不一样,是需要使用者定制的部分。
    • Scheduler:Scheduler负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重。也支持使用Redis进行分布式管理。除非项目有一些特殊的分布式需求,否则无需自己定制Scheduler。
    • Pipeline:Pipeline负责抽取结果的处理,包括计算、持久化到文件、数据库等。WebMagic默认提供了“输出到控制台”和“保存到文件”两种结果处理方案。Pipeline定义了结果保存的方式,如果你要保存到指定数据库,则需要编写对应的Pipeline。对于一类需求一般只需编写一个Pipeline。

    用于数据流转的对象

    • Request:Request是对URL地址的一层封装,一个Request对应一个URL地址。它是PageProcessor与Downloader交互的载体,也是PageProcessor控制Downloader唯一方式。除了URL本身外,它还包含一个Key-Value结构的字段extra。你可以在extra中保存一些特殊的属性,然后在其他地方读取,以完成不同的功能。例如附加上一个页面的一些信息等。
    • Page:Page代表了从Downloader下载到的一个页面——可能是HTML,也可能是JSON或者其他文本格式的内容。Page是WebMagic抽取过程的核心对象,它提供一些方法可供抽取、结果保存等。
    • ResultItems:ResultItems相当于一个Map,它保存PageProcessor处理的结果,供Pipeline使用。它的API与Map很类似,值得注意的是它有一个字段skip,若设置为true,则不应被Pipeline处理。

    Part.03 Webmagic 实例(爬取 笔趣阁&bilibili的数据)

    webmagic使用maven管理依赖,在项目中添加对应的依赖即可使用webmagic

            <!-- web magic -->
            <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>
    

    WebMagic 使用slf4j-log4j12作为slf4j的实现.如果你自己定制了slf4j的实现,请在项目中去掉此依赖

    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
    

    核心代码

    • BilibiliReptile.java
    package com.reptile.bilibili;
    
    import com.mysql.dao.BilibiliDao;
    import com.mysql.entity.Bilibili;
    
    import com.mysql.pipeline.MysqlPipelineBilibili;
    import com.tool.SplitJson;
    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;
    
    
    import java.sql.SQLException;
    
    import static com.reptile.json.GetHttpInterface.GetHttpInterface;
    
    public class BilibiliReptile implements PageProcessor{
    
        //设置拼接的url变量
        //爬取av号从1至1000000
        private static int start =1;
        private static int end =1000000;
    
        //设置网站相关配置
        //重试次数和抓取间隔
        private Site site = Site.me().setRetryTimes(5).setSleepTime(0).setUserAgent("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36");
    
        public synchronized void process(Page page) {
    
            //视频标题
            page.putField("title",page.getHtml().xpath("//h1[@class='video-title']/span/text()").get());
            //如果title为空则跳过
            if (page.getResultItems().get("title") == null) {
                page.setSkip(true);
            }
    
            //标题图
            page.putField("image",page.getHtml().xpath("/html/head/meta[10]").get());
    
            //up
            page.putField("up",page.getHtml().xpath("//div[@class='name']/a[1]/text()").get());
    
            //简介
            page.putField("info",page.getHtml().xpath("//div[@class='u-info']/div[2]/text()").get());
    
            //分p
            page.putField("part",page.getHtml().xpath("//*[@id="multi_page"]/div[1]/div/span/text()").get());
    
            //时间戳
            page.putField("date",page.getHtml().xpath("//div/time/text()").get());
        }
    
        public Site getSite() {
            // TODO Auto-generated method stub
            return site;
        }
    
        public static void main(String[] args) throws SQLException {
    
            int id = 1;
    
            BilibiliDao bilidao = new BilibiliDao();
            Bilibili bilibili = new Bilibili();
    
            SplitJson sj = new SplitJson();
    
            while (start<end) {
    
                Spider.create(new BilibiliReptile()).addUrl("https://www.bilibili.com/video/av" + start + "/")
                        //输出到控制台
                        .addPipeline(new ConsolePipeline())
                        //传输到数据库
                       // .addPipeline(new MysqlPipelineBilibili())
                        //开启5个线程抓取
                        .thread(5)
                        //启动爬虫
                        .run();
    
                String str = GetHttpInterface("https://api.bilibili.com/x/web-interface/archive/stat?aid=" + start);
    
                if((sj.splitCode(str)).equals("0"))
                {
                    bilibili.setId(id);
                    bilibili.setPlay(sj.splitView(str));
                    bilibili.setBarrage(sj.splitDanmaku(str));
                    bilidao.addData(bilibili);
                    System.out.println(str);
                    System.out.println("view:" + sj.splitView(str));
                    System.out.println("danmuke:" + sj.splitView(str));
                    id++;
                }
    
                start++;
    
            }
        }
    }
    
    • BiQuGeReptile.java
    package com.reptile.biquge;
    
    import com.mysql.pipeline.MysqlPipelineBiQuGe;
    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;
    
    import java.util.List;
    
    public class BiQuGeReptile implements PageProcessor {
    
        //regex of URL:http://www.xbiquge.la/
        public static final String FIRST_URL = "http://www\.xbiquge\.la/\w+";
        public static final String HELP_URL = "/\d+/\d+/";
        public static final String TARGET_URL = "/\d+/\d+/\d+\.html/";
    
        private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);
    
        public Site getSite() {
            // TODO Auto-generated method stub
            return site;
        }
    
        public void process(Page page) {
    
    
            if(page.getUrl().regex(FIRST_URL).match()){
    
                List<String> urls = page.getHtml().links().regex(HELP_URL).all();
                page.addTargetRequests(urls);
    
                //标题
                page.putField("title",page.getHtml().xpath("//div[@id='info']/h1/text()").get());
                //如果title为空则跳过
                if (page.getResultItems().get("title") == null) {
                    page.setSkip(true);
                }
    
                //作者
                page.putField("author",page.getHtml().xpath("//div[@id='info']/p/text()").get());
    
                //简介
                page.putField("info",page.getHtml().xpath("//div[@id='intro']/p[2]/text()").get());
    
                //首图url
                page.putField("image",page.getHtml().xpath("//div[@id='fmimg']/img").get());
    
                //下一深度的网页爬取章节和内容
                if(page.getUrl().regex(HELP_URL).match()){
    
                    List<String> links = page.getHtml().links().regex(TARGET_URL).all();
                    page.addTargetRequests(links);
    
                    //章节
                    page.putField("chapter", page.getHtml().xpath("//div[@class='bookname']/h1/text()").get());
    
                    //内容
                    page.putField("content", page.getHtml().xpath("//div[@id='content']/text()").get());
            }
            }
        }
    
        public static void main(String[] args){
            Spider.create(new BiQuGeReptile()).addUrl("http://www.xbiquge.la/xiaoshuodaquan/")
                    //输出到控制台
                    .addPipeline(new ConsolePipeline())
                    //传输到数据库
                   // .addPipeline(new MysqlPipelineBiQuGe())
                    //开启5个线程抓取
                    .thread(5)
                    //启动爬虫
                    .run();
        }
    
    }
    

    注意事项

    在0.7.3版本中,爬取只支持TLS1.2的https站点的时候会报错:

    javax.net.ssl.SSLException: Received fatal alert: protocol_version
    

    解决办法:https://github.com/code4craft/webmagic/issues/701


    Part.04 Webmagic 拓展

    URL 去重

    Scheduler是WebMagic中进行 URL 管理的组件。一般来说,Scheduler包括两个作用:

        对待抓取的URL队列进行管理。
        对已抓取的URL进行去重。
    

    Scheduler的内部实现进行了重构,去重部分被单独抽象成了一个接口:DuplicateRemover,从而可以为同一个Scheduler选择不同的去重方式,以适应不同的需要,目前提供了三种去重方式。

        HashSet
            使用 java 中 HashSet 不能重复的特点去重。占用内存大,性能低
        Redis 去重
            使用 Redis 的 set 进行去重。优点是速度快,而且不会占用爬虫服务器的资源。可以处理更大数据量的数据爬取;缺点是需要 redis 服务器,增加开发和使用成本
        布隆过滤器(BloomFilter)
            优点是占用内存比 HashSet 小的多,也适合大数据量的去重操作。
    

    布隆过滤器的使用实例:

    @Scheduled(initialDelay = 1000, fixedDelay = 60 * 1000 * 60 * 12)
        public void start() {
            Spider.create(new BdProcessor())
                    .addUrl(URL)
                    .thread(10)
                    // 设置布隆过滤器去重操作(默认使用HashSet来进行去重,占用内存较大;使用BloomFilter来进行去重,占用内存较小,但是可能漏抓页面)
                    .setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(10000000)))
                    .addPipeline(dbPipeline)
                    .run();
        }
    

    网页去重

    指纹码对比

    最常见的去重方案是生成文档的指纹门。例如对一篇文章进行 MD5 加密生成一个字符串,我们可以认为这是文章的指纹码,再和其他的文章指纹码对比,一致则说明文章重复。

    但是这种方式是完全一致则是重复的,如果文章只是多了几个标点符号,那仍旧被认为是重复的,这种方式并不合理。

    BloomFilter

    这种方式就是我们之前对 url 进行去重的方式,使用在这里的话,也是对文章进行计算得到一个数,再进行对比,缺点和方法 1 是一样的,如果只有一点点不一样,也会认为不重复,这种方式不合理。

    KMP 算法

    KMP 算法是一种改进的字符串匹配算法。KMP 算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。能够找到两个文章有哪些是一-样的,哪些不一样

    这种方式能够解决前面两个方式的“只要一点不一样就是不重复”的问题。但是它的时空复杂度太高了,不适合大数据量的重复比对

    SimHash (主要)

    Google 的 simhash 算法产生的签名,可以满足上述要求。这个算法并不深奥,比较容易理解。这种算法也是目前 Google 搜索引擎所目前所使用的网页去重算法

        分词,把需要判断文本分词形成这个文章的特征单词。
        hash,通过 hash 算法把每个词变成 hash 值,比如“美国”通过 hash 算法计算为 100101,“51 区”通过 hash 算法计算为 101011。这样我们的字符串就变成了一串串数字。
        加权,通过 2 步骤的 hash 生成结果,需要按照单词的权重形成加权数字串,。“美国”的 hash 值为“100101”,通过加权计算为“4-4-44-44”。“51 区”计算为‘“5-55-555”。
        合并,把上面各个单词算出来的序列值累加,变成只有一一个序列串。。“美国”的“4-4-44-44”,“51 区”的“5-55-555”。
    

    代理的使用

    有些网站不允许爬虫进行数据爬取,因为会加大服务器的压力。其中一种最有效的方式是通过 ip+时间进行鉴别,因为正常人不可能短时间开启太多的页面,发起太多的请求。

    我们使用的 WebMagic 可以很方便的设置爬取数据的时间(参考第二天的的 3.1. 爬虫的配置、启动和终止)。但是这样会大大降低我们 J 爬取数据的效率,如果不小心 ip 被禁了,会让我们无法爬去数据,那么我们就有必要使用代理服务器来爬取数据。

    代理 L(英语:Proxy),也称网络代理,是一-种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。

    提供代理服务的电脑系统或其它类型的网络终端称为代理服务器(英文:Proxy. Server)。一个完整的代理请求过程为:客户端首先与代理服务器创建连接,接着根据代理服务器所使用的代理协议,请求对目标服务器创建连接、或者获得目标服务器的指定资源。

    我们就需要知道代理服务器在哪里(ip 和端口号)才可以使用。网上有很多代理服务器的提供商,但是大多是免费的不好用,付费的还行。推荐个免费的服务网站:

    配置代理

    WebMagic的代理API ProxyProvider。因为相对于 Site 的“配置”,ProxyProvider定位更多是一个“组件”,所以代理不再从Site设置,而是由HttpClientDownloader设置。

    • 设置代理:HttpClientDownloader.setProxyProvider(ProxyProvider proxyProvider)

    ProxyProvider有一个默认实现:SimpleProxyProvider。它是一个基于简单Round-Robin的、没有失败检查的ProxyProvider。可以配置任意个候选代理,每次会按顺序挑选一个代理使用。它适合用在自己搭建的比较稳定的代理的场景。

    代理示例:

    设置单一的普通HTTP代理为101.101.101.101的8888端口,并设置密码为"username","password"

        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
        httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy("101.101.101.101",8888,"username","password")));
        spider.setDownloader(httpClientDownloader);
    
        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
        httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(
        new Proxy("101.101.101.101",8888)
        ,new Proxy("102.102.102.102",8888)));
    

    Part.05 总结

    • 关于Webmagic使用说明的总结

      • Webmagic属于可快速上手的简易爬虫框架,在阅读官方文档后可快速上手开发,主要难点在于对于xpath(会正则的同学会很快就上手)的学习以及对于部分网站需要进行的cookie验证、代理以及登陆验证时有一定难度,对于部分动态渲染的前端页面可通过Chrome内核内嵌代码渲染的方式解决

    Part.06 参考文献

    PS:确实觉得写的很好,转给大家分享,文中提到的一些操作自己打算日后试试


  • 相关阅读:
    java入门学习(二)
    java入门学习(一)
    python3之数据类型
    pip基础用法
    python中的序列化与反序列化
    python装饰器
    python WEB接口自动化测试之requests库详解
    QQ发送邮件实例
    获取当前目录下最新的文件
    The Zen of Python
  • 原文地址:https://www.cnblogs.com/theTuring/p/13137719.html
Copyright © 2020-2023  润新知