java爬虫
目标
1. 网络爬虫
1.1. 名称
1.2. 简述
- 网络爬虫是通过网页的链接地址来寻找网页,从网站某一个页面(通常是首页)开始,读取网页的内容,找到
在网页中的其它链接地址,然后通过这些链接地址寻找下一个网页,这样一直循环下去,直到把这个网站所有
的网页都抓取完为止。
- 如果把整个互联网当成一个网站,那么网络蜘蛛就可以用这个原理把互联网上所有的网页都抓取下来。
- 所以要想抓取网络上的数据,不仅需要爬虫程序还需要一个可以接受”爬虫“发回的数据并进行处理过滤的服务
器,爬虫抓取的数据量越大,对服务器的性能要求则越高。
2. 流程
- 网络爬虫是做什么的? 他的主要工作就是 跟据指定的url地址 去发送请求,获得响应, 然后解析响应 , 一方面从
响应中查找出想要查找的数据,另一方面从响应中解析出新的URL路径,然后继续访问,继续解析;继续查找需要的
数据和继续解析出新的URL路径 .
- 这就是网络爬虫主要干的工作. 下面是流程图:
通过上面的流程图 能大概了解到 网络爬虫 干了哪些活 ,根据这些 也就能设计出一个简单的网络爬虫出来.
一个简单的爬虫 必需的功能:
- 发送请求和获取响应的功能 ;
- 解析响应的功能 ;
- 对 过滤出的数据 进行存储 的功能 ;
- 对解析出来的URL路径 处理的功能 ;
2.1. 关注点
3. 分类
- 网络爬虫按照系统结构和实现技术,大致可以分为以下几种类型:
- 通用网络爬虫(General Purpose Web Crawler)
- 聚焦网络爬虫(Focused Web Crawler)
- 增量式网络爬虫(Incremental Web Crawler)
- 深层网络爬虫(Deep Web Crawler)。
- 实际的网络爬虫系统通常是几种爬虫技术相结合实现的。
4. 思路分析
- 下面我用我们的官网将跟大家一起来分析下如何实现这样的一个爬虫:
首先观察我们爬虫的起始页面是:http://www.wanho.net/a/jyxb/
- 分析页面
所有的喜报信息的URL用XPath表达式来表示就是://p[@class='main_l']/ul/li
相关数据
- 标题:用XPath表达式来表示
//p[@class='content']/h4/a/text()
- 描述:用XPath表达式来表示
//p[@class='content']/p/text()
- 图片:用XPath表达式来表示
//a/img/@src
好了,我们上面已经将在代码中需要获取的关键信息的XPath表达式都找到了,接下来就可以正式写代码来实现了
5. 代码实现
代码实现部分采用webmagic框架,因为这样比使用基本的的java网络编程要简单得多注:关于webmagic框架可以看一下面讲义
5.1. 代码结构
5.2. 程序入口
Demo.java
/**
* 程序入口
*/
public class Demo {
public static void main(String[] args) {
// 爬取开始
Spider
// 爬取过程
.create(new WanhoPageProcessor())
// 爬取结果保存
.addPipeline(new WanhoPipeline())
// 爬取的第一个页面
.addUrl("http://www.wanho.net/a/jyxb/")
// 启用的线程数
.thread(5).run();
}
}
5.3. 爬取过程
- WanhoPageProcessor.java
package net.wanho.wanhosite;
import java.util.ArrayList;
import java.util.List;
import java.util.vector;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;
public class WanhoPageProcessor implements PageProcessor {
// 部分一:抓取网站的相关配置,包括编码、抓取间隔、重试次数等
private Site site = Site
.me()
.settimeout(10000)
.setretryTimes(3)
.setSleepTime(1000)
.setCharset("UTF-8");
// 獲得站點
@Override
public Site getSite() {
return site;
}
//爬取過程
@Override
public void process(Page page) {
//获取当前页的所有喜报
List<String> list = page.getHtml().xpath("//p[@class='main_l']/ul/li").all();
//要保存喜报的集合
Vector<ArticleVo> voLst = new Vector<>();
//遍历喜报
String title;
String content;
String img;
for (String item : list) {
Html tmp = Html.create(item);
//标题
title = tmp.xpath("//p[@class='content']/h4/a/text()").toString();
//内容
content = tmp.xpath("//p[@class='content']/p/text()").toString();
//图片路径
img = tmp.xpath("//a/img/@src").toString();
//加入集合
ArticleVo vo = new ArticleVo(title, content, img);
voLst.add(vo);
}
//保存数据至page中,后续进行持久化
page.putfield("e_list", voLst);
//加载其它页
page.addTargetrequests( getOtherUrls());
}
//其它页
public List<String> getOtherUrls(){
List<String> urlLsts = new ArrayList<>();
for(int i=2;i<5;i++){
urlLsts.add("http://www.wanho.net/a/jyxb/list_15_"+i+".html");
}
return urlLsts;
}
}
5.4. 结果保存
- WanhoPipeline.java
package net.wanho.wanhosite;
import java.io.BufferedInputStream;
import java.io.Bufferedoutputstream;
import java.io.File;
import java.io.filenotfoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.printwriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Vector;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;
public class WanhoPipeline implements Pipeline {
@Override
public void process(ResultItems resultItems, Task arg1) {
// 获取抓取过程中保存的数据
Vector<ArticleVo> voLst =resultItems.get("e_list");
// 持久到文件中
PrintWriter pw=null;
try {
pw = new PrintWriter(new FileWriter("wanho.txt",true));
for(ArticleVo vo :voLst){
pw.println(vo);
pw.flush();
saveImg(vo.getImg());
}
} catch (FileNotFoundException e) {
e.printstacktrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
pw.close();
}
}
private void saveImg(String img) {
String imgUrl = "http://www.wanho.net"+img;
InputStream is = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
URL url = new URL(imgUrl);
URLConnection uc = url.openConnection();
is = uc.getInputStream();
bis = new BufferedInputStream(is);
File photoFile = new File("photo");
if(!photoFile.exists()){
photoFile.mkdirs();
}
String imgName = img.substring(img.lastindexof("/")+1);
File saveFile = new File(photoFile,imgName);
bos = new BufferedOutputStream(new FileOutputStream(saveFile));
byte[] bs = new byte[1024];
int len;
while((len=bis.read(bs))!=-1){
bos.write(bs, 0, len);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5.5. 模型对象
- ArticleVo.java
package net.wanho.wanhosite;
public class ArticleVo {
private String title;
private String content;
private String img;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public ArticleVo(String title, String content, String img) {
super();
this.title = title;
this.content = content;
this.img = img;
}
@Override
public String toString() {
return "Article [title=" + title + ", content=" + content + ", img=" + img + "]";
}
}
6. webmagic框架
我们使用的是
6.1. 简介
- webmagic的是一个无须配置、便于二次开发的爬虫框架,它提供简单灵活的API,只需少量代码即可实现一
个爬虫
- webmagic采用完全模块化的设计,功能覆盖整个爬虫的生命周期(链接提取、页面下载、内容抽取、持久
化),支持多线程抓取,分布式抓取,并支持自动重试、自定义UA/cookie等功能
- webmagic包含强大的页面抽取功能,开发者可以便捷的使用css selector、xpath和正则表达式进行链接和内
容的提取,支持多个选择器链式调用
注:官方中文文档:http://webmagic.io/docs/zh/
6.2. 实现
6.2.1. jar包下载:
可以使用maven构建依赖,如:
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>0.6.1</version>
</dependency>
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>0.6.1</version>
</dependency>
当然,也可以自行下载jar包,其地址是:http://webmagic.io/
6.2.2. 观察目标数据所处的HTML页面位置:
一般来说,如果我们需要抓取的目标数据不是通过ajax异步加载进来的话,那么我们都可以在页面的HTML源代码中的某个位置找到我们所需要的数据
注:如果数据是通过异步加载到页面中,那么一般有以下两种方式来获取数据:
- 观察页面加载完成之前请求的所有URL(F12 –> Network选项),然后找到加载数据的那些json请求,最后直接请求那些URL获取数据即可
- 模拟浏览器请求,等待一定时间之后从完全加载完成的页面中获取数据即可。这类爬虫通常需要内嵌浏览器内核,如:webmagic、phantom.js、HttpUnit等
下面我用我们的官网将跟大家一起来分析下如何实现这样的一个爬虫:
首先观察我们爬虫的起始页面是:http://www.wanho.net/a/jyxb/
- 分析页面
从上图可以看出,我们是可以直观地从首页的HTML源代码中找到我们所需要的标题、内容、图片链接等信息的。那么接下来我们可以通过哪种方式将这些目标数据提取出来呢?
其实,关于页面元素的抽取webmagic框架主要支持以下三种方式:
- XPath
- 正则表达式
- CSS选择器
当然,选择哪种方式来抽取数据需要根据具体页面具体分析。在这个例子中,很显然使用XPath来抽取数据是最方便的
因此,接下来我就直接给出我们需要抓取的数据的所在XPath路径了:
- 所有的喜报信息的URL用XPath表达式来表示就是:
//p[@class='main_l']/ul/li
- 标题:用XPath表达式来表示
//p[@class='content']/h4/a/text()
- 描述:用XPath表达式来表示
//p[@class='content']/p/text()
- 图片:用XPath表达式来表示
//a/img/@src
注:“//”表示从相对路径开始,最前面以“/”开始则表示从页面的跟路经开始;后面的两个/之间的内容表示一个元素,中括号里面的内容则表示该元素的执行属性,如:h1[@class=’entry-title’] 表示:拥有class属性为“entry-title”的h1元素
6.2.3. 页面数据抽取:
使用webmagic抽取页面数据时需要自定义一个类实现PageProcessor接口。
这个实现了PageProcessor接口的类主要功能为以下三步曲:
- 爬虫的配置:抓取页面的相关配置,包括编码、抓取间隔、重试次数等
- 页面元素的抽取:使用正则表达式或者XPath等方式来抽取页面元素
- 新链接的发现:从一个页面发现待爬取的其他目标页面的链接
package net.wanho.wanhosite;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;
public class WanhoPageProcessor implements PageProcessor {
// 部分一:抓取网站的相关配置,包括编码、抓取间隔、重试次数等
private Site site = Site
.me()
.setTimeOut(10000)
.setRetryTimes(3)
.setSleepTime(1000)
.setCharset("UTF-8");
// 獲得站點
@Override
public Site getSite() {
return site;
}
//爬取過程
@Override
public void process(Page page) {
//获取当前页的所有喜报
List<String> list = page.getHtml().xpath("//p[@class='main_l']/ul/li").all();
//要保存喜报的集合
Vector<ArticleVo> voLst = new Vector<>();
//遍历喜报
String title;
String content;
String img;
for (String item : list) {
Html tmp = Html.create(item);
//标题
title = tmp.xpath("//p[@class='content']/h4/a/text()").toString();
//内容
content = tmp.xpath("//p[@class='content']/p/text()").toString();
//图片路径
img = tmp.xpath("//a/img/@src").toString();
//加入集合
ArticleVo vo = new ArticleVo(title, content, img);
voLst.add(vo);
}
//保存数据至page中,后续进行持久化
page.putField("e_list", voLst);
//加载其它页
page.addTargetRequests( getOtherUrls());
}
//其它页
public List<String> getOtherUrls(){
List<String> urlLsts = new ArrayList<>();
for(int i=2;i<5;i++){
urlLsts.add("http://www.wanho.net/a/jyxb/list_15_"+i+".html");
}
return urlLsts;
}
}
6.2.4. 爬虫的启动与停止:
Spider是爬虫启动的入口。在启动爬虫之前,我们需要使用一个PageProcessor创建一个Spider对象,然后使用run()进行启动。同时Spider的其他组件(Downloader、Scheduler、Pipeline)都可以通过set方法来进行设置
注:更多详细参数介绍可以参考这里的官方文档:http://webmagic.io/docs/zh/posts/ch4-basic-page-processor/spider-config.html
Demo.java
package net.wanho.wanhosite;
import us.codecraft.webmagic.Spider;
public class Demo {
/**
* 程序的入口
*/
public static void main(String[] args) {
Spider
// 爬取过程
.create(new WanhoPageProcessor())
// 爬取结果保存
.addPipeline(new WanhoPipeline())
//第一个页面
.addUrl("http://www.wanho.net/a/jyxb/")
//启动三个线程
.thread(3)
//开始
.run();
}
}
6.3. 基于注解
对于抽取逻辑比较复杂的爬虫我们通常像上面那样实现PageProcessor接口自己来写页面元素的抽取逻辑。但是对于抽取逻辑比较简单的爬虫来说,这时我们可以选择在实体类上添加注解的方式来构建轻量型的爬虫
6.3.1. 实体类的构造:
package net.wanho.wanhosite;
import java.util.List;
import us.codecraft.webmagic.model.annotation.extractBy;
import us.codecraft.webmagic.model.annotation.HelpUrl;
import us.codecraft.webmagic.model.annotation.TargetUrl;
@TargetUrl(value="http://www.wanho.net/a/jyxb/\\w+.html")
@HelpUrl(value="http://www.wanho.net/a/jyxb/")
public class ArticleExtra{
/**
* 标题
*/
@ExtractBy(value="//p[@class='content']/h4/a/text()",notnull=true)
private List<String> title;
/**
* 描述
*/
@ExtractBy(value="//p[@class='content']/p/text()",notNull=true)
private List<String> content;
/**
* 图片
*/
@ExtractBy("//a/img/@src")
private List<String> img;
@Override
public String toString() {
return "ArticleExtra [title=" + title + ", content=" + content + ", img=" + img + "]";
}
public List<String> getTitle() {
return title;
}
public void setTitle(List<String> title) {
this.title = title;
}
public List<String> getContent() {
return content;
}
public void setContent(List<String> content) {
this.content = content;
}
public List<String> getImg() {
return img;
}
public void setImg(List<String> img) {
this.img = img;
}
}
从上面的代码可以看出,这里除了添加了几个注解之外,这个实体类就是一个普通的POJO,没有依赖其他任何东西。关于上面使用到的几个注解的大概含义是:
- @TargetUrl:我们需要抽取的数据所有的目标页面,其值是一个正则表达式
- @HelpUrl:为了得到目标页面的链接所需要访问的页面
- @ExtractBy:一个用于抽取元素的注解,它描述了一种抽取规则。它表示“使用这个抽取规则,将抽取到的结果保存到这个字段中”。可以使用XPath、CSS选择器、正则表达式和jsonpath等方式来抽取元素
6.3.2. 数据的持久化:
虽然在PageProcessor中我们可以实现数据的持久化(PS:基于注解的爬虫可以实现AfterExtractor 接口达到类似的目的),将爬虫抓取到的数据保存到文件、数据库、缓存等地方。但是很显然PageProcessor或者实体类主要负责的是页面元素的抽取工作,因此更好的处理方式是在另一个地方单独做数据的持久化。这个地方也就是——Pipeline
为了实现数据的持久化,我们通常需要实现Pipeline 或者PageModelPipeline接口。普通爬虫使用前一个接口,基于注解的爬虫则使用后一个接口
package net.wanho.wanhosite;
import java.util.List;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.model.OOSpider;
import us.codecraft.webmagic.pipeline.PageModelPipeline;
/**
* 自定义Pipeline以实现数据的保存
*
*/
public class WanhoPipeline2 implements PageModelPipeline<ArticleExtra> {
public void process(ArticleExtra articleExtra, Task task) {
List<String> titleLst = articleExtra.getTitle();
List<String> contentLst = articleExtra.getContent();
for(int i=0;i<titleLst.size();i++){
System.out.println("title:"+titleLst.get(i));
System.out.println("content:"+contentLst.get(i));
}
}
public static void main(String[] args) {
Site site = Site.me().setTimeOut(10000).setRetryTimes(3).setSleepTime(1000).setCharset("UTF-8");
OOSpider.create(site,new WanhoPipeline2 (), ArticleExtra.class)
.addUrl("http://www.wanho.net/a/jyxb")
.thread(5)
.run();
}
}
6.3.3. 爬虫的启动:
基于注解的爬虫,其启动类就不是Spider了,而是OOSpider类了,当然二者的使用方式类似。示例代码如下:
public static void main(String[] args) {
Site site = Site.me().setTimeOut(10000).setRetryTimes(3).setSleepTime(1000).setCharset("UTF-8");
OOSpider.create(site,new WanhoPipeline2 (), ArticleExtra.class)
.addUrl("http://www.wanho.net/a/jyxb")
.thread(5)
.run();
}
7. Jsoup介绍
jsoup 是一款 Java 的HTML 解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于JQuery的操作方法来取出和操作数据
Jsoup是Java世界的一款HTML解析工具,它支持用CSS Selector方式选择DOM元素,也可过滤HTML文本,防止XSS攻击。
下载Jsouphttp://jsoup.org/download
查看官方提供的手册:http://jsoup.org/cookbook/
7.1. 概述
Jsoup是Java世界用作html解析和过滤的不二之选。支持将html解析为DOM树、支持CSS Selector形式选择、支持html过滤,本身还附带了一个Http下载器。
Jsoup的代码相当简洁,Jsoup总共53个类,且没有任何第三方包的依赖,对比最终发行包9.8M的SAXON,实在算得上是短小精悍了。
jsoup
├── examples #样例,包括一个将html转为纯文本和一个抽取所有链接地址的例子。
├── helper #一些工具类,包括读取数据、处理连接以及字符串转换的工具
├── nodes #DOM节点定义
├── parser #解析html并转换为DOM树
├── safety #安全相关,包括白名单及html过滤
└── select #选择器,支持CSS Selector以及NodeVisitor格式的遍历
7.2. 功能
- 从一个URL,文件或字符串中解析HTML;
- 使用DOM或CSS选择器来查找、取出数据;
- 可操作HTML元素、属性、文本;
7.3. 使用
Jsoup的入口是Jsoup
类。examples包里提供了两个例子,解析html后,分别用CSS Selector以及NodeVisitor来操作Dom元素。
这里用ListLinks
里的例子来说明如何调用Jsoup:
package net.wanho.blog;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
*
* 使用JSoup 解析网页,语法使用 JS,css,Jquery 选择器语法,方便易懂
* Jsoup教程网:http://www.open-open.com/jsoup/
*
*/
public class JsoupDemo {
public static void main(String[] args) throws IOException {
String url = "https://www.oschina.net/news";
Document document = Jsoup.connect(url)
.useragent("Mozilla/5.0 (windows NT 6.1; rv:30.0) Gecko/20100101 Firefox/30.0").get();
Elements elements = document.select("#all-news p ");
Elements es = document.select("#all-news p p ");
for (Element e : es) {
System.out.println(e.select("a.title>span").text());
System.out.println(e.select("p").text());
System.out.println("-----------");
}
System.out.println(elements.size());
}
}
Jsoup使用了自己的一套DOM代码体系,这里的Elements、Element等虽然名字和概念都与Java XML
APIorg.w3c.dom
类似,但并没有代码层面的关系。就是说你想用XML的一套API来操作Jsoup的结果是办不到的,
但是正因为如此,才使得Jsoup可以抛弃xml里一些繁琐的API,使得代码更加简单。
8. XPath语法介绍
- 第一种形式
- /AAA/DDD/BBB:表示一层一层的,AAA下面DDD下面的BBB
- 第二种形式
- //BBB:表示和这个名称相同,表示只要名称是BBB,都得到
- 第三种形式
- /*:所有元素
- 第四种形式
- BBB[1]: 表示第一个BBB元素
- BBB[last()]:表示最后一个BBB元素
- 第五种形式
- //BBB[@id]:表示只要BBB元素上面有id属性,都得到
- 第六种形式
- //BBB[@id='b1']表示元素名称是BBB,在BBB上面有id属性,并且id的属性值是b1
- 例如:
- /students/student[@id='1002']
- 根students标记下student标记下属性名为id且属性值为1002的student元素
更多资深讲师相关课程资料、学习笔记请入群后向管理员免费获取,更有专业知识答疑解惑。入群即送价值499元在线课程一份。
qq群号:560819979
敲门砖(验证信息):浪淘沙
相关阅读
在历次技术革命中,一个人、一家企业,甚至一个国家,可以选择的道路只有两条:要么加入浪潮,成为前2%的人,要么观望徘徊,被淘汰------黑斑马
华为笔试题 Java 2018 搞到一份题,分享一下,如果你和我一样看不懂,就立下 flag,一起学习 Java, ✈✈✈ 第一题 题目描述 输入任意
获取CLASSPATH下的配置文件。(src下,Build Path下) ResourceBundle rb = ResourceBundle.getBundle(“service”); 获取外部配置(当
java:多线程(设置线程的优先级-setPriority())
* setPriority()设置线程的优先级 public class Demo7_Priority { public static void main(String[] args) { Thread t1=ne
今天带给大家一个比较实用的轮播图框架,banner 1.导入依赖 'com.youth.banner:banner:1.4.9' 2.添加权限 <uses-permission andro