前言
最近想学点数据分析的知识,于是想到先用爬虫爬点数据下来,后面能够利用数据做些分析处理。由于之前没有做过爬虫的相关项目,调查后了解到除了主流Python外,Java爬取数据也是挺方便的,可以利用Webmagic框架进行爬取。
项目简介
因为要把数据存下来,虽然利用Webmagic框架的一些自带的Pipeline
如JsonFilePipeline
可以很容易的将数据存到本地,但这里为了更好地学习这个框架,我选择了自己编写定制一个操作数据库的Pipeline
进行数据持久化存储。对于这种小项目,操作数据库实际用Statement编写插入语句等就行。但由于我没怎么用过Hibernate,平时用Mybatis比较多,想借此练练手,同时考虑到可能要进行一些拓展,所以就用了Hibernate来进行数据操作存储。因此项目框架选型就定为Webmagic框架+Hibernate框架。主要内容包括:实现Webmagic的PageProcessor
,定制Pipeline
,Scheduler
。
Webmagic简介
按照Webmagic官网介绍,该框架有四大组件:PageProcessor、Scheduler、Downloader和Pipeline。分别对应爬虫生命周期中的下载、处理、管理和持久化等功能。整体架构图如下:
实际上我们需要操作的只是实现PageProcessor,其他三个Scheduler、Downloader和Pipeline都有提供现成的实现类给我们选择。而Spider是一个控制引擎,在我们编写好PageProcessor、Scheduler、Downloader和Pipeline之后,就可以用Spider来启动爬数据了。在本项目中,我将实现一个PageProcessor
,定制下载图片和存储数据到数据库的Pipeline
,以及定制一个Scheduler
。
利用Hibernate插入数据
1、引入依赖
在使用Hibernate之前,需要先引入依赖,下面这几个是比较重要的依赖,包括了Webmagic框架需要引入的依赖,在pom文件中添加,将相应的包引入到项目中。
<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>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.0.12.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.43</version>
</dependency>
2、设置配置文件
接下来需要在resources/META-INF中添加一个persistence.xml,用于数据库的一些配置。文件内容如下:
<!--?xml version="1.0" encoding="UTF-8"?-->
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
<persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.username" value="root"/>
<!-- <property name="hibernate.connection.password" value=""/>-->
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=UTF-8"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
3、构建实体类
完成配置之后,需要创建实体类与数据库表相映射。这里我将爬简书数据,所以建了一张t_simple_book表,并且先简单地只设计了三个字段:id、title、user,分别对应主键、文章标题、用户名。所以对应的实体类对象如下:
@Data
@Entity
@Table(name = "t_simple_book")
public class SimpleBook {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "title")
private String title;
@Column(name = "user")
private String user;
}
并定义了一个插入数据的静态方法:
public static <T> void insertOneData(T data) {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit");
EntityManager entityManager = factory.createEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(data);
entityManager.getTransaction().commit();
entityManager.close();
factory.close();
}
到此为止,就可以往数据库里插入数据了。当然,这样做会不断地创建及关闭连接对象,影响性能。先不管,后面做连接池处理就行。
实现PageProcessor
接下来就可以实现PageProcessor,做一些爬取数据的相关工作了。简书的首页即是列表页,每次重新请求就能够重新刷出新的文章列表。所以当我们不断把首页网址加入到请求中,就能一直不断地刷出新的文章列表。而Page.addTargetRequests()
方法会将请求加入到Page
内部的List
中,并且会不断地加入到Scheduler
内部的队列中,再通过队列poll()出来进而发起请求。因此,可以采取的思路就是,爬取每一页的列表的URL加入到请求中,再把首页网址也加入,这样就能一直不断地爬取新内容了。当进入到文章详情页后,就可以对页面元素进行分析,提取需要的内容出来了。这里我只提取了文章标题、作者、以及文章中所有出现的图片的URL。
定制Pipeline
我写了两个Pipeline
,一个用于下载图片,另外一个用于将文章的一些数据写入到数据库中。Pipeline
实际上就是对PageProcessor
的再加工处理。所以实际上你在Pipeline
中完成的工作,在PageProcessor
中都可以完成。但由于两者是对应爬虫的不同阶段,分开来好一些。正如,我所写的两个Pipeline
实际上用一个Pipeline
处理就可以,但我认为它们完成的是不同的工作,分开写结构会比较清晰。于是我就采取了分别写两个Pipeline
的方式。
定制Scheduler
Scheduler
在爬虫阶段主要的工作是能够不断把请求添加到队列中,再不断把队列中的请求poll出来。在写项目的时候,有时候为了测试一些结果,需要启动程序,但启动之后就会不断地爬取下去,除非终止程序。那么我就想能不能做到让程序自己达到一定条件后就自动停止。所以我查看了源码,而要知道程序最终是怎么停止的,就需要看下Spider.run()
方法。源码如下:
public void run() {
this.checkRunningStat();
this.initComponent();
this.logger.info("Spider {} started!", this.getUUID());
while(!Thread.currentThread().isInterrupted() && this.stat.get() == 1) {
final Request request = this.scheduler.poll(this);
if (request == null) {
if (this.threadPool.getThreadAlive() == 0 && this.exitWhenComplete) {
break;
}
this.waitNewUrl();
} else {
this.threadPool.execute(new Runnable() {
public void run() {
try {
Spider.this.processRequest(request);
Spider.this.onSuccess(request);
} catch (Exception var5) {
Spider.this.onError(request);
Spider.this.logger.error("process request " + request + " error", var5);
} finally {
Spider.this.pageCount.incrementAndGet();
Spider.this.signalNewUrl();
}
}
});
}
}
this.stat.set(2);
if (this.destroyWhenExit) {
this.close();
}
this.logger.info("Spider {} closed! {} pages downloaded.", this.getUUID(), this.pageCount.get());
}
可以看到,在while循环里面,通过Scheduler
不断地poll
出request
,当request
不为null时,就会启动线程去执行它。而当request
一直为null时,活跃线程执行完任务后就能够退出循环了。所以我想到了写一个计数的Scheduler
,可以统计下载的页面,当达到指定值时,poll方法就一直返回null,这样就能退出了。这样我就能指定只下载一定量的页面就行了。同样的思路,也可以写一个计时的Scheduler
,给定一个时间段,当达到条件就退出。下面代码即是我实现的计数Scheduler
,仿照的是自带的QueueScheduler
进行实现。
@ThreadSafe
public class CountableScheduler extends DuplicateRemovedScheduler implements MonitorableScheduler {
private BlockingQueue<Request> queue = new LinkedBlockingQueue();
private int count = -1;
public CountableScheduler() {
}
public void pushWhenNoDuplicate(Request request, Task task) {
this.queue.add(request);
}
@Override
public Request poll(Task task) {
if (count == -1) {
return (Request)this.queue.poll();
}
if (count > 0) {
count--;
return (Request)this.queue.poll();
}else {
this.queue.clear();
return null;
}
// return (Request)this.queue.poll();
}
@Override
public int getLeftRequestsCount(Task task) {
return this.queue.size();
}
@Override
public int getTotalRequestsCount(Task task) {
return this.getDuplicateRemover().getTotalRequestsCount(task);
}
public CountableScheduler setCount(int count) {
if(count <= 0) {
return this;
}
this.count = count + 1;
return this;
}
}
后语
本次的爬虫经历就介绍到这里,简单地记录下我自己对这个框架的一个浅显的学习,后面我想利用Elasticsearch+Webmagic做一个对大量数据做分析处理的项目。