旨在通过使用java爬虫,提取网络中的各种商品信息,并收集的商品信息建立统一数据模型存储数据,通过数据模型描述商品的基本属性。如spu,sku,商品描述,价格等信息,同时需要剔除非必要信息,做到精准分析。根据所获取的信息提供商品展示页面,通过搜索,得到商品数据信息。抓取商品数据,建立统一数据模型,模型的可扩展性,商品数据展示。
目的:该项目有利于简单理解java的爬虫过程,spring boot简单的项目调试,调用,映射方式,数据库连接,帮助理解的前后端交互原理。
该程序主要通过调用webmagic使用爬虫功能爬取数据,建立数据模型,利用MySQL存储数据。查询调用数据库内容,模型的可扩展性,通过html/css提供web页面展示。
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中添加属性,并添加对应的浏览器操作。
开发顺序
后端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里,从程序代码中彻底分离,降低耦合度,更加为程序的可拓展性提供基础。
@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 nodes = html.$("ul.gl-warp.clearfix > li").nodes();
List- 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);
}
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- getAll(){
List
- list = itemService.findItemAll();
return list;
}
@GetMapping("/getAllJson") // 通过title获取id
public String getAllJson(){
List
- list = itemService.findItemAll();
Gson gson = new Gson();
return gson.toJson(list);
}
}
Title
Springboot整合MyBatis通过ajax查询MySQL数据库数据
首先可以通过更高效的框架加快爬虫速度,实现更加灵活的定制化爬取。其次,可以通过优化算法,对于一些爬取失败或数据获取失败的记录进行汇总,在页面反馈成功完整数据,通过网页分析算法过滤主题无关的链接。
交互问题是一个需要解决的问题,爬取会页面涉及到用户信息输入,验证码处理,随着各类花样繁多的验证码的出现,爬虫遇到这种情况会很难处理。
Javascript 解析问题,目前大多数网页属于动态网页,网页中大多数有用的数据都是通过ajax/fetch动态获取后然后再由js填充到网页,单纯的html静态页面中有用的数据很少。让后台脚本去做javascript操作会很麻烦,不仅需要清楚的理解原网页代码逻辑也会让代码显得很臃肿。
ip解析问题,尽管在本程序使用代理ip,但这仍然是爬虫会遇到的最致命问题。网站防火墙会对某个ip在某段时间内请求的次数做限制,如果超过上限则拒绝请求。后台爬取时机器和ip有限,很容易达到上线而导致请求被拒绝。目前主要的应对方案是使用代理,这样一来ip的数量就会多一些,但代理ip依然有限。