【Java】爬虫,能不能再详细讲讲?万字长文送给你!

前言

本文仅用于学习知识探讨,绝无其它恶意。

前两篇基础文章链接:

《【Java】爬虫,看完还爬不下来打我电话 》

《【Java】爬虫,数据持久化到MongoDB》

本文打算再详细的讲讲一些流程细节,另外,最后有写到如何分析爬取下来的内容。

在开始正文之前,还要说清一件事:我是小白,能不能学会爬虫?

答:学不会,别学了,放弃吧。赶紧拿起手机,打游戏吧。这么热的天,哪凉快哪去,千万别遭这个罪。

正文

静态与动态网页

​ 我所理解的静态网页就是,浏览器中右键查看源代码,源码内容与用户看到的内容一致;动态网页就是,源码中找不到用户所看到的内容(部分)。

​ 这就有意思了,源码中都没有的内容,那是怎么呈现出来的?答案就是通过二次加载甚或多次加载得到。

​ 存在即合理,简单想想就能想通,倘若我点进去一个网页,加载半天一片空白,很大概率我是会不耐烦的。所以势必要一部分一部分的加载进去,有些东西还需要我点击“加载更多“之后才会加载。

爬静态网页的流程

  1. 第一步就是学习框架,Java首推Jsoup框架
  2. 省省吧,静态网页没有第二步。
  3. 为了整体格式,这里要凑够三行。

爬动态网页的流程

  1. 第一步依然是学习框架,Java首推cdp4j框架
  2. 学会“抓包”。
  3. 可以了,爬虫就这么点东西,都凑不够三步。
第一步 学习cdp4j框架

​ 因为是全英文网站,可能有些人会产生抵触的心理。但是,类名总能看懂吧:Code Samples地址

cdp4j样例代码这上面的代码我挨个运行过一遍,最后用到我这个项目里的只需要上面红框4个即可。

第二步 学会“抓包“

​ 有些人吃包子还需要用筷子夹,我向来都是用手抓 :)

​ “抓包”这个动词看起来很神秘,高深莫测。其实,就是看网页。东北话:瞅!瞪大眼睛瞅就可以成为抓包了,只不过需要瞅对地方。这个对的地方就是浏览器开发者工具,谷歌浏览器是右键->检查;火狐浏览器是右键->查看元素;搜狗、360浏览器是右键->审查元素;或者,统统按F12。推荐使用Chrome浏览器,因为好用,另外cdp4j框架使用前提就是安装Chrome浏览器。这里有个坑,请认准Chrome官网下载地址官方图标:除此图标之外,任何图标都是假的Chrome。配色不一样、中间不是蓝点等等,都是假的Chrome。

​ Chrome浏览器安装好并不能使用,需要点右上角的三个点,然后选设置,或者直接在地址栏输入chrome://settings/

设置

​ 将搜索引擎设置为国内的搜索引擎,谷歌浏览器不能用谷歌搜索引擎,说起来也是令人欷歔(不知道国外的百度浏览器能否使用百度搜索引擎 : )

百度搜索引擎

进入开发者工具后,刷新一下页面,然后先点击Network再点击Type按响应类型排序,如下图:

抓包
​ 现在这个过程就叫“抓包”,至于能不能抓到,那看你有没有那个眼力劲了。

​ 当我们点击Type之后,Network响应的数据就会进行按类别进行排序。因为动态网页和静态网页的区别就在于数据不是一口气加载进来的,而是先加载浏览器地址栏URL,响应后再加载对应的脚本去后台获取JSON数据,解析JSON数据填充到页面中进行渲染。既然是脚本,当然需要关注script类型的响应了,如下图:

Script
​ 如果不能一眼看出来这些script类型响应了什么内容,可以逐个点击,之后会弹出一个展示框,选中Preview。最终,我发现了新闻列表是通过下图中红框的链接得到的:

新闻列表
​ 至此,网易新闻·国内新闻列表这个“包”,已经被我们“抓”到了。单次抓包结束!

​ 不过,爬虫程序逻辑还没完。

​ 如果往下浏览网页,当翻到距离底部不远的时候,网易新闻(国内)会再次加载新闻列表。这时候Network下对应就出现了新的响应内容。
加载更多
​ 直到出现 :-)已经到最后啦~, 就代表我们只能获取这么多。每次加载70篇新闻,总共可以加载3次。也就是说,我们只能获得网易新闻(国内)最近的3*70=210篇新闻,时间是近7天。对于“新闻”来说,210篇、近7天,差不多就够了,再多就不新了(我曾想挣扎一下拿到更久远的数据,能力有限,没整出来 :)
加载全部
​ 我们可以对每个响应逐个右键,Copy,Copy link address
copy
我已经给整出来了,瞪大眼睛瞅瞅有啥规律?

https://temp.163.com/special/00804KVA/cm_guonei.js?callback=data_callback   (第一次加载)
https://temp.163.com/special/00804KVA/cm_guonei_02.js?callback=data_callback(第二次加载)
https://temp.163.com/special/00804KVA/cm_guonei_03.js?callback=data_callback(第三次加载)

​ 规律就是都包含这个正则表达式

"cm_guonei[_0-9]*\\.js"

​ 但是,等等,我们是不是忽略了一个重要的事情,就是,怎么才能获取到响应的url?

​ 两种方式:一、手动“抓包”,然后写死在程序中。二、手动“抓包”,然后使用cdp4j进行获取Network响应URL。

​ 为了锻炼锻炼我的正则表达式 能力,我选择了后者。

​ 还记得我在一开始用红色框标记的4个官方样例吗?NetworkResponse.java

​ 主要代码如下:
NetworkResponse

​ 通过这个Demo我们就能拿到后台响应的url链接了,再用正则"cm_guonei[_0-9]*\.js"进行匹配就能筛选出我们想要的URL链接。

​ 一旦正则匹配成功,我们就可以自己手动构造3个url链接。下面代码纯属我写着玩,大家完全可以分析出来后手动写死,没必要费这么大劲。

// 获取Network响应的新闻列表url
public static List<String> getNetworkResponseNewsListUrl(String url){
   
    Launcher launcher = new Launcher();
    List<String> newsListUrlList = new ArrayList<>();
    try (SessionFactory factory = launcher.launch(asList("--disable-gpu", "--headless"))) {
   
        String context = factory.createBrowserContext();
        try (Session session = factory.create(context)) {
   
            // 连通网络
            session.getCommand().getNetwork().enable();
            // 事件监听器
            session.addEventListener(new EventListener() {
   
                @Override
                public void onEvent(Events event, Object value) {
   
                    if (NetworkResponseReceived.equals(event)) {
   
                        ResponseReceived rr = (ResponseReceived) value;
                        Response response = rr.getResponse();
                        // 从response对象中获得url
                        String newsListUrl = response.getUrl();
                        // 正则匹配,检测网站 https://regex101.com 匹配3条约用时2ms
                        Pattern pattern = Pattern.compile("cm_guonei[_0-9]*\\.js");
                        Matcher matcher = pattern.matcher(newsListUrl);
                        if (matcher.find()) {
   
                            newsListUrlList.add(newsListUrl);
                            newsListUrl = newsListUrl.replaceFirst("cm_guonei[_0-9]*\\.js","cm_guonei_02.js");
                            newsListUrlList.add(newsListUrl);
                            newsListUrl = newsListUrl.replaceFirst("cm_guonei[_0-9]*\\.js","cm_guonei_03.js");
                            newsListUrlList.add(newsListUrl);
                        }
                    }
                }
            });
            // 监听器写在导航之前
            // 一定要有连接超时设置,且不小于2s(多次测试得出结论,具体时间和网速有关系),否则无法获取
            session.navigate(url).wait(5 * 1000).waitDocumentReady(5 * 1000);
        }
        // 处理浏览器上下文,源码:contexts.remove(browserContextId)
        factory.disposeBrowserContext(context);
    }
    // 关闭后台进程,由于历史原因,关闭进程习惯使用kill
    launcher.getProcessManager().kill();
    return newsListUrlList;
}

​ 拿到获取新闻列表的响应链接后,我们就可以使用静态网页工具Jsoup进行获取网页了。这里或许会有疑问,为啥又是cdp4j,又是Jsoup来回混用呢?答案很简单,哪个工具擅长什么就使用哪个呗。

​ Jsoup显然是爬取静态网页中的哥哥!

获取Network响应的新闻列表JSON串
public static String getNewsListJsonStr(String url) throws IOException {
   
    // 使用parse,请求连接时指明编码GBK(试出来的),否则获取的内容会乱码
    // 参考:https://www.sojson.com/blog/225.html
    org.jsoup.nodes.Document doc = Jsoup.parse(new URL(url).openStream(), "GBK", url);
    String body = doc.text();

    // 零宽断言,从body上截取路径data_callback( )圆括号内数据
    Pattern pattern = Pattern.compile("(?<=data_callback\\()(.*)(?=\\))");
    Matcher matcher = pattern.matcher(body);
    while(matcher.find()){
   
        return matcher.group();
    }
    return "";
}

解析JSON

​ 通过上面代码就可以拿到Network返回的JSON数据了,之后,我们可以先去一个在线网站解析一下,复制一下JSON内容区这个网站:在线解析JSON

​ 可以发现,最外层是1个JSONArray,里面嵌套70个JSONObject

嵌套


JSON总体可以分为对象、集合(数组)两大类,二者可以任意嵌套。

常用的工具就是Google的Gson,两大功能:序列化、反序列化,官方给出教程如下

序列化,即对象转为Json字符串

反序列化,即Json字符串转为对象

Object序列化与反序列化

// 序列化
UserSimple userObject = new UserSimple(  
    "Norman", 
    "norman@futurestud.io", 
    26, 
    true
);

Gson gson = new Gson();  
String userJson = gson.toJson(userObject);  
/*
结果 ==>
"{
  "age": 26,
  "email": "norman@futurestud.io",
  "isDeveloper": true,
  "name": "Norman"
}"
*/

// 反序列化
String userJson = "{'age':26,'email':'norman@futurestud.io','isDeveloper':true,'name':'Norman'}";  

Gson gson = new Gson();  
UserSimple userObject = gson.fromJson(userJson, UserSimple.class);  
// 结果 ==> 就是上面那个userObject

Array序列化与反序列化

int[] ints = {
   1, 2, 3, 4, 5};
String[] strings = {
   "abc", "def", "ghi"};

Gson gson = new Gson();
// 序列化
gson.
  • 9
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值