Java爬虫实战: Jsoup+HtmlUnit+多线程+异步线程+七牛云oss+SpringBoot+MyBatis-Plus+MySQL8

PS:本开源爬虫项目仅供学习交流,请勿使用爬虫从事违法行为,爬取到的数据不贩卖,不外传,坚决遵守国家相关法律法规。

1. 项目介绍

本爬虫由站长一个人独立编写,由于Java爬虫相关文档少,写爬虫的时候踩到了许多坑,本文会配合视频逐一讲解。由于爬虫是结合自己做的电商项目,和同类爬虫相比做得还是比较细致的,项目共涉及6张表,表结构和爬取数据展示如下:

本爬虫支持爬取搜索页整个页面的spu(30条)和详情页的单个spu,爬取spu时会爬取对应的子商品。可以将爬取到的图片存储到本地或者七牛云,也可以都不存储,直接用第三方的链接,只需在application配置即可。爬取结果会有日志记录,对应表tb_crawler_log, 表中有个type字段代表爬取类型。0-定时任务爬取,1-爬取商品列表(30个spu),2-爬取详情页spu, 3-更新爬取数据。其中type=3很实用,因为大的电商反爬做得比较严,爬取的数据肯定有部分异常的,当发现某个商品数据异常时,可以重新爬取并更新结果。本网站商品详情页有重新爬取,更新异常商品数据的功能。

定时任务爬虫的配置对应表tb_crawler_config, 爬虫可以在多个服务器上跑,每个服务器根据自己ip地址在表中找到自己的配置信息,然后去爬取商品。

 商品列表爬取正常范围未Spu: 20~30; Sku: 30-500,否则可能是被反爬限流了或者该商品爬取过,不会再存储到数据库了。

 2. 源码分析

2.1 导入依赖

利用IDEA的Spring Initializr快速搭建一个SpringBoot环境,导入爬虫的核心依赖

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.10.3</version>
</dependency>
<!--模拟网页,实现动态获取-->
<dependency>
    <groupId>net.sourceforge.htmlunit</groupId>
    <artifactId>htmlunit</artifactId>
    <version>2.60.0</version>
</dependency>

2.2 使用HtmlUnit解析页面 

/**
 * @param cookie
 * @param url 爬取链接
 * @return
 * @throws Exception
 */
public  String parseByUrl(String cookie, String url) throws Exception{
    // 得到浏览器对象,直接New一个就能得到,现在就好比说你得到了一个浏览器了
    WebClient webClient = new WebClient(BrowserVersion.CHROME);
    webClient.setJavaScriptErrorListener(new HUnitJSErrorListener());
    webClient.setCssErrorHandler(new HUnitCssErrorListener());
    webClient.setJavaScriptTimeout(30000);

    // 这里是配置一下不加载css和javaScript,因为httpunit对javascript兼容性不太好
    webClient.getOptions().setCssEnabled(true);
    webClient.getOptions().setJavaScriptEnabled(true);
    webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
    webClient.getOptions().setThrowExceptionOnScriptError(false);
    webClient.getOptions().setPrintContentOnFailingStatusCode(false);

    Cookie ck = new Cookie(".jd.com","Cookie",cookie);
    webClient.getCookieManager().addCookie(ck);
    // header设置
    webClient.addRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36");
    // 做的第一件事,去拿到这个网页,只需要调用getPage这个方法即可
    HtmlPage htmlpage = webClient.getPage(url);
    return htmlpage.asXml();
}

解析过程中会打印大量日志,WebClient自带的配置将打印js和css日志关了,还是会打印日志。解决这个问题就需要重写DefaultJavaScriptErrorListener和DefaultCssErrorHandler两个类,捕捉到异常了什么都不处理,也就不会打印报错和警告日志信息了。

 2.3 application.yml配置

  • 新建爬虫数据库,导入项目的sql文件,配置好数据库连接池
  • 配置文件qiniu下面的storage和ossStorage值为Boolean类型,分别表示是否将图片存储到本地或者七牛云。一开始我配置ossStorage=true, 想把图片存到七牛云,但是图片太占空间了,没爬多少数据空间就用完了。然后就把两个属性设置为false,表示既不存储到本地,也不存储到七牛云oss, 直接用第三方的链接.(以下配置信息只是模板,非真实数据)
spring:
  application:
    name: crawler
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://lqjai.com:3306/mall_goods?serverTimezone=Asia/Shanghai
    username: zhangsan
    password: 6666666

qiniu:
  storage: false #是否开启本地存储
  ossStorage: false #是否开启七牛云存储
  accessKey: Jm9_djiofs16bXsFtWOSxMUvBJa3Rxp3wA0N0poH
  secretKey: 4hRBlT6DM_dBdUoLw8eMSu55kYiacl0Fzb2ZKn0J
  bucket: kili  #空间名称
  file:
    url: http://oss.lqjai.cn/
    path: qjmall/img/goods/

2.4  定时任务配置

自己设置合适的时间间隔,我这里间隔30秒后执行下一次任务。同时配置好数据库tb_crawler_config信息,主要是设置好爬取的关键词和需要爬取的页数,如果爬虫在多台服务器上跑则要配置多条记录,爬虫读取数据的时候根据ip获取自己的配置信息。

2.5 多线程和异步线程

这里配置线程池大小为30,可根据自己电脑配置修改.这里要注意多线程对共享变量互斥访问的问题,我这里只简单利用了下mysql的行级锁实现互斥访问

// 初始化线程池
@PostConstruct
private void initThreadPool() {
    if (executorService == null) {
        executorService = new ThreadPoolExecutor(30, 30, 0L, TimeUnit.MILLISECONDS,
                                                 new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.DiscardPolicy());
    }
}

异步线程这里默认关闭,想开启异步线程直接把注释@Async放开 

异步线程不是加个注解就完事了,有很多注意事项的,否则有时候异步线程根本就没生效,还是同步线程,并且往往不易发现。以下是异步线程不生效的常见情形:

  • @Async需要在不同类使用才会产生异步效果,方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的
  • 没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器管理
  • @SpringBootApplication启动类当中没有添加@EnableAsync注解
  • 异步方法使用注解@Async的返回值只能为void或者Future
  • 注解的方法必须是public方法。
  • 如果需要从类的内部调用,需要先获取其代理类

3 反爬踩到的坑 

  • 详情页需要登录 进入商品详情页需要登录,尝试过写脚本登录,但是京东登录有滑动滑块,识别物体等验证,机器暂时模拟不了人的行为验证信息。这个解决方案就是自己手动登录,然后复制cookie, 短信登录cookie有效期为30天,够用了。
  • 评论和价格爬不到数据 京东反爬,评论和价格是通过js脚本动态获取的,开始加载的页面没有这两项数据,所以要调接口单独获取
//http请求查询价格
public Map<String, Object> queryPrice(String ck, String id){
    HttpHeaders headers = new HttpHeaders();//header参数
    List<String> cookies = Arrays.asList(ck.split(";"));
    // header设置
    headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36");
    // cookie设置
    headers.put(HttpHeaders.COOKIE, cookies);
    HttpEntity<String> httpEntity = new HttpEntity(headers);

    String priceUrl = "https://p.3.cn/prices/mgets?callback=jQuery2414702&pduid=15282860256122085625433&pdpin=&skuIds=J_"+id;
    log.info("\n###### priceUrl:{}", priceUrl);
    ResponseEntity<String> priceResult = restTemplate.exchange(priceUrl, HttpMethod.GET, httpEntity, String.class);
    return getPrice(priceResult.getBody());
}
  • 不要爬太猛 一开始只有一个爬虫跑,爬取到的数据几乎没有异常数据。后面感觉这要太慢了,我想爬100万条商品数据,得加快进度。然后用了4台机器跑,3台云服务器24小时跑,1台本机闲置的时候就运行爬虫爬数据。后面估计京东检测到我访问异常了,应该是限流了,限流后偶尔就会爬到商品名称、价格、图片地址为空的异常数据 ,正常情况下这三项数据是不为空的。限流后我还不停止,继续4台机器跑,毕竟偶然会有异常数据,还是能爬到一些正常数据的,我再定期清理异常数据就行了。后来京东直接把我号限死了,我网页版的不能登录,一登录就闪退,app的能正常登录。大概过了一天,我的号又能登录了,估计是安全部门审核过,把我从拉黑又降级为限流了吧。现在限流后价格字段偶尔会爬不到数据,爬取到的其他字段无误。

 4. 总结

这次爬虫做得比较细致,因为爬到的数据要应用到自己做的一个电商项目。Java爬虫文档少,自己摸索的时候踩了不少坑,花了不少时间。爬虫还是python好用,以后更偏向于python写爬虫。

5.相关资料

由于平台不让随意放链接,审核一直不通过,我这里不放链接了。想要源码的进我个人主页“Kili学习网”,或者Github直接搜索项目,关键字就是博客标题

猜你喜欢

转载自blog.csdn.net/m0_70140421/article/details/124852475