赞
踩
本文仅用于学习知识探讨,绝无其它恶意。
前两篇基础文章链接:
本文打算再详细的讲讲一些流程细节,另外,最后有写到如何分析爬取下来的内容。
在开始正文之前,还要说清一件事:我是小白,能不能学会爬虫?
答:学不会,别学了,放弃吧。赶紧拿起手机,打游戏吧。这么热的天,哪凉快哪去,千万别遭这个罪。
我所理解的静态网页就是,浏览器中右键查看源代码,源码内容与用户看到的内容一致;动态网页就是,源码中找不到用户所看到的内容(部分)。
这就有意思了,源码中都没有的内容,那是怎么呈现出来的?答案就是通过二次加载甚或多次加载得到。
存在即合理,简单想想就能想通,倘若我点进去一个网页,加载半天一片空白,很大概率我是会不耐烦的。所以势必要一部分一部分的加载进去,有些东西还需要我点击“加载更多“之后才会加载。
因为是全英文网站,可能有些人会产生抵触的心理。但是,类名总能看懂吧:Code Samples地址
这上面的代码我挨个运行过一遍,最后用到我这个项目里的只需要上面红框4个即可。
有些人吃包子还需要用筷子夹,我向来都是用手抓 :)
“抓包”这个动词看起来很神秘,高深莫测。其实,就是看网页。东北话:瞅!瞪大眼睛瞅就可以成为抓包了,只不过需要瞅对地方。这个对的地方就是浏览器开发者工具,谷歌浏览器是右键->检查;火狐浏览器是右键->查看元素;搜狗、360浏览器是右键->审查元素;或者,统统按F12。推荐使用Chrome浏览器,因为好用,另外cdp4j框架使用前提就是安装Chrome浏览器。这里有个坑,请认准Chrome官网下载地址和官方图标:除此图标之外,任何图标都是假的Chrome。配色不一样、中间不是蓝点等等,都是假的Chrome。
Chrome浏览器安装好并不能使用,需要点右上角的三个点,然后选设置,或者直接在地址栏输入chrome://settings/
将搜索引擎设置为国内的搜索引擎,谷歌浏览器不能用谷歌搜索引擎,说起来也是令人欷歔(不知道国外的百度浏览器能否使用百度搜索引擎 : )
进入开发者工具后,刷新一下页面,然后先点击Network再点击Type按响应类型排序,如下图:
现在这个过程就叫“抓包”,至于能不能抓到,那看你有没有那个眼力劲了。
当我们点击Type之后,Network响应的数据就会进行按类别进行排序。因为动态网页和静态网页的区别就在于数据不是一口气加载进来的,而是先加载浏览器地址栏URL,响应后再加载对应的脚本去后台获取JSON数据,解析JSON数据填充到页面中进行渲染。既然是脚本,当然需要关注script类型的响应了,如下图:
如果不能一眼看出来这些script类型响应了什么内容,可以逐个点击,之后会弹出一个展示框,选中Preview。最终,我发现了新闻列表是通过下图中红框的链接得到的:
至此,网易新闻·国内新闻列表这个“包”,已经被我们“抓”到了。单次抓包结束!
不过,爬虫程序逻辑还没完。
如果往下浏览网页,当翻到距离底部不远的时候,网易新闻(国内)会再次加载新闻列表。这时候Network下对应就出现了新的响应内容。
直到出现 :-)已经到最后啦~, 就代表我们只能获取这么多。每次加载70篇新闻,总共可以加载3次。也就是说,我们只能获得网易新闻(国内)最近的3*70=210篇新闻,时间是近7天。对于“新闻”来说,210篇、近7天,差不多就够了,再多就不新了(我曾想挣扎一下拿到更久远的数据,能力有限,没整出来 :)
我们可以对每个响应逐个右键,Copy,Copy link address
我已经给整出来了,瞪大眼睛瞅瞅有啥规律?
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
主要代码如下:
通过这个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显然是爬取静态网页中的哥哥!
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 ""; }
通过上面代码就可以拿到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.toJson(ints);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。