Java爬虫爬取京东商城

一、任务:

旨在通过使用java爬虫,提取网络中的各种商品信息,并收集的商品信息建立统一数据模型存储数据,通过数据模型描述商品的基本属性。如spu,sku,商品描述,价格等信息,同时需要剔除非必要信息,做到精准分析。根据所获取的信息提供商品展示页面,通过搜索,得到商品数据信息。抓取商品数据,建立统一数据模型,模型的可扩展性,商品数据展示。

目的:该项目有利于简单理解java的爬虫过程,spring boot简单的项目调试,调用,映射方式,数据库连接,帮助理解的前后端交互原理。

二、类及数据的设计

2.1项目的开发环境

2.2系统功能结构设计

该程序主要通过调用webmagic使用爬虫功能爬取数据,建立数据模型,利用MySQL存储数据。查询调用数据库内容,模型的可扩展性,通过html/css提供web页面展示。

2.2.1数据爬取,数据模型建立

WebMagic:

WebMagic的结构分为Downloader、PageProcessor、Scheduler、Pipeline四大组件,并由Spider将它们彼此组织起来。

1)Downloader:负责从互联网上下载页面,以便后续处理。WebMagic默认使用了Apache HttpClient作为下载工具。

2)PageProcessor:负责解析页面,抽取有用信息,以及发现新的链接。WebMagic使用Jsoup作为HTML解析工具,并基于其开发了解析XPath的工具Xsoup。在这四个组件中,PageProcessor对于每个站点每个页面都不一样,是需要使用者定制的部分。

3)Scheduler:负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重。也支持使用Redis进行分布式管理。除非项目有一些特殊的分布式需求,否则无需自己定制Scheduler。

4)Pipeline:负责抽取结果的处理,包括计算、持久化到文件、数据库等。WebMagic默认提供了“输出到控制台”和“保存到文件”两种结果处理方案。Pipeline定义了结果保存的方式,如果你要保存到指定数据库,则需要编写对应的Pipeline。对于一类需求一般只需编写一个Pipeline。

Selenium:

Selenium是一个Web的自动化测试工具,可以根据我们的指令,使用代码控制浏览器,让浏览器自动加载页面,获取需要的数据,甚至页面截屏,或者判断网站上某些动作是否发生,支持主流的浏览器

该程序使用Downloader、PageProcessor、Pipeline,Spider组件进行爬虫,建立数据模型。通过selenium对谷歌无头浏览器进行自动化操作。

1、定时任务

在springboot工程中使用定时器。在工程中创建一个普通的类,添加注解@Component,

在定义方法上使用@Scheduled注解标注,配置定期执行时间,在spring boot工程的引导类上添加@EnableScheduling注解。

2、设置代理

使用代理服务器发起请求,防止反爬策略封ip

代理服务器流程:

爬虫服务器 -> 代理服务器 -> 目标服务器

目标服务器 -> 代理服务器 -> 爬虫服务器 ->解析数据

可用的免费代理:

免费私密代理 - 米扑代理

http://www.xiladaili.com/gaoni/

3、使用selenium+无头浏览器抓取数据

通过Maven添加Selenium依赖。Selenium是一个前端的测试框架,通过Selenium使用代码控制浏览器。

无头浏览器:没有界面的浏览器,解析js。得到一些默认不到的数据。用于各类测试场景,在任何给定页面上可采取的频繁重复的操作,反复测试。

4、使用浏览器渲染,抓取京东商城的数据并保存

1)PageProcess解析html

        1. 判断是列表页面还是详细页面 

        2. 如果是列表页面

                a、解析列表中的商品数据,去sku和spu,封装成一个对象,传递给pipeline

                b、解析商品的链接地址,把地址添加到访问队列中

                c、翻页处理,设置固定url:Enterprise Cybersecurity Solutions, Services & Training | Proofpoint US 添加一个附件:当前请求的url

        3. 如果是详细页面

                a、解析商品的详细信息

                b、把详细信息封装成一个商品对象

                c、传递给pipeline

2)Downloader下载页面

 

        1. 抓取列表页面

                a、访问url

                b、页面滚动到最下方

                c、从浏览器中取html

                d、需要把结构封装成Page对象

        2. 如果是详情页面

                a、直接访问url

                b、取html,封装成Page,返回

        3. 如果是翻页处理

                a、从Request对象中取附件,翻页之前的url

                b、访问url

                c、点击翻页按钮,翻到第二页

                d、让页面滚到最下方,加载30条数据

                e、把去浏览器渲染的html结果封装成Page对象返回

3)Pipeline保存到数据库

        创建数据库表,创建对应的属性

5、模型的可扩展性

        基于springboot的控制反转,类与类之间没有很强的耦合性,具有很好的“特性:“高内聚、低耦合”实例化的操作交给Spring 的bean工厂,通过xml配置文件去记录。所以模型具有很强的可扩展性。只需在Item中添加属性,并添加对应的浏览器操作。

2.2.2 SpringBoot+Ajax+MyBatis查询操作数据库

开发顺序

        后端SpringBoot+MyBatis, 前端Ajax+jQuery+CSS+HTML,通过爬虫操作得到数据,根据数据对于后端接口数据设计和使用,前端数据请求和响应填充界面的过程,数据库采用MySQL 8.0.26,用于学习掌握前后端开发的关键技术和开发架构。

        随着 Spring Boot 越来越流行,MyBatis 也开发了一套基于 Spring Boot 模式的 starter:mybatis-spring-boot-starter。

 

entity层:存放的是实体类,属性值与数据库值保持一致,实现 setter 和 getter 方法。

dao层:即 mapper层,对数据库进行持久化操作,他的方法使针对数据库操作的,基本上用的就是增删改查。作为接口,只有方法名,具体实现在mapper.xml中实现。

service层:业务层,存放业务逻辑处理,不直接对数据库进行操作,有接口和接口实现类,提供 controller 层调用方法。

controller层:控制层,导入 service层,调用你service方法,controller通过接受前端传来的参数进行业务操作,在返回一个制定的路径或数据表。

选择ajax原因是基于爬虫操作数据量大,变化多,AJAX能提供在无需重新加载整个网页的情况下,能够更新部分网页的技术。AJAX 是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

而选择MyBatis,因为MyBatis可以使用简单的XML或注释进行配置,并将图元,映射接口和 POJO映射到数据库记录。消除了大部分JDBC代码以及参数的手动设置和结果检索。同时基于MyBatis灵活特性,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,降低耦合度,更加为程序的可拓展性提供基础。

 三、部分代码

3.1爬虫部分

@Override
     public void process(Page page) {
         String level = page.getRequest().getExtra("level").toString();
         switch (level){
             case "list":
                 parseList(page);
                 break;
             case "detail":
                 praseDetail(page);
                 break;
         }

/**
      * 解析详情页
      *
      * @param page
      */
     private void praseDetail(Page page) {
         Html html = page.getHtml();
         String title = html.$("div.master .p-name").xpath("///allText()").get();
         String priceStr = html.$("div.summary-price-wrap .p-price span.price").xpath("///allText()").get();
         String pic = "https:"+html.$("#spec-img").xpath("///@src").get();
         String url = "https:"+html.$("div.master .p-name a").xpath("///@href").get();
         String sku = html.$("a.notice.J-notify-sale").xpath("///@data-sku").get();
 
         Item item = new Item();
         item.setTitle(title);
         item.setPic(pic);
         item.setPrice(Float.valueOf(priceStr));
         item.setUrl(url);
         item.setUpdated(new Date());
         item.setSku(StringUtils.isNotBlank(sku)?Long.valueOf(sku) : null);
 
         // 单条数据塞入
         page.putField("item", item);
     }

 /**
      * 解析列表页
      * @param page
      */
     private void parseList(Page page) {
         Html html = page.getHtml();
         // 这里拿到sku 和 spu 并交给pipeline
         List<Selectable> nodes = html.$("ul.gl-warp.clearfix > li").nodes();
         List<Item> itemList = new ArrayList<>();
         for (Selectable node : nodes) {
             // 拿到sku和spu
             String sku = node.$("li").xpath("///@data-sku").get();
             String spu = node.$("li").xpath("///@data-spu").get();
             String href = "https:" + node.$("div.p-img a").xpath("///@href").get();
 
             Item item = new Item();
             item.setSku(Long.valueOf(sku));
             item.setSpu(StringUtils.isNotBlank(spu) ? Long.valueOf(spu) : 0);
             item.setCreated(new Date());
             itemList.add(item);
 
             // 同时还需要把链接加到详情页 加到队列
             Request request = new Request(href);
             request.putExtra("level", "detail");
             request.putExtra("pageNum", page.getRequest().getExtra("pageNum"));
             request.putExtra("detailUrl", href);
             page.addTargetRequest(request);
         }
 
         // 以集合的方式存入
         page.putField("itemList", itemList);
 
         // 同时还要去做分页
         String pageNum = page.getRequest().getExtra("pageNum").toString();
         if ("1".equals(pageNum)){
             Request request = new Request("https://nextpage.com");
             request.putExtra("level", "page"); // 标识去分页
             request.putExtra("pageNum", (Integer.valueOf(pageNum) + 1) + "");// 页码要+1 接下来要的是第二页
             // 添加到队列
             page.addTargetRequest(request);
         }

3.2 spring boot查询操作数据库

Control控制层:

@RestController//返回rest服务类型的数据格式
 @RequestMapping("/Jd")//数据接口controller怎么被调用
 public class ItemController {
     //调用一些方法得到返回值,把服务层作为对象
     @Autowired//自动注入,生成实例
     private ItemService itemService;//好封装
 
     @GetMapping("/getJd")//路径如果是Jd下的getJd,会获得前端传来的参数‘id',获得值,把id值传到findById方法中
     public String getItem(@Param("id")Integer id){
         Item item = itemService.findById(id);
         return item.getTitle();
     }
     @GetMapping("/getId") // 通过title// 获取id
     public Integer getId(@Param("Message") String title){
         Item item = itemService.findByTitle(title);
         return item.getId();
     }
 
     @GetMapping("/getOne") // 通过title// 获取id,一条数据记录
     public Item getAll(@Param("id") Integer id){
         Item item = itemService.findById(id);
         return item;
     }
 
     @GetMapping("/getJson") // 通过title获取id
     public String getJson(@Param("id") Integer id) {
         Item item = itemService.findById(id);
         Gson gson = new Gson();
         return gson.toJson(item);
     }
     @GetMapping("/getAll") // 通过title获取id,获得多条数据
     public List<Item> getAll(){
         List<Item> list = itemService.findItemAll();
         return list;
     }
 
     @GetMapping("/getAllJson") // 通过title获取id
     public String getAllJson(){
         List<Item> list = itemService.findItemAll();
         Gson gson = new Gson();
         return gson.toJson(list);
     }
}

3.3前端设计

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" type="text/css"href="https://cdn.bootcss.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css">
<script type="text/javascript"src="https://cdn.bootcss.com/jquery/1.4.0/jquery.js"></script>
<script>
$(document).ready(function(){
$("#btn1").click(function(){
test1();
});

$("#btn2").click(function(){
$.test2();
});
});
//data为数组
function test1(){
//alert("Text1: " + $("#test").text());
$.ajax({
url:'/msg/getAllJson',//访问后台接口,用get,后台返回json
type:'get',
dataType:'json',
success:function(data){
$("#tabletest").find('tr').remove();
tr='<td>id</td><td>title</td>'
$("#tabletest").append('<tr>'+tr+'</tr>')
//方法中传入的参数data为后台获取的数据
for(i in data) //data指的是数组,i为数组的索引
{
var tr;
tr='<td>'+data[i].id+'</td>'+'<td>'+data[i].title +'</td>'
$("#tabletest").append('<tr>'+tr+'</tr>')
}
}
});
}
</script>
<style type="text/css">
.center{
margin: auto;
text-align:center;
font-size: 24px;
width: 60%;
background: lightblue;
}
</style>
</head>
<body>
<div class="center">
<p id="test">Springboot整合MyBatis通过ajax查询MySQL数据库数据</b></p>
<button id="btn1">显示所有数据</button>
<button id="btn2">查询</button>
<input id="id" name="id" type="text" placeholder="请输入id值"/>
<br>
<table class="table table-bordered" id='tabletest'>
</table>
</div>
</body>
</html>

四、程序运行、测试与分析

4.1程序运行

 

4.2总结分析

  1. 在使用java程序应注意jdk版本问题,以及jdk对应的数据库版本。
  2. 在使用数据库操作时,应注意数据库与idea连接application中的url,username,password格式问题。
  3. 应注意传入数据文件路径相对绝对路径问题。
  4. 学习通过debug,逐步了解一个项目的进程,排除错误。
  5. 在调试程序中,应该带着思考搜索解决方式,逐一排除造成错误的原因。
  6. 了解各种注释API作用,有助于优化代码。
  7. 对于繁多的技术,需要从需求出发,合理选择合适,高效,可拓展的技术。
  8. 通过程序更加深入了解前后端工作方式,更加理解springboot的工作原理。

4.3改进方案

4.3.1对于爬虫:

        首先可以通过更高效的框架加快爬虫速度,实现更加灵活的定制化爬取。其次,可以通过优化算法,对于一些爬取失败或数据获取失败的记录进行汇总,在页面反馈成功完整数据,通过网页分析算法过滤主题无关的链接。

        交互问题是一个需要解决的问题,爬取会页面涉及到用户信息输入,验证码处理,随着各类花样繁多的验证码的出现,爬虫遇到这种情况会很难处理。

        Javascript 解析问题,目前大多数网页属于动态网页,网页中大多数有用的数据都是通过ajax/fetch动态获取后然后再由js填充到网页,单纯的html静态页面中有用的数据很少。让后台脚本去做javascript操作会很麻烦,不仅需要清楚的理解原网页代码逻辑也会让代码显得很臃肿。

        ip解析问题,尽管在本程序使用代理ip,但这仍然是爬虫会遇到的最致命问题。网站防火墙会对某个ip在某段时间内请求的次数做限制,如果超过上限则拒绝请求。后台爬取时机器和ip有限,很容易达到上线而导致请求被拒绝。目前主要的应对方案是使用代理,这样一来ip的数量就会多一些,但代理ip依然有限。

4.3.2.前端太丑,过于简陋

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值