直接下载代码,修改test类的参数即可使用。
源代码:https://github.com/gaojinheng/crawler-baiduPicture
工具:IntelliJ IDEA,jdk9.0.4
1 页面分析
-
进入https://image.baidu.com/,搜索杨超越
-
在网页上右键单击,弹出菜单中选择“检查”,进入开发者模式。
-
选择Network,并且选中XHR后,重新加载页面,并滑动滚轮,更多图片会自动加载,此时我们看见下侧框中出现一些文件名类似的文件。
-
我们选择第一个文件,然后在跳出来的右侧框中选择Preview,我们看到的是一些json格式的数据,我们将折叠的“data”节点点开。
-
data里面有30条数据
-
我们再随便点开一条数据,查找http或https链接,并将其复制粘贴到浏览器中打开。最终发现以".jpg"结尾的链接即是图片的链接。(由于图片链接比较好辨认,我们可以考虑用正则表达式)
-
我们再回过头来看看这些json数据的链接,我们分别点击这些文件,再点击Headers,查看Request URL,这就是我们获得刚才分析的json数据的url。
分析:我们要用爬取图片,有两样东西是肯定要设置的,一是要搜索的关键字,二是爬取的图片数量。我们还要找出获取json数据的url之间的变化规律。- 关键字:既然我们在这里是搜索杨超越的图片,所以我们推断url中会有“杨超越”三个字。但我们在查看url时,并没有发现“杨超越”,那是因为“杨超越”被编码成了“%E6%9D%A8%E8%B6%85%E8%B6%8A”,其为queryWord和word的值,所以在爬取图片时,我们将queryWord和word的值的值改为我们想要搜索的关键词即可。
- 图片数量:我们在之前的分析中,看见一个url请求返回了30张图片的数据,所以我们查看url中有没有数字“30”。我们看见所有url中都有“rn=30”,我们可以推断,rn表示图片的数量。
- 链接变化规律:我们对比不同url,找出变化规律。我们看见了第1、2、3条url中分别有“pn=30”,“pn=60”,“pn=90”,可以推断,我们只要改变pn的值,就能加载更多不同的图片,且pn是rn的整数倍。
2 工程文件
3 代码分析
3.1 变量
- searchName:搜索时图片输入的关键字
- picNum:一次请求得到图片的url的数量,即rn的值,经测试,rn不大于60
- pageNum:获取多少页
- httpUtils:工具类,我们写一个工具类,其内部有三个方法
- doGetHtml:根据请求地址加载页面数据并返回
- doGetImage:根据图片的url下载图片
- doGetUrl:获取的html页面中的url
private String searchName;//搜索时图片输入的关键字
private int picNum;//一次请求得到图片的数量,不大于60
private int pageNum;//获取多少页
private HttpUtils httpUtils;//工具类
3.2 引入依赖
<dependencies>
<!--爬虫所需的依赖-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
3.3 Crawler.java
- 该类定义了上面提到的四个变量,并用searchName,picNum,pageNum三个变量来组建url,用httpUtil调用doGetHtml方法获取json数据,用doGetUrl方法解析json数据,并返回图片的url集合。最后用doGetImage方法根据图片url下载图片。
package crawler.baiduPicture.service;
import crawler.baiduPicture.util.HttpUtils;
import java.util.HashSet;
public class Crawler {
private String searchName;//搜索时图片输入的关键字
private int picNum;//一次请求得到图片的数量,不大于60
private int pageNum;//获取多少页
private HttpUtils httpUtils;//工具类
public void CrawlerBaiduPicture() throws Exception {
// 创建工具类对象
HttpUtils httpUtils = new HttpUtils();
// 按照页码搜索结果进行遍历解析
for (int i = 0; i < pageNum; i++) {
// 声明要解析的初始地址
String url = "https://image.baidu.com/search/acjson?tn=resultjson_com&logid=6200883187742644854&ipn=rj&ct=201326592&is=&fp=result&queryWord=" + searchName + "&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&hd=&latest=©right=&word=" + searchName + "&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&expermode=&force=&pn=" + (i + 1) * picNum + "&rn=" + picNum + "&gsm=3c&1619267607521=";
// 获取html页面(这里我们实际获取的是json)
String html = httpUtils.doGetHtml(url);
// 根据html获取图片的url的set集合
HashSet<String> urlSet = httpUtils.doGetUrl(html);
for (String urlset : urlSet) {
// 遍历set集合,下载图片
httpUtils.doGetImage(urlset);
}
}
}
public void setSearchName(String searchName) {
this.searchName = searchName;
}
public void setPicNum(int picNum) {
this.picNum = picNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
}
3.4 HttpUtils.java
- 该类主要定义了doGetHtml,doGetImage, doGetUrl方法,并配置了HttpClient连接池和设置了请求信息。注意:doGetUrl方法用正则表达式解析json数据获取图片的url。
package crawler.baiduPicture.util;
import org.apache.http.client.config.RequestConfig;
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.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HttpUtils {
private PoolingHttpClientConnectionManager cm;
private static int fileName = 1;
public HttpUtils() {
this.cm = new PoolingHttpClientConnectionManager();
//设置最大连接数
cm.setMaxTotal(100);
//设置每个主机的最大连接数
cm.setDefaultMaxPerRoute(10);
}
/**
* 根据请求地址加载页面数据并返回
* @param url
* @return
*/
public String doGetHtml(String url){
//获取httpClient对象
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
//设置httpClient请求对象,设置url地址
HttpGet httpGet = new HttpGet(url);
//设置请求信息
httpGet.setConfig(getConfig());
//设置User-Agent
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36");
CloseableHttpResponse response = null;
try {
//使用httpClient发起请求,获取响应
response = httpClient.execute(httpGet);
//解析响应,返回结果
if (response.getStatusLine().getStatusCode() == 200){
//判断响应体Entity是否不为空,如果不为空就可以使用EntityUtils
if (response.getEntity() != null){
String content = EntityUtils.toString(response.getEntity());
return content;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭response,httpClient已交给连接池管理,不用关闭
if (response != null){
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 返回空字符串
return "";
}
/**
* 根据图片的url下载图片
* @param url
* @return
*/
public String doGetImage(String url){
//获取httpClient对象
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
//设置httpClient请求对象,设置url地址
HttpGet httpGet = new HttpGet(url);
//设置请求信息
httpGet.setConfig(getConfig());
CloseableHttpResponse response = null;
try {
//使用httpClient发起请求,获取响应
response = httpClient.execute(httpGet);
//解析响应,返回结果
if (response.getStatusLine().getStatusCode() == 200){
//判断响应体Entity是否不为空,如果不为空就可以使用EntityUtils
if (response.getEntity() != null){
//下载图片
//获取图片的后缀
String extName = url.substring(url.lastIndexOf("."));//从最后一个.开始截取
//下载图片
File file = new File("C:\\Users\\86134\\Desktop\\images");
if (!file.exists()){
file.mkdir();
}
OutputStream outputStream = new FileOutputStream(new File("C:\\Users\\86134\\Desktop\\images\\" + fileName++ + extName));
response.getEntity().writeTo(outputStream);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭response,httpClient已交给连接池管理,不用关闭
if (response != null){
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 如果下载失败返回空字符串
return "";
}
/**
* 获取的html页面中的url
* @param html
* @return
*/
public HashSet<String> doGetUrl(String html) throws Exception {
HashSet<String> urlSet = new HashSet<String>();
String regex = "https\\:\\/\\/(.*?)\\.jpg";
Pattern pat = Pattern.compile(regex);
Matcher mat = pat.matcher(html);
while(mat.find()) {
String group = mat.group();
urlSet.add(group);
}
return urlSet;
}
//设置请求信息
private RequestConfig getConfig() {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(1000)//创建连接的最长时间
.setConnectionRequestTimeout(500)//获取时间的最长时间
.setSocketTimeout(10000)//数据传输的最长时间
.build();
return config;
}
}
3.5 test.java
- 该类用于设置searchName,picNum,pageNum三个变量的值,即搜索关键字、每页获取图片数、获取多少页。并调用Crawler的CrawlerBaiduPicture方法爬取图片。
package crawler.baiduPicture;
import crawler.baiduPicture.service.Crawler;
import org.junit.Test;
public class test {
private Crawler crawler = new Crawler();
@Test
public void intallPicture() throws Exception {
//设置搜索关键字
crawler.setSearchName("杨超越");
//设置每页获取图片数
crawler.setPicNum(60);
//设置获取多少页
crawler.setPageNum(10);
//爬取图片
crawler.CrawlerBaiduPicture();
}
}