一、目的

       虽然说python很好写爬虫,并且Java也有很多爬虫框架,比如,crawler4j,WebMagic,WebCollector,我写的这个爬虫框架呢,只能解决特定的小问题,还没办法达到很好的通用性,但是通过这个项目,我们可以了解熟悉一下爬虫的整体思路,以后用第三方爬虫框架的时候也就很好上手了。

二、分析以及实现

      我们这里以古诗文网作为数据来源,进行爬虫;

      大致框架如下:

爬虫数据分析亮点 爬虫数据分析项目_解析器

 

采集:

      我们的目标是,拿下来古诗文网中的古诗数据(标题,朝代,作者,正文),然后将数据放入数据库中,或许可以直接copy页面内容,再分析存储,但是这个工作量简直是太大了,不太现实,毕竟页面中有很多我们并不需要的数据。

       那么我们就需要采用第三方工具htmlunit去进行网页信息抓取,这个过程我们称之为采集;htmlunit 是一款开源的java 页面分析工具,读取页面后,可以有效的使用htmlunit分析页面上的内容;浏览官网,我们可以看到htmlunit是这样采集网页信息的:

try (WebClient webClient=new WebClient(BrowserVersion.CHROME);){
            HtmlPage htmlPage=webClient.getPage("https://so.gushiwen.org/gushi/tangshi.aspx");
            String text=htmlPage.asText();
            System.out.println(text);
        } catch (IOException e) {
            e.printStackTrace();
        }

        我们知道浏览器既可以发送数据,也可以接收数据,htmlunit中正好就是模拟了一个浏览器来发请求接收数据;运行代码我们可以得到该网页的全部数据。

         但是我们发现会有很多警告,主要原因是:(1)HtmlUnit对JavaScript支持不是很好

                                                                             (2)HtmlUnit对CSS支持不是很好

        我们的这个页面中就有很多的js文件,所以我们需要手动禁用一下,当然,爬不同的网页,情况是不同的,可以按情况选择是否禁用。

webClient.getOptions().setJavaScriptEnabled(false);
webClient.getOptions().setCssEnabled(false);

===============================================================================================

我们先进行一次简单的爬虫,来理解一下采集,解析,清洗:

try (WebClient webClient=new WebClient(BrowserVersion.CHROME);){
            webClient.getOptions().setJavaScriptEnabled(false);
            webClient.getOptions().setCssEnabled(false);
            HtmlPage htmlPage=webClient.getPage("https://so.gushiwen.org/shiwenv_45c396367f59.aspx");
            HtmlDivision division=(HtmlDivision) htmlPage.getElementById("contson45c396367f59");
            String text=division.asText();
            System.out.println(text);
        } catch (IOException e) {
            e.printStackTrace();
        }

         如上,我们采集了一个网页,用getElementById()得到了网页的一个片段,用asTest()取得了它的文本,这个过程可以看做是解析,我们进行了简单的控制台输出,可以看做是清洗。那我们就明白了,解析就是得到我们想要的数据,清洗就是决定数据的去向。

爬虫数据分析亮点 爬虫数据分析项目_数据_02

===============================================================================================

解析:     

        我们分析,对于古诗文网这个网站,首先进去的时候是一个包含有很多超链接的页面,我们称呼为文档页面,点进去超链接之后才是我们要找的古诗,这个页面我们成为详情页,也就是刚才文档页面的子页面,那么就是说,对于抓取下来的不同的页面,我们要进行不同的解析。

       再分析,我们抓取下来的页面,实际上只有htmlpage这个信息,htmlpage有很多是我们需要的,但是我们想要页面可以告诉我们,它是不是详情页,这个页面信息htmlpage,它的子页面内容又是些什么呢,页面数据,页面的url;

      所以,我们就需要包装一个Page类,包含url(根地址+具体路径),detail,htmlpage,subpage(子页面集合),dataSet(数据集合,HashMap实现,key-value的形式存入,全程用HashMap的话,非常不清楚,我们就把它包装成数据对象,提供get,put方法,用于放数据和取数据);

      这样,我们就对我们包装过的Page进行解析:

(1)详情页解析:

          前提,非详情页则返回;

         我们要得到古诗的标题,朝代,作者,正文;

爬虫数据分析亮点 爬虫数据分析项目_解析器_03

如图,分析页面源代码,我们看到标题是h1标签里的文本内容,我们可以右击复制xPath,也可以自己写,如下

String titlePath = "//div[@class='cont']/h1/text()";
DomText titleDom = (DomText) body.getByXPath(titlePath).get(0);
String title = titleDom.asText();

类似分析,我们也可以得到朝代,作者,正文信息啦,就可以把他们存到我们包装的Page里面的DataSet里

(2)文档页解析

前提:是详情页则返回

爬虫数据分析亮点 爬虫数据分析项目_爬虫数据分析亮点_04

    这就是我们的文档页面,这里显然是拿不到我们想要的全部信息的,可以看到,每个古诗对应一个url的具体路径,我们只有把url的具体路径拿下来,加上根地址组成url,再进入这个url页面,进行详情页解析,就可以拿到我们想要的数据了;

    所以看页面源码,可以看到url的具体路径是在div下,属性为class ,值为typecont的下的a标签下,属性为href的值,那么遍历存储,我们就可以得到文档页面所有的具体路径;得到之后,new 一个subpage对象,根地址base不变,具体路径path就是刚才得到的,此时detail设置为true,然后再把这个页面加入子页面集合中。

清洗:

(1)存储到数据库

解析完数据,就已经把数据放到DataSet里面了,我们只需要从里面getdata(),然后提供一个数据源dataSource,sql语句,建立连接,然后preparedStatement,执行更新,放入数据就好了。

(2)打印到控制台

直接从DataSet里面获取数据,然后输出。

================================================================================================ 

爬虫调度器:

        由上面可以看出,解析器和清洗器都有多个,那么我们就建立一个解析器集合和清洗器集合

        要实现爬虫调度器,主要要做到以下几步:启动(采集,解析,清洗),关闭,多线程执行

爬虫数据分析亮点 爬虫数据分析项目_爬虫数据分析亮点_05

        我们建立一个文档页队列和详情页队列来放文档页和详情页。

      (1)  从文档页队列中拿Page,循环拿,直到page为空,然后对page进行解析遍历,完成之后,如果是文档页,就会得到subpage,这时,subpage只有url和detail信息,所以遍历subpage集合,把子页面放回到文档队列中去,第二次的时候,就会得到htmlpage信息,遍历解析器,进行详情页解析器解析,得到dataset,放到详情页队列中去;这整个过程就是parse()方法要实现的。

     (2)清洗器同样也是,pipeline()方法内实现,从详情页队列中拿Page,遍历清洗器集合。

     (3)给parse,pipeline加上线程执行器executorService,由单线程变成多线程;

     (4)向解析器集合中加解析器,实现addParse();向清洗器集合中加清洗器,实现addPipeline();向文档队列中加Page,实现addPage();

     (5)关闭调度器,如果调度器不为空,并且没有关闭,我们就shutdown();

然后在主方法中运行:

public static void main(String[] args) {
        final Page page=new Page("https://so.gushiwen.org","/gushi/tangshi.aspx",false);

        Crawler crawler=new Crawler();

        crawler.addParse(new DocumentParse());
        crawler.addParse(new DataPageParse());

        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setUsername("***");//加入你的数据库的用户名
        dataSource.setPassword("***");//加入你数据库的密码
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/tangshi");//按这个格式,加入你的数据库表
        crawler.addPipeline(new DatabasePipeline(dataSource));
//       crawler.addPipeline(new ConsolePipeline());
        crawler.addPage(page);
        crawler.start();

    }

这里使用Druid链接池,减少连接创建的次数和时间,控制资源的使用。(我觉得是可以类比晾衣服,你不可能晾一个衣服就取去衣柜取一次衣架,最好的肯定是把衣架多拿几个,不够用了再拿,如果衣服干了,就把衣架拿下来重复使用);   

================================================================================================