Java爬虫框架WebMagic的使用总结

最近,项目做一个公司新闻网站,分为PC&移动端(h5),数据来源是从HSZX与huanqiu2个网站爬取,主要使用Java编写
的WebMagic作为爬虫框架,数据分为批量抓取、增量抓取,批量抓当前所有历史数据,增量需要每10分钟定时抓取一次,
由于从2个网站抓取,并且频道很多,数据量大,更新频繁;开发过程中遇到很多的坑,今天腾出时间,感觉有必要做以总结。

工具说明

使用到的技术,如下

1
2
WebMagic作为爬虫框架、httpclient作为获取网页工具、Jsoup作为分析页面定位抓取内容、
ExecutorService线程池作为定时增量抓取、Jdiy作为持久层框架

项目简介

历史抓取代码(忽略,可以查看源码)

增量抓取代码,如下((忽略,可以查看源码))

> 说明:增量每10分钟执行一次,每次只抓取最新一页数据,根据增量标识(上一次第一条新闻news_id),
> 存在相同news_id或一页爬完就终止抓取。

定时抓取,配置如下

web.xml重配置监听

1
2
3
4
5

<!-- 添加:增量数据抓取监听 -->
<listener>
<listener-class>com.spider.utils.AutoRun</listener-class>
</listener>

定时代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

package com.spider.utils;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.spider.huanqiu.timer.HQJob1;
import com.spider.huanqiu.timer.HQJob2;
import com.spider.huanqiu.timer.HQJob3;
import com.spider.huanqiu.timer.HQJob4;
import com.spider.huasheng.timer.HSJob1;
import com.spider.huasheng.timer.HSJob2;
/**
* 描 述:监听增量抓取Job
* 创建时间:2016-11-4
* @author Jibaole
*/
public class AutoRun implements ServletContextListener {

public void contextInitialized(ServletContextEvent event) {
ScheduledExecutorService scheduExec = Executors.newScheduledThreadPool(6);
/*
* 这里开始循环执行 HSJob()方法了
* scheduleAtFixedRate(param1, param2,param3)这个函数的三个参数的意思分别是:
* param1:你要执行的方法;param2:延迟执行的时间,单位毫秒;param3:循环间隔时间,单位毫秒
*/
scheduExec.scheduleAtFixedRate(new HSJob1(), 1*1000*60,1000*60*10,TimeUnit.MILLISECONDS); //延迟1分钟,设置没10分钟执行一次
scheduExec.scheduleAtFixedRate(new HSJob2(), 3*1000*60,1000*60*10,TimeUnit.MILLISECONDS); //延迟3分钟,设置没10分钟执行一次

scheduExec.scheduleAtFixedRate(new HQJob1(), 5*1000*60,1000*60*10,TimeUnit.MILLISECONDS); //延迟5分钟,设置没10分钟执行一次
scheduExec.scheduleAtFixedRate(new HQJob2(), 7*1000*60,1000*60*10,TimeUnit.MILLISECONDS); //延迟7分钟,设置没10分钟执行一次
scheduExec.scheduleAtFixedRate(new HQJob3(), 9*1000*60,1000*60*14,TimeUnit.MILLISECONDS); //延迟9分钟,设置没10分钟执行一次
scheduExec.scheduleAtFixedRate(new HQJob4(), 11*1000*60,1000*60*10,TimeUnit.MILLISECONDS); //延迟11分钟,设置没10分钟执行一次
}
public void contextDestroyed(ServletContextEvent event) {
System.out.println("=======timer销毁==========");
//timer.cancel();
}
}

具体执行业务(举一个例子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.spider.huasheng.timer;

import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;
import com.spider.huasheng.task.HSTaskDao;
import com.spider.huasheng.task.HSTaskDao1;
import com.spider.huasheng.task.HSTaskDao2;

/**
* 描 述:国际、社会、国内、评论等频道定时任务
* 创建时间:2016-11-9
* @author Jibaole
*/
public class HSJob1 implements Runnable{
@Override
public void run() {
System.out.println("======>>>开始:xxx-任务1====");
try {
runNews();
runNews1();
runNews2();
} catch (Throwable t) {
System.out.println("Error");
}
System.out.println("======xxx-任务1>>>结束!!!====");
}
/**
* 抓取-新闻 频道列表
*/
public void runNews(){
List<String> strList=new ArrayList<String>();
/**##############>>>16、国际<<<##################*/
//国际视野
strList.add("http://xxx/class/2199.html?pindao=国际");

/**##############>>>17、社会<<<##################*/
//社会
strList.add("http://xxx/class/2200.html?pindao=社会");

/**##############>>>18、国内<<<##################*/
//国内动态
strList.add("http://xxx/class/1922.html?pindao=国内");
HQNewsTaskDao.runNewsList(strList);
}

/**
* 抓取-新闻 频道列表
*/
public void runNews1(){
List<String> strList=new ArrayList<String>();
/**##############>>>19、评论<<<##################*/
//华声视点
strList.add("http://xxx/class/709.html?pindao=评论");
//财经观察
strList.add("http://xxx/class/2557.html?pindao=评论");
/**##############>>>20、军事<<<##################*/
//军事
strList.add("http://xxx/class/2201.html?pindao=军事");
HQNewsTaskDao.runNewsList(strList);
}
/**
* 抓取-新闻 频道列表
*/
public void runNews2(){
List<String> strList=new ArrayList<String>();
/**##############>>>24、财经<<<##################*/
//财讯
strList.add("http://xxx/class/2353.html?pindao=财经");
//经济观察
strList.add("http://xxx/class/2348.html?pindao=财经");
/**##############>>>30、人文<<<##################*/
//历史上的今天
strList.add("http://xxx/class/1313.html?pindao=人文");
//正史风云
strList.add("http://xxx/class/1362.html?pindao=人文");
HSTaskDao2.runNewsList(strList);
}
}

遇到的坑

增量抓取经常遇到这2个异常,如下

抓取超时:Jsoup 获取页面内容,替换为 httpclient获取,Jsoup去解析
页面gzip异常(这个问题特别坑,导致历史、增量抓取数据严重缺失,线上一直有问题)
gzip异常

  • 解决方案:

    增加:Site..addHeader(“Accept-Encoding”, “/“)

    这个是WebMagic的框架源码有点小Bug,如果没有设置Header,默认页面Accept-Encoding为:gzip

    源码截图

定时抓取

> 由ScheduledExecutorService多线程并行执行任务,替换Timer单线程串行

>  原方式代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.spider.utils;

import java.util.Timer;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.spider.huanqiu.timer.HQJob1;
import com.spider.huanqiu.timer.HQJob2;
import com.spider.huanqiu.timer.HQJob3;
import com.spider.huanqiu.timer.HQJob4;
import com.spider.huasheng.timer.HSJob1;
import com.spider.huasheng.timer.HSJob2;
/**
* 描 述:监听增量抓取Job
* 创建时间:2016-11-4
* @author Jibaole
*/
public class AutoRun implements ServletContextListener {
//HS-job
private Timer hsTimer1 = null;
private Timer hsTimer2 = null;
//HQZX-job
private Timer hqTimer1 = null;
private Timer hqTimer2 = null;
private Timer hqTimer3 = null;
private Timer hqTimer4 = null;

public void contextInitialized(ServletContextEvent event) {
hsTimer1 = new Timer(true);
hsTimer2 = new Timer(true);

hqTimer1 = new Timer(true);
hqTimer2 = new Timer(true);
hqTimer3 = new Timer(true);
hqTimer4 = new Timer(true);
/*
* 这里开始循环执行 HSJob()方法了
* schedule(param1, param2,param3)这个函数的三个参数的意思分别是:
* param1:你要执行的方法;param2:延迟执行的时间,单位毫秒;param3:循环间隔时间,单位毫秒
*/
hsTimer1.scheduleAtFixedRate(new HSJob1(), 1*1000*60,1000*60*10); //延迟1分钟,设置没10分钟执行一次
hsTimer2.scheduleAtFixedRate(new HSJob2(), 3*1000*60,1000*60*10); //延迟3分钟,设置没10分钟执行一次

hqTimer1.scheduleAtFixedRate(new HQJob1(), 5*1000*60,1000*60*10); //延迟5分钟,设置没10分钟执行一次
hqTimer2.scheduleAtFixedRate(new HQJob2(), 7*1000*60,1000*60*10); //延迟7分钟,设置没10分钟执行一次
hqTimer3.scheduleAtFixedRate(new HQJob3(), 9*1000*60,1000*60*10); //延迟9分钟,设置没10分钟执行一次
hqTimer4.scheduleAtFixedRate(new HQJob4(), 11*1000*60,1000*60*10); //延迟11分钟,设置没10分钟执行一次
}
public void contextDestroyed(ServletContextEvent event) {
System.out.println("=======timer销毁==========");
//timer.cancel();
}
}

定时多个任务时,使用多线程,遇到某个线程抛异常终止任务

> 解决方案:在多线程run()方法里面,增加try{}catch{}

通过HttpClient定时获取页面内容时,页面缓存,抓不到最新内容

> 解决方案:在工具类请求URL地址后面增加:url+"?date=" + new Date().getTime()

一些方面的处理

页面抓取规则调整

>  先抓列表,在抓内容;改为 抓取列表的同时,需要获取内容详情

保存数据方式作调整

>  先抓取标题等概要信息,保存数据库,然后,更新内容信息,根据业务需求再删除一些非来源文章(版权问题);
改为:直接控制来源,得到完整数据,再做批量保存;

页面有一个不想要的内容,处理方法

>  注释、JS代码、移除无用标签块
评论