基于Jsoup实现的简单网络爬虫

时间:2022-10-31 08:00:15

之前是完全不会爬虫的,但是新项目中需要从网页上爬一大堆的数据,所以就花了一天时间学习了下。主题部分还是很简单的。
* 既然想要写博文,那我就要写的细致点,对自己对读者都是一种负责!


什么是爬虫?

我所理解的爬虫就是从互联网上获取Url,顺着Url一个一个的去访问页面
一个页面会有很多的链接,对于每个链接可以判断是否使我们想要的,再对子链接进行操作、访问等等。

for each 链接 in 当前网页所有的链接
{
if(如果本链接是我们想要的 || 这个链接从未访问过)
{
处理对本链接
把本链接设置为已访问
}
}

对于爬虫:
1. 首先你需要给定一个种子链接。
2. 在种子链接当中寻找你要的子链接。
3. 通过子链接访问其他页面,在页面中获取你所需要的内容。

这当中涉及到的内容有:

  1. Http
  2. 内容解析器

其主要的过程是这样的:

  1. 取一个种子URL,比如www.oschina.net

  2. 通过httpclient请求获取页面资源(获取的页面资源其中肯定包含了其他的URL,可以作为下一个种子循环使用)

  3. 通过正则或者jsoup解析出想要的内容(解析出其他的URL链接,同时获取本页面的所有图片,这都是可以的)

  4. 使用3获取的下一个种子URL,重复1


我们先来看下如何用 HttpClient 获取到整个页面:
在使用 HttpClient 之前,你需要先导入 HttpClint.jar 包

在HttpClient jar更新之后,使用的实例都是 CloseableHttpClient 了所以我们也用它

 public static String get(String url){
String result = "";
try {
//获取httpclient实例
CloseableHttpClient httpclient = HttpClients.createDefault();
//获取方法实例。GET
HttpGet httpGet = new HttpGet(url);
//执行方法得到响应
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
//如果正确执行而且返回值正确,即可解析
if (response != null
&& response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
//从输入流中解析结果
result = readResponse(entity, "utf-8");
}
} finally {
httpclient.close();
response.close();
}
}catch (Exception e){
e.printStackTrace();
}
return result;
}

/**
* stream读取内容,可以传入字符格式
* @param resEntity
* @param charset
* @return
*/

private static String readResponse(HttpEntity resEntity, String charset) {
StringBuffer res = new StringBuffer();
BufferedReader reader = null;
try {
if (resEntity == null) {
return null;
}

reader = new BufferedReader(new InputStreamReader(
resEntity.getContent(), charset));
String line = null;

while ((line = reader.readLine()) != null) {
res.append(line);
}

} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
}
}
return res.toString();
}

通过这种方法,获取到的是整个页面的资源,其中包含了Html的代码(www.baidu.com):

<!DOCTYPE html>
<!--STATUS OK-->
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta content="always" name="referrer">
<link rel="stylesheet" type="text/css" href="http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css">
<title>百度一下,你就知道</title>
</head>
<body link="#0000cc">
<div id="wrapper">
<div id="head">
<div class="head_wrapper">
<div class="s_form">
<div class="s_form_wrapper">
<div id="lg">
<img hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270" height="129">
</div>
<form id="form" name="f" action="//www.baidu.com/s" class="fm">
<input type="hidden" name="bdorz_come" value="1">
<input type="hidden" name="ie" value="utf-8">
<input type="hidden" name="f" value="8">
<input type="hidden" name="rsv_bp" value="1">
<input type="hidden" name="rsv_idx" value="1">
<input type="hidden" name="tn" value="baidu">
<span class="bg s_ipt_wr"><input id="kw" name="wd" class="s_ipt" value="" maxlength="255" autocomplete="off" autofocus></span>
<span class="bg s_btn_wr"><input type="submit" id="su" value="百度一下" class="bg s_btn"></span>
</form>
</div>
</div>
<div id="u1">
<a href="http://news.baidu.com" name="tj_trnews" class="mnav">新闻</a>
<a href="http://www.hao123.com" name="tj_trhao123" class="mnav">hao123</a>
<a href="http://map.baidu.com" name="tj_trmap" class="mnav">地图</a>
<a href="http://v.baidu.com" name="tj_trvideo" class="mnav">视频</a>
<a href="http://tieba.baidu.com" name="tj_trtieba" class="mnav">贴吧</a>
<noscript>
<a href="http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1" name="tj_login" class="lb">登录</a>
</noscript>
<script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script>
<a href="//www.baidu.com/more/" name="tj_briicon" class="bri" style="display: block;">更多产品</a>
</div>
</div>
</div>
<div id="ftCon">
<div id="ftConw">
<p id="lh"> <a href="http://home.baidu.com">关于百度</a> <a href="http://ir.baidu.com">About Baidu</a> </p>
<p id="cp">?2017&nbsp;Baidu&nbsp;<a href="http://www.baidu.com/duty/">使用百度前必读</a>&nbsp; <a href="http://jianyi.baidu.com/" class="cp-feedback">意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src="//www.baidu.com/img/gs.gif"> </p>
</div>
</div>
</div>
</body>
</html>

其中包含了,子链接,文本,图片。这不是我们要的,需要过滤。


正则表达式过滤方法

相信大家都知道正则表达式,在Java中两个类 Pattern 和 Matcher 都是为它服务的。

        String regexStr = "[abdh]e";
String targetStr = "hello world";
//获取Pattern对象
Pattern pattern = Pattern.compile(regexStr);
// 定义一个matcher用来做匹配
Matcher matcher = pattern.matcher(targetStr);
if (matcher.find()) {
System.out.println(matcher.group());
}

我只是提一下有这个东西,它并不好用。。。所以我们需要用的是Jsoup


Jsoup

使用Jsoup的前提是需要导入对应的包
Jsoup当中已经封装了对应的方法来获取网页当中的内容:

        String url = "http://www.baidu.com";
org.jsoup.nodes.Document doc = Jsoup.connect(url).get();

是不是很方便。。。。都在Document中了
而且Jsoup定义了检索的方法,可以遍历文档中的内容

比如你需要的是子链接:

        String url = "http://www.baidu.com";
org.jsoup.nodes.Document doc = Jsoup.connect(url).get();
Elements links = doc.select("a[href]");
System.out.println(links.size());
for (org.jsoup.nodes.Element link : links) {
System.out.println(link.attr("abs:href") + " " + link.text());
}

只需要这么写,在links中得到的就全部都是子链接的筛选结果,并且通过遍历的方式展示出来(我从其他网址抓的内容):


http://www.haodou.com/recipe/839235/ 猪蹄花生煲
http://www.haodou.com/recipe/287246/ 银耳陈皮生姜炖梨
http://www.haodou.com/recipe/160157/ 心太软
http://www.haodou.com/recipe/260392/ 韩式蜂蜜大枣茶
http://www.haodou.com/recipe/296090/ 红枣莲子银耳羹
http://www.haodou.com/recipe/3717/ 蜂蜜柚子茶
http://www.haodou.com/recipe/271602/ 姜枣桂圆汤
http://www.haodou.com/recipe/273075/ 红枣当归粥
http://www.haodou.com/recipe/267272/ 米酒南瓜红枣汤

上面几个样例是在我安卓大作业中使用的内容,从种子链接中选择所有的链接通过筛选得到我们所需要的:

public static boolean retrieveLink(org.jsoup.nodes.Element link) {
String url = "http://www.haodou.com/recipe/";
if (link.attr("abs:href").contains(url)
&& !link.attr("abs:href").contains("#")
&& !link.attr("abs:href").contains("album")
&& !link.attr("abs:href").contains("knowledge")
&& !link.attr("abs:href").contains("all")
&& !link.attr("abs:href").contains("create")
&& !link.attr("abs:href").contains("food")
&& !link.attr("abs:href").contains("category")
&& !link.attr("abs:href").contains("top")
&& !link.attr("abs:href").contains("expert"))
return true;
return false;

}

在每一条子链接中的内容是我们所需要的,所以对应子链接我们需要定义不同的模式来抓取:

public static void getAll(String url) {
try {
// 样例链接
url = "http://www.haodou.com/recipe/869836/";
url = "http://www.haodou.com/recipe/326177/";
org.jsoup.nodes.Document doc = Jsoup.connect(url).get();
// 成品图
Elements elements = doc.select(".recipe_cover");
for (Element element : elements)
System.out.println(element.attr("abs:src"));
// 步骤图
elements = doc.select(".imit_m > img");
for (Element element : elements)
System.out.println(element.attr("abs:src"));
// 小贴士
elements = doc.select(".data");
for (Element element : elements)
System.out.println(element.text());
System.out.println("aaaa");
// 做菜步骤
Element prompt = doc.select(".prompt span").first();
System.out.println(prompt.text());
// 获取菜名
Element title = doc.select("h1").first();
// result.append(link.text());
System.out.println(title.text());
// 菜的简介
title = doc.select("[data]").first();
// result.append(link.text());
System.out.println(title.text());
// 食材
Elements links = doc.select(".ingtbur");
for (Element element : links)
// result.append(element.text());
System.out.println(element.text());

// 步骤
links = doc.select(".sstep");
for (Element element : links)
// result.append(element.text());
System.out.println(element.text());
// 标签
links = doc.select("p > a[href]");

int i = 0;
for (Element element : links) {
i++;
if (element.attr("abs:href").contains("http://www.haodou.com/recipe/all")) {
System.out.println(element.toString());
}
}
elements = doc.select(".quantity");
for (Element element : elements)
System.out.println(element.text());
elements = doc.select(".pop_tags a");
for (Element element : elements)
System.out.println(element.text());
Element element = doc.select(".des span").last();
System.out.println(element.text());
} catch (IOException e) {
e.printStackTrace();
}
}

得到的结果:

869836,炸茄盒炸茄盒外焦里嫩,入口不腻,咬上一口,淡淡的肉香和茄子的清香同时浮现,让人心动不已。鸡蛋1个姜适量蒜子适量葱适量盐适量白糖适量料酒适量酱油适量老抽适量胡椒粉适量麻油适量面粉适量生粉适量1.首先我们将猪肉剁成肉泥、姜切成姜米、葱切葱花、蒜子切成蒜末、茄子去皮,然后在每一小段中间切一刀,但不要切断,做成茄盒。2.然后我们来制作肉馅:将猪肉泥放入盘中,加入姜米、蒜末、葱花、少许盐、少许白糖、适量料酒、少量酱油、少量老抽、少许胡椒粉、淋入食用油、麻油,抓匀,接着再加入生粉,抓打至肉馅变得粘稠。3.将茄夹中间抹上生粉,用肉馅填满茄夹。4.填满肉馅后,再逐一将整个茄盒抹上生粉。5.接着我们来调面糊:准备一个碗,在碗中放入适量面粉、适量生粉、半勺盐、一个鸡蛋拌匀,再加少许清水,拌至粘稠状,备用。6.锅中放入半锅油,烧至8成热后,将茄盒放入面糊中裹上面糊,再逐个下油锅中,炸至茄盒表面呈金黄色后捞出沥油。这道菜就完成了。菜谱大全健脾开胃儿童减肥宴请家常菜小吃炸白领私房菜聚会

这只是一个样例,我一共抓取了6000+道菜,包括图片和链接资源。


当然如果你想要网页上的其他内容还有很多可以选择:
我建议是最好先抓取所有的内容,然后根据html来选择你要的部分

File input = new File("/tmp/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");

Elements links = doc.select("a[href]"); //带有href属性的a元素
Elements pngs = doc.select("img[src$=.png]");
//扩展名为.png的图片

Element masthead = doc.select("div.masthead").first();
//class等于masthead的div标签

Elements resultLinks = doc.select("h3.r > a"); //在h3元素之后的a元素

说明(这个姿势一部分,更加详细的请点击参考文献链接)

jsoup elements对象支持类似于CSS (或jquery)的选择器语法,来实现非常强大和灵活的查找功能。.

这个select 方法在Document, Element,或Elements对象中都可以使用。且是上下文相关的,因此可实现指定元素的过滤,或者链式选择访问。

Select方法将返回一个Elements集合,并提供一组方法来抽取和处理结果。

Selector选择器概述
tagname: 通过标签查找元素,比如:a
ns|tag: 通过标签在命名空间查找元素,比如:可以用 fb|name 语法来查找 <fb:name> 元素
#id: 通过ID查找元素,比如:#logo
.class: 通过class名称查找元素,比如:.masthead
[attribute]: 利用属性查找元素,比如:[href]
[^attr]: 利用属性名前缀来查找元素,比如:可以用[^data-] 来查找带有HTML5 Dataset属性的元素
[attr=value]: 利用属性值来查找元素,比如:[width=500]
[attr^=value], [attr$=value], [attr*=value]: 利用匹配属性值开头、结尾或包含属性值来查找元素,比如:[href*=/path/]
[attr~=regex]: 利用属性值匹配正则表达式来查找元素,比如: img[src~=(?i)\.(png|jpe?g)]
*: 这个符号将匹配所有元素

以下是参考文献:
使用选择器语法来查找元素
Jsoup Cookbook(中文版)
Jsoup实现网络爬虫的整理集合-CSDN
java爬虫中jsoup的使用
java简单爬虫中正则表达式的使用