爬取京东商品案例
需求分析
一、需求
抓取京东商城的数据,把商品数据保存到数据库。
二、功能分析
- 使用HttpClient发送一个get请求,请求搜索url,得到商品列表
- 使用jsoup解析搜索结果页面。
- 把商品信息封装一个对象中。
- 把商品数据保存到数据库。
三、京东页面分析
当在京东的搜索框输入手机时,此时的url为
https://search.jd.com/Search?keyword=手机&wq=手机&page=1&s=1&click=1
当进行翻页操作,发现page值为1,3,5,7…进一步分析发现,京东商品每一页展示两页数据,当看完一页数据后,拉到底部,会显示第二页数据,当看完第二页数据,才会进行翻页操作,此时真正进入第二页。
问题:京东商城每次只展示30条数据,后30条数据(每页的第二页)是ajax动态加载的,使用HttpClient解决非常麻烦,此处不进行爬取后30条数据。
爬取页数:取10页数据。
爬取的信息:商品标题,价格,图片,skuid,spuid,商品详情地址等
四、数据库表创建
保存到数据库:创建一个数据库。需要的字段都是商品列表中可以解析出来的字段。
CREATE TABLE `jd_item` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`spu` bigint(15) DEFAULT NULL COMMENT '商品集合id',
`sku` bigint(15) DEFAULT NULL COMMENT '商品最小品类单元id',
`title` varchar(1000) DEFAULT NULL COMMENT '商品标题',
`price` float(10,0) DEFAULT NULL COMMENT '商品价格',
`pic` varchar(200) DEFAULT NULL COMMENT '商品图片',
`url` varchar(1500) DEFAULT NULL COMMENT '商品详情地址',
`created` datetime DEFAULT NULL COMMENT '创建时间',
`updated` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `sku` (`sku`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=217 DEFAULT CHARSET=utf8 COMMENT='京东商品';
代码实现
编写爬虫的业务逻辑
1、使用工具类创建一个HttpClient对象。
2、使用HttpClient发送请求,请求就是搜索的url+页码
3、接收服务端响应html
4、使用Jsoup解析html
5、把解析的商品数据封装成Item对象
6、使用dao把商品写入数据库。
7、需要翻页。
注意:图片也是一个http请求,需要再次发起请求爬取图片,然后存储到本地
@Component
public class Crawler {
@Autowired
private ItemDao itemDao;
private String startUrl = "https://search.jd.com/Search?keyword=手机&wq=手机&s=1&click=1&page=";
/**
* 爬取页面
*/
public void doCrawler() {
try {
//1、使用工具类创建一个HttpClient对象。
CloseableHttpClient httpClient = HttpsUtils.getHttpClient();
for (int i = 0; i < 10; i++) {
//2、使用HttpClient发送请求,请求就是搜索的url+页码
HttpGet get = new HttpGet(startUrl + (i * 2 + 1));
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);
//3、接收服务端响应html
HttpEntity entity = response.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("src");
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();
//需要截取扩展名
String extName = imageUrl.substring(imageUrl.lastIndexOf("."));
//需要生成文件名。可以使用uuid生成文件名。
String fileName = UUID.randomUUID() + extName;
//D:\temp\term331\images
//创建一个文件输出流,把文件保存到磁盘
FileOutputStream outputStream = new FileOutputStream("D:\\temp\\images\\" + fileName);
//接收流,把内容保存到磁盘。
entity.writeTo(outputStream);
//关闭流
outputStream.close();
//关闭Response对象
response.close();
return fileName;
}
}
开启新线程可以让爬取在后台进行,而不是页面一直在转
@RestController
public class CrawlerController {
@Autowired
private Crawler crawler;
@RequestMapping("/start")
public String startCrawler() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程已经启动。。。。。");
crawler.doCrawler();
}
}).start();
return "OK";
}
}