文章目录

  • 使用Java实现爬虫
    • 一、HttpClient实现模拟HTTP访问
      • 1.1 HttpClient
      • 1.2 引入依赖
      • 1.3 创建简单的请求操作
        • 1.3.1 创建实例
        • 1.3.2 Jsoup应用
      • 1.4 爬取过程中可能出现的问题
        • 1.4.1 JS异步加载问题
        • 1.4.2 反爬技术的影响
      • 1.5 爬取需要登录的页面
        • 1.5.1 在header中直接携带Cookie
        • 1.5.2 模拟登录自动获取Cookie
    • 二、HtmlUtil实现JS异步加载页面
      • 2.1 HtmlUtil
      • 2.2 引入依赖
      • 2.3 创建简单的请求操作
        • 2.3.1 创建实例
        • 2.3.2 模拟浏览器操作
      • 2.4 爬取待登录的页面

使用Java实现爬虫

一、HttpClient实现模拟HTTP访问

1.1 HttpClient

HTTP 协议是 Internet 上使用得最多、最重要的协议之一,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。Commons HttpClient项目现已终止,不再开发。 它已被Apache HttpComponents项目里的HttpClient和HttpCore模块取代,它们提供了更好的性能和更大的灵活性。

1.2 引入依赖

<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>${httpclient.version}</version>
</dependency>
<dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>${jsoup.version}</version>
</dependency>

向项目中引入HttpClient和Jsoup依赖。

Jsoup用于解析获取的HTML文本,可以像JS一样通过id和class获取元素。同时Jsoup也可访问页面。

1.3 创建简单的请求操作

1.3.1 创建实例

public void testLinked() throws Exception {// 创建HttpClient对象CloseableHttpClient httpClient = HttpClients.createDefault();// 创建GET请求HttpGet httpGet = new HttpGet("https://blog.csdn.net/weixin_43347659");httpGet.setHeader("use-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36");// 获取响应结果CloseableHttpResponse response = httpClient.execute(httpGet);if (response.getStatusLine().getStatusCode() == 200) {String html = EntityUtils.toString(response.getEntity(), "UTF-8");System.out.println(html);}httpClient.close();response.close();
}

HttpClient用于创建连接对象,如果请求方式为GET则可以创建HttpGet对象,若为POST请求可创建HttpPost对象,请求的参数为待访问的URL。

可以根据实际请求内容适当的增加header的内容。调用HttpClientexecute()方法发起请求,并创建一个CloseableHttpResponse响应对象,可以通过判断响应状态码确定请求的结果。

根据现在的一些防爬虫设置,可能需要在header添加固定的请求内容,例如hostorigin等内容区分人机,可根据实际情况设置。

1.3.2 Jsoup应用

@Test
public void testJsoup() throws Exception {// 创建HttpClientCloseableHttpClient httpClient = HttpClients.createDefault();// 创建GET请求HttpGet httpGet = new HttpGet("https://www.cnblogs.com/sam-uncle/category/1469093.html");httpGet.setHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36");// 获取响应CloseableHttpResponse response = httpClient.execute(httpGet);// 获取页面内容if (response.getStatusLine().getStatusCode() == 200) {String html = EntityUtils.toString(response.getEntity(), "UTF-8");// 创建Document对象Document document = Jsoup.parse(html);// 获取博客列表Element blog = document.getElementsByClass("entrylist").first();Elements blogList = blog.getElementsByClass("entrylistItem");for (Element element : blogList) {Elements title = element.select("a[class='entrylistItemTitle'] span");System.out.println(title.text());}}response.close();httpClient.close();
}

通过调用Jsoupparse(String html)方法即可将原始的HTML页面解析为Document类,这样我们就能够通过getElementById(String attr)getElementsByClass(String attr)select(String classAttr)等方式获取页面中的标签元素。

Document类为org.jsoup.nodes.Document注意不要使用错类。

1.4 爬取过程中可能出现的问题

1.4.1 JS异步加载问题

随着前端技术的发展,在页面中应用AJAX、VUE和AngularJS等技术已经很普及,因此在使用HttpClient时会发现,响应的结果与页面不相同,或者响应的页面并没有所需的内容。

因此可以从其他的思路来实现,例如我们可以通过访问内部接口获取响应值,通过这种方法可以跳过对页面的分析,直接获取想要的结果。主要难点在于分析该内容调用的接口。

例如我们查看CSDN的博客页面,点击搜索框可看到CSDN会推送热门的搜索信息,但是如果查看当前页面的网页源码是无法搜索到该内容的。

此时我们可以打开F12,查看页面的所有请求

此时我们可以只选择Fetch/XHR查看页面所有调用的接口,从中找到正确的接口。根据实际请求中携带参数和header的信息,编写代码。

@Test
public void testApi() {CloseableHttpClient httpClient = HttpClients.createDefault();HttpGet httpGet = new HttpGet("https://silkroad.csdn.net/api/v2/assemble/list/channel/search_hot_word?new_hot_flag=1&channel_name=pc_hot_word&size=20&user_name=weixin_43347659&platform=pc&imei=10_20960811560-1623721797026-245775");httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36");httpGet.setHeader("Accept", "application/json, text/javascript, */*; q=0.01");httpGet.setHeader("Content-Type", "application/json;charset=UTF-8");httpGet.setHeader("Origin", "https://blog.csdn.net");httpGet.setHeader("Accept-Encoding", "gzip, deflate, br");try {CloseableHttpResponse response = httpClient.execute(httpGet);if (response.getStatusLine().getStatusCode() == 200) {System.out.println(EntityUtils.toString(response.getEntity(), "UTF-8"));}} catch (IOException e) {e.printStackTrace();} finally {response.close();}httpClient.close();
}

一般勾选保留日志停用缓存已防止页面发生重定向时丢失以前的请求内容。

1.4.2 反爬技术的影响

具体可查看知乎贴做爬虫怎可不知反爬虫?如何做反反爬虫。

1.5 爬取需要登录的页面

当需要获取登录后的页面信息时,就绕不开Cookie的问题。在请求时携带正确的Cookie值可直接跳过登录操作。该问题可通过两种方案解决。

1.5.1 在header中直接携带Cookie

在设置请求头时,可以直接绑定Cookie值,该Cookie值可以通过实际访问时查看请求内容获取,示例:

@Test
public void testCookie() throws Exception {CloseableHttpClient httpClient = HttpClients.createDefault();HttpGet httpGet = new HttpGet("https://mall.csdn.net/myorder?spm=1001.2014.3001.5137");httpGet.setHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36");httpGet.setHeader("Cookie", "yourCookie");CloseableHttpResponse response = httpClient.execute(httpGet);if (response.getStatusLine().getStatusCode() == 200) {System.out.println("==============================开始打印页面==============================");System.out.println(EntityUtils.toString(response.getEntity()));System.out.println("==============================结束打印页面==============================");}httpClient.close();response.close();
}

对于携带Cookie的方式登录存在一个问题,就是cookie存在有效期,当有效期过了之后就需要重新更换cookie,所以如果需要持续性的自动爬取数据,就存在很大弊端。

1.5.2 模拟登录自动获取Cookie

在发送请求时可以将登录信息添加到HttpPost中去尝试请求登录,如果登录成功,登录后的Cookie会保留在HttpClient中,再请求其他页面时则会跳过登录。

以CSDN的登录为例,通过F12查找登录接口,根据请求头信息,配置HttpPost

@Test
public void testLogin() throws Exception {CloseableHttpClient httpClient = HttpClients.createDefault();HttpPost httpPost = new HttpPost("https://passport.csdn.net/v1/register/pc/login/doLogin");httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36");httpPost.setHeader("Accept", "application/json, text/plain, */*");httpPost.setHeader("Accept-Encoding", "gzip, deflate, br");httpPost.setHeader("Accept-Language", "zh-CN,zh;q=0.9");httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");httpPost.setHeader("Host", "passport.csdn.net");httpPost.setHeader("Origin", "https://passport.csdn.net");httpPost.setHeader("Referer", "https://passport.csdn.net/login?code=applets");// 配置登录参数List<NameValuePair> pairList = new ArrayList<NameValuePair>();pairList.add(new BasicNameValuePair("loginType", "1"));pairList.add(new BasicNameValuePair("pwdOrVerifyCode", "password"));pairList.add(new BasicNameValuePair("uaToken", ""));pairList.add(new BasicNameValuePair("userIdentification", "username"));pairList.add(new BasicNameValuePair("webUmidToken", ""));httpPost.setEntity(new UrlEncodedFormEntity(pairList, HTTP.UTF_8));CloseableHttpResponse response = httpClient.execute(httpPost);if (response.getStatusLine().getStatusCode() == 200) {System.out.println("登录成功");// 这里要注销请求,否则会影响后续的请求httpPost.abort();HttpGet httpGet = new HttpGet("https://mall.csdn.net/myorder?spm=1001.2014.3001.5137");httpGet.setHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36");CloseableHttpResponse response1 = httpClient.execute(httpGet);if (response1.getStatusLine().getStatusCode() == 200) {System.out.println("==============================开始打印页面==============================");System.out.println(EntityUtils.toString(response1.getEntity()));System.out.println("==============================结束打印页面==============================");}response1.close();}response.close();httpClient.close();
}

该案例为失败案例,由于登录方式多变,可能出现的验证码等人机校验,导致用户登录的难度加大,例如上述案例,直接访问登录接口后,会直接重定向到人机验证界面,导致无法正常登录。有些页面也存在在前端进行密码加密,导致无法获取正确的密码。

因此上述例子只是提供一个思路。

二、HtmlUtil实现JS异步加载页面

2.1 HtmlUtil

htmlunit 是一款开源的java 页面分析工具,读取页面后,可以有效的使用htmlunit分析页面上的内容。项目可以模拟浏览器运行,被誉为java浏览器的开源实现。是一个没有界面的浏览器,运行速度迅速。是junit的扩展之一。

2.2 引入依赖

<dependency><groupId>net.sourceforge.htmlunit</groupId><artifactId>htmlunit</artifactId><version>${htmlutil.version}</version>
</dependency>

2.3 创建简单的请求操作

2.3.1 创建实例

@Test
public void testLinked() {try (WebClient webClient = new WebClient(BrowserVersion.CHROME)) {webClient.getOptions().setThrowExceptionOnScriptError(false);//当JS执行出错的时候是否抛出异常webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);//当HTTP的状态非200时是否抛出异常webClient.getOptions().setActiveXNative(false);webClient.getOptions().setCssEnabled(false);//是否启用CSSwebClient.getOptions().setJavaScriptEnabled(true); //很重要,启用JSwebClient.setAjaxController(new NicelyResynchronizingAjaxController());//很重要,设置支持AJAX//开始请求网站HtmlPage loginPage = webClient.getPage("https://ent.sina.com.cn/film/");webClient.waitForBackgroundJavaScript(30000);//该方法阻塞线程System.out.println("=================开始打印页面=================");System.out.println(loginPage.asXml());System.out.println("=================结束打印页面=================");} catch (Exception e) {e.printStackTrace();}
}

对webClient的配置很重要,尤其是setAjaxController()方法,使得模拟页面可以支持AJAX异步加载。

对于Vue和AngularJS渲染的页面HtmlUtil在其处理上也不是太好,加载JS也只能加载原始页面中包含的内容,

2.3.2 模拟浏览器操作

HtmlUtil可以创建一个无界面的浏览器,所以可以通过代码对文本框赋值和进行点击操作,完成一些简单的操作。示例:

@Test
public void testSearch() {WebClient webClient = new WebClient(BrowserVersion.CHROME);// 设置当前的AJAX控制器webClient.setAjaxController(new NicelyResynchronizingAjaxController());// 设置CSS支持webClient.getOptions().setCssEnabled(false);// 设置JavaScript是否启用webClient.getOptions().setJavaScriptEnabled(true);// 设置ActiveX是否启用webClient.getOptions().setActiveXNative(false);// 设置访问错误时是否抛出异常webClient.getOptions().setThrowExceptionOnFailingStatusCode(true);// 设置JS报错时是否抛出异常webClient.getOptions().setThrowExceptionOnScriptError(false);try {HtmlPage htmlPage = webClient.getPage("https://www.csdn.net/");// 阻塞当前线程,直到指定时间后结束webClient.waitForBackgroundJavaScript(10*1000);// 获取搜索框HtmlInput search = (HtmlInput) htmlPage.getByXPath("//*[@id=\"toolbar-search-input\"]").get(0);search.setAttribute("value", "HtmlUtil用法");// 点击搜索HtmlButton button = (HtmlButton) htmlPage.getByXPath("//*[@id=\"toolbar-search-button\"]").get(0);HtmlPage newHtmlPage = button.click();System.out.println("=============打印页面=============");System.out.println(newHtmlPage.asXml());System.out.println("=============打印页面=============");} catch (IOException e) {e.printStackTrace();}}

该示例通过访问CSDN的首页,为搜索框赋值,操作点击后可获取搜索结果。

由于CSDN的搜索结果是通过Vue框架加载,所以无法获取到最终的结果。

2.4 爬取待登录的页面

操作与2.3.2节类似,主要思路为获取登录页面,为表单元素添加用户名和密码,再通过click()方法点击登录按钮,提交表单,登录成功后会自动将cookie存放在WebClient中,可通过WebClient再次访问其他需要登录的页面。

相较于HttpClient,HtmlUtil可以直接操作页面比访问接口相对容易,但是针对于Vue框架和需要验证码登录的页面还是存在问题。


参考资料:

HtmlUnit 官网

HtmlUtil API文档

HttpClient 官网

HttpClient API文档

【Java】使用Java实现爬虫相关推荐

  1. java 搜索机制_Java爬虫搜索原理实现

    新人国庆没事做,又研究了一下爬虫搜索,两三天时间总算是把原理闹的差不多了,基本实现了爬虫搜索的原理,本次实现还是俩程序,分别是按广度优先和深度优先完成的,广度优先没啥问题,深度优先请慎用,有极大的概率 ...

  2. Java使用Jsoup写爬虫

    Java使用Jsoup写爬虫 安装Jsoup.jar 简单了解Jsoup Jsoup框架中的常用方法 动手实践 进阶写法 安装Jsoup.jar 1.首先我们打开Jsoup官网 2.按照图片这里下载 ...

  3. java爬虫拉勾网_[Java教程]node.js爬虫爬取拉勾网职位信息

    [Java教程]node.js爬虫爬取拉勾网职位信息 0 2017-03-14 00:00:21 简介 用node.js写了一个简单的小爬虫,用来爬取拉勾网上的招聘信息,共爬取了北京.上海.广州.深圳 ...

  4. 深圳java培训:Java也能做爬虫。

    深圳java培训:Java也能做爬虫. 现在提到爬虫人第一个想到的就是python,其实使用Java编写爬虫也是很好的选择, 下面给大家展示一个使用Java基础语言编写的爬取小说的案例: 实现功能: ...

  5. Java——通过Java代码启动批处理文件(一)

    作者专注于Java.架构.Linux.小程序.爬虫.自动化等技术. 工作期间含泪整理出一些资料,微信搜索[javaUp],回复 [java][黑客][爬虫][小程序][面试]等关键字免费获取资料.技术 ...

  6. 21天学会Java之(Java SE第十三篇):网络编程、TCP/UDP通信

    如今,计算机已经成为人们学习.工作.生活必不可少的工具.人们利用计算机可以和亲朋好友在网上聊天,玩网游或发邮件等,这些功能的实现都离不开计算机网络.计算机网络实现了不同计算机之间的通信,而这些必须依靠 ...

  7. 【软件创新实验室2021年寒假集训】Java技术培训——Java前置知识学习

    系列文章目录 [软件创新实验室2021年寒假集训]汇总篇 20级Java培训 第一天:[软件创新实验室2021年寒假集训]Java技术培训--Java前置知识学习 第二天:Java基础(一) 第三天: ...

  8. java unlimited_具有无限参数的Java方法(Java method with unlimited arguments)

    具有无限参数的Java方法(Java method with unlimited arguments) Spring框架使用方法,您可以根据需要传递尽可能多的参数. 我想写一个函数,也可以采取无限量的 ...

  9. 介绍java -cp java -jar的区别

    java -cp 和 -classpath 一样,是指定类运行所依赖其他类的路径,通常是类库,jar包之类,需要全路径到jar包,window上分号";" java -cp &am ...

  10. 【Java】Java连接Mysql数据库的demo示例

    [Java]Java连接Mysql数据库的demo示例 1.安装mysql数据库 2.下载java-mysql-connector.jar包 3.完成java配置 4.写java代码运行测试 1.安装 ...

最新文章

  1. keras 的 example 文件 mnist_sklearn_wrapper.py 解析
  2. Oracle中,使 CREATE TABLE AS SELECT 支持ORDER BY
  3. 32个程序员萌翻全场的瞬间!
  4. mysql怎么用命令行导出sql文件_使用mysql命令行导出sql_MySQL
  5. 常考数据结构与算法:子数组中的最大累加和问题
  6. Shell脚本之条件判断
  7. Facebook 开源了一整套重要的 Linux 内核组件与工具!
  8. Full_of_Boys训练4总结
  9. 软件开发人员进修必备的20本书
  10. (19)System Verilog模块设计示例
  11. Myeclipse J2EE Project, 折腾死我了。
  12. MATLAB——zeros
  13. 黑客入侵WinXP常用七个技巧
  14. laravel手册链接
  15. C语言的加减乘除函数
  16. 传播正能量——《海南英才》阅读的读后感2200字
  17. Word中如何快速删除页眉下的横线?教你一招,轻松解决!
  18. Redis 6 学习记录
  19. 看雪CTF2020 KCTF 秋季赛 签到题
  20. 一款通过人工智能AI计算无损放大图像软件

热门文章

  1. (第24讲)java小程序——Applet
  2. 《高质量程序设计指南:C++/C语言》图书信息
  3. 自定义viewFlipper
  4. Android常用应用市场包名
  5. 【verilog教程】第10篇:verilog代码规范
  6. 深度学习入门之二阶段小demo练习(持续更新系列)
  7. IT运维岗位可以分为哪几种?10个运维岗详解
  8. Python软件安装教程
  9. MFC学习——下检测计算机是否联网
  10. 彩虹查课插件 使用说明 网课查询插件 极速版