一、什么是爬虫
爬虫是一段程序,抓取互联网上的数据,保存到本地。
抓取过程:
1、使用程序模拟浏览器
2、向服务器发送请求。
3、服务器响应html
4、把页面中的有用的数据解析出来。
解析页面中的链接地址。
把链接地址添加到url队列中。
5、爬虫从url队列中取url,返回的操作。
二、爬虫的抓取环节
1、抓取页面。
可以使用java api中提供的URLConnection类发送请求。
推荐使用工具包HttpClient。
是apache旗下的一个开源项目。
可以模拟浏览器。
2、对页面进行解析。
使用Jsoup工具包。
可以像使用jQuery一样解析html。
二、HttpClient
可以使用HttpClient模拟浏览器。
1、使用HttpClient发送get请求
步骤:
@Test
public void testGet() throws Exception {
//1)创建一个HttpClient对象,使用CloseableHttpClient,使用HttpClients工具类创建。
CloseableHttpClient httpClient = HttpClients.createDefault();
//2)创建一个HttpGet对象,get对象封装请求的url
//HttpGet get = new HttpGet("http://bbs.itheima.com/search.php?mod=forum&searchid=50&orderby=lastpost&ascdesc=desc&searchsubmit=yes&kw=java");
HttpGet get = new HttpGet("https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&enc=utf-8&wq=%E6%89%8B%E6%9C%BA&pvid=2d8c452b8a904b16afc71167f307f975");
//3)使用HttpClient执行请求
CloseableHttpResponse response = httpClient.execute(get);
//4)接收服务端响应的内容。
StatusLine statusLine = response.getStatusLine();
System.out.println(statusLine);
// 响应的内容包含响应头
int statusCode = statusLine.getStatusCode();
System.out.println(statusCode);
// 包含响应的内容(html)
HttpEntity entity = response.getEntity();
String html = EntityUtils.toString(entity);
System.out.println(html);
//5)关闭连接
response.close();
httpClient.close();
}
2、使用HttpClient发送Post请求
步骤:
@Test
public void testPost() throws Exception {
//1)创建一个HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//2)创建HttpPost对象,封装一个url
HttpPost post = new HttpPost("http://bbs.itheima.com/search.php");
//3)如果有参数就应该把参数封装到表单中。
List<NameValuePair> form = new ArrayList<>();
form.add(new BasicNameValuePair("mod", "forum"));
form.add(new BasicNameValuePair("searchid", "50"));
form.add(new BasicNameValuePair("orderby", "lastpost"));
form.add(new BasicNameValuePair("ascdesc", "desc"));
form.add(new BasicNameValuePair("searchsubmit", "yes"));
form.add(new BasicNameValuePair("kw", "java"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(form);
post.setEntity(entity);
//4)使用HttpClient执行请求。
CloseableHttpResponse response = httpClient.execute(post);
//5)接收服务端响应html
HttpEntity resultEntity = response.getEntity();
String html = EntityUtils.toString(resultEntity);
System.out.println(html);
//6)关闭连接
response.close();
httpClient.close();
}
爬京东的数据,需要加上请求头(最近新出的规则,具体可以百度):
@Test
public void testHtts() throws Exception {
//创建一个HttpClient对象
CloseableHttpClient httpClient = HttpsUtils.getHttpClient();
//创建一个HttGet对象,封装url
HttpGet get = new HttpGet("https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&enc=utf-8&wq=%E6%89%8B%E6%9C%BA&pvid=2d8c452b8a904b16afc71167f307f975");
get.addHeader("User-Agent", " Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
//执行请求
CloseableHttpResponse response = httpClient.execute(get);
//接收结果
HttpEntity entity = response.getEntity();
String html = EntityUtils.toString(entity, "utf-8");
//打印结果
System.out.println(html);
//关闭Response
response.close();
}
三、jsoup
jsoup就是一个java的工具包。解析html的工具包。
1、使用方法
1)使用Jsoup工具类提供的方法parse,解析html
parse的参数可以是url、本地文件、String(html)、InputStream
解析之后得到一个Document对象。
2)可以使用Document对象的方法对页面进行解析
1、常用的方法
根据id选择
根据节点名称选择
根据属性选择
根据属性名称选择
根据class名称选择
2、使用css选择器
和jQuery相同的css选择器。
使用select方法执行css选择器。
2、Document的常用方法
根据标签名称选择节点:
getElementsByTag
根据id选择节点:
getElementById
根据属性选择节点:判断节点是否包含此属性。
getElementsByAttribute
根据属性的值选择节点:
getElementsByAttributeValue
根据class选择节点:
getElementsByClass
取标签内的文本信息:
text()
取标签的属性:
attr(“属性名称”)
取标签中的子标签,返回集合:
nodes()
@Test
public void parseHtml() throws Exception {
//使用Jsoup工具类提供的方法parse,解析html
//Document document = Jsoup.parse(new URL("http://www.itcast.cn"), 3000);
Document document = Jsoup.parse(new File("D:\\temp\\term331\\test.html"), "utf-8");
//解析title
Elements elements = document.getElementsByTag("title");
for (Element element : elements) {
System.out.println(element.text());
}
//取链接地址
System.out.println("取链接地址-----------------------");
Elements elements1 = document.getElementsByTag("a");
for (Element element : elements1) {
System.out.println(element.attr("href"));
}
System.out.println("取标签内的文本信息-------------------");
Element element = document.getElementById("test");
System.out.println(element.text());
System.out.println("根据属性选择节点:判断节点是否包含此属性,有就取,没有就不取------------");
Elements elements2 = document.getElementsByAttribute("id");
for (Element element1 : elements2) {
System.out.println(element1);
}
System.out.println("根据属性的值选择节点------");
Elements elements3 = document.getElementsByAttributeValue("target", "_blank");
for (Element element1 : elements3) {
System.out.println(element1);
}
System.out.println("根据class选择节点-------------");
Elements elements4 = document.getElementsByClass("s_name");
for (Element element1 : elements4) {
System.out.println(element1);
}
}
3、Document对象的css选择器
select方法解析css选择器。
@Test
public void testCssSelector() throws Exception {
Document document = Jsoup.parse(new File("D:\\temp\\term331\\test.html"), "utf-8");
//解析document对象
//根据id选择
//Elements elements = document.select("#city_bj");
//根据标签名称选择
//Elements elements = document.select("li");
//根据class选择
//Elements elements = document.select(".s_name");
//根据属性选择
//Elements elements = document.select("[class='slogan']");
//css选择器可以组合使用
//Elements elements = document.select("div.city > div.city_in div.city_con span[abc='123']");
Elements elements = document.select("#test > a:nth-child(1) > span:nth-child(1)");
for (Element element : elements) {
System.out.println(element);
}
}
四、案例
1、需求
抓取京东商城的数据,把商品数据保存到数据库。
2、功能分析
1)使用HttpClient发送一个get请求,请求搜索url,得到商品列表
2)使用jsoup解析搜索结果页面。
3)把商品信息封装一个对象中。
4)把商品数据保存到数据库。
https://search.jd.com/ Search?keyword=%E7%94%B5%E8%A7%86&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=%E7%94%B5%E8%A7%86&stock=1&s=55&click=0&page=1
京东商城每次只展示30条数据,后30条数据是ajax动态加载的。
取10页数据。
保存到数据库:
创建一个数据库。
需要的字段都是商品列表中可以解析出来的字段。
持久层框架可以使用springDataJpa,使用springboot搭建工程。
3、工程搭建
1)创建一个springboot工程
2)添加父工程及依赖的jar包
SpringDataJpa的起步依赖
mysql的数据库驱动
添加spring-boot-stater-web模块
HttpClient
jsoup
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>crawler-331</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
<dependencies>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringData Jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--MySQL连接包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!--Jsoup-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.3</version>
</dependency>
<!--appch 封装了String的工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</dependency>
</dependencies>
</project>
3)创建一个application.properties
其中配置数据库的连接信息
#DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/crawler-331
spring.datasource.username=root
spring.datasource.password=123
#JPA Configuration:
spring.jpa.database=MySQL
spring.jpa.show-sql=true
@Entity
@Table(name = "jd_item")
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private long spu;
private long sku;
private String title;
private float price;
private String url;
private String pic;
private Date created;
private Date updated;
package com.itheima.component;
import com.itheima.dao.ItemDao;
import com.itheima.entity.Item;
import com.itheima.utils.HttpsUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.swing.text.html.parser.Entity;
import java.io.FileOutputStream;
import java.util.Date;
import java.util.UUID;
@Component
public class Crawler {
@Autowired
private ItemDao itemDao;
private String startUrl = "https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA" +
"&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=%E6%89%8B%E6%9C%BA&cid2=653" +
"&cid3=655&s=58&click=0&page=";
//获取京东页面的方法
public void doCrawler() {
//1、使用工具类创建一个HttpClient对象。
try {
for (int i = 0; i < 10; i++) {
CloseableHttpClient httpClient = HttpsUtils.getHttpClient();
//2、使用HttpClient发送请求,请求就是搜索的url+页码
HttpGet httpGet = new HttpGet(startUrl+(i*2+1));
httpGet.addHeader("User-Agent", " Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
//3、接收服务端响应html
HttpEntity entity = httpResponse.getEntity();
String html = EntityUtils.toString(entity, "utf-8");
//4、使用Jsoup解析html,之后存入数据库
parseHtml(html);
//5、把解析的商品数据封装成Item对象
//6、使用dao把商品写入数据库。
//7、需要翻页。
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 页面解析的业务逻辑
* @param html
*/
private void parseHtml(String html) throws Exception {
//4、使用Jsoup解析html
Document document = Jsoup.parse(html);
//解析商品列表
Elements elements = document.select("li.gl-item");
for (Element element : elements) {
//解析节点中的商品数据
//spu
String spu = element.attr("data-spu");
//sku
String sku = element.attr("data-sku");
//title
String title = element.select("div.p-name em").text();
//price
String price = element.select("div.p-price i").text();
//图片
String imgUrl = element.select("div.p-img img").attr("source-data-lazy-img");
String imageName = downloadImage(imgUrl);
//商品的url
String itemUrl = element.select("div.p-img > a").attr("href");
//5、把解析的商品数据封装成Item对象
Item item = new Item();
item.setSpu(Long.parseLong(spu));
item.setSku(Long.parseLong(sku));
item.setTitle(title);
if (StringUtils.isNotBlank(price)) {
item.setPrice(Float.parseFloat(price));
}
item.setPic(imageName);
item.setUrl(itemUrl);
item.setCreated(new Date());
item.setUpdated(new Date());
//6、使用dao把商品写入数据库。
itemDao.save(item);
}
}
/**
* 图片下载
* @param imageUrl
* @return
*/
private String downloadImage(String imageUrl) throws Exception {
//创建一个HttpClient对象
CloseableHttpClient httpClient = HttpsUtils.getHttpClient();
//创建一个HttpGet对象
HttpGet get = new HttpGet("https:" + imageUrl);
get.addHeader("User-Agent", " Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
//发送请求
CloseableHttpResponse response = httpClient.execute(get);
//接收服务端响应的内容。
HttpEntity entity = response.getEntity();
//需要截取扩展名,subString截取最后一个点,得到.加后面的字符
String extName = imageUrl.substring(imageUrl.lastIndexOf("."));
//需要生成文件名。可以使用uuid生成文件名。
String fileName = UUID.randomUUID() + extName;
//D:\temp\term331\images
//创建一个文件输出流,把文件保存到磁盘
FileOutputStream outputStream = new FileOutputStream("D:\\Code\\crawler331\\src\\images\\" + fileName);
//接收流,把内容保存到磁盘。
entity.writeTo(outputStream);
//关闭流
outputStream.close();
//关闭Response对象
response.close();
return fileName;
}
}