最近,项目做一个公司新闻网站,分为PC&移动端(h5),数据来源是从HSZX与huanqiu2个网站爬取,主要使用Java编写
的WebMagic作为爬虫框架,数据分为批量抓取、增量抓取,批量抓当前所有历史数据,增量需要每10分钟定时抓取一次,
由于从2个网站抓取,并且频道很多,数据量大,更新频繁;开发过程中遇到很多的坑,今天腾出时间,感觉有必要做以总结。
工具说明
- 1、WebMagic是一个简单灵活的爬虫框架。基于WebMagic,你可以快速开发出一个高效、易维护的爬虫。
- 2、jsoup是Java的一个html解析工作,解析性能很不错。
- 3、Jdiy一款超轻量的java极速开发框架,javaEE/javaSE环境均适用,便捷的数据库CRUD操作API。支持各大主流数据库。
使用到的技术,如下
1 | WebMagic作为爬虫框架、httpclient作为获取网页工具、Jsoup作为分析页面定位抓取内容、 |
项目简介
历史抓取代码(忽略,可以查看源码)
增量抓取代码,如下((忽略,可以查看源码))
> 说明:增量每10分钟执行一次,每次只抓取最新一页数据,根据增量标识(上一次第一条新闻news_id),
> 存在相同news_id或一页爬完就终止抓取。
定时抓取,配置如下
web.xml重配置监听
1
2
3
4
5
<!-- 添加:增量数据抓取监听 -->
<listener>
<listener-class>com.spider.utils.AutoRun</listener-class>
</listener>
定时代码
1 |
|
具体执行业务(举一个例子)
1 | package com.spider.huasheng.timer; |
遇到的坑
增量抓取经常遇到这2个异常,如下
抓取超时:Jsoup 获取页面内容,替换为 httpclient获取,Jsoup去解析
页面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代码、移除无用标签块