WebMagic抓取前端Ajax渲染的页面

本文介绍如何使用WebMagic爬虫处理现代网页中由Ajax动态加载的内容,讲解了针对JavaScript渲染页面的抓取策略和技术要点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

抓取前端渲染的页面

随着AJAX技术不断的普及,以及现在AngularJS这种Single-page application框架的出现,现在js渲染出的页面越来越多。对于爬虫来说,这种页面是比较讨厌的:仅仅提取HTML内容,往往无法拿到有效的信息。那么如何处理这种页面呢?总的来说有两种做法:
在抓取阶段,在爬虫中内置一个浏览器内核,执行js渲染页面后,再抓取。这方面对应的工具有Selenium、HtmlUnit或者PhantomJs。但是这些工具都存在一定的效率问题,同时也不是那么稳定。好处是编写规则同静态页面一样。
因为js渲染页面的数据也是从后端拿到,而且基本上都是AJAX获取,所以分析AJAX请求,找到对应数据的请求,也是比较可行的做法。而且相对于页面样式,这种接口变化可能性更小。缺点就是找到这个请求,并进行模拟,是一个相对困难的过程,也需要相对多的分析经验。
对比两种方式,我的观点是,对于一次性或者小规模的需求,用第一种方式省时省力。但是对于长期性的、大规模的需求,还是第二种会更靠谱一些。对于一些站点,甚至还有一些js混淆的技术,这个时候,第一种的方式基本是万能的,而第二种就会很复杂了。

对于第一种方法,webmagic-selenium就是这样的一个尝试,它定义了一个Downloader,在下载页面时,就是用浏览器内核进行渲染。selenium的配置比较复杂,而且跟平台和版本有关,没有太稳定的方案。感兴趣的可以看我这篇博客:使用Selenium来抓取动态加载的页面

这里我主要介绍第二种方法,希望到最后你会发现:原来解析一个前端渲染的页面,也没有那么复杂。这里我们以AngularJS中文社区http://angularjs.cn/为例。

如何判断前端渲染
判断页面是否为js渲染的方式比较简单,在浏览器中直接查看源码(Windows下Ctrl+U,Mac下command+alt+u),如果找不到有效的信息,则基本可以肯定为js渲染。

angular-view

angular-source

这个例子中,在页面中的标题“有孚计算机网络-前端攻城师”在源码中无法找到,则可以断定是js渲染,并且这个数据是AJAX得到。
分析请求
下面我们进入最难的一部分:找到这个数据请求。这一步能帮助我们的工具,主要是浏览器中查看网络请求的开发者工具。

以Chome为例,我们打开“开发者工具”(Windows下是F12,Mac下是command+alt+i),然后重新刷新页面(也有可能是下拉页面,总之是所有你认为可能触发新数据的操作),然后记得保留现场,把请求一个个拿来分析吧!

这一步需要一点耐心,但是也并不是无章可循。首先能帮助我们的是上方的分类筛选(All、Document等选项)。如果是正常的AJAX,在XHR标签下会显示,而JSONP请求会在Scripts标签下,这是两个比较常见的数据类型。

然后你可以根据数据大小来判断一下,一般结果体积较大的更有可能是返回数据的接口。剩下的,基本靠经验了,例如这里这个"latest?p=1&s=20"一看就很可疑…

angular-ajax-list

对于可疑的地址,这时候可以看一下响应体是什么内容了。这里在开发者工具看不清楚,我们把URLhttp://angularjs.cn/api/article/latest?p=1&s=20复制到地址栏,重新请求一次(如果用Chrome推荐装个jsonviewer,查看AJAX结果很方便)。查看结果,看来我们找到了想要的。

json

同样的办法,我们进入到帖子详情页,找到了具体内容的请求:http://angularjs.cn/api/article/A0y2。

编写程序
回想一下之前列表+目标页的例子,会发现我们这次的需求,跟之前是类似的,只不过换成了AJAX方式-AJAX方式的列表,AJAX方式的数据,而返回数据变成了JSON。那么,我们仍然可以用上次的方式,分为两种页面来进行编写:

数据列表

在这个列表页,我们需要找到有效的信息,来帮助我们构建目标AJAX的URL。这里我们看到,这个_id应该就是我们想要的帖子的id,而帖子的详情请求,就是由一些固定URL加上这个id组成。所以在这一步,我们自己手动构造URL,并加入到待抓取队列中。这里我们使用JsonPath这种选择语言来选择数据(webmagic-extension包中提供了JsonPathSelector来支持它)。

 if (page.getUrl().regex(LIST_URL).match()) {
     //这里我们使用JSONPATH这种选择语言来选择数据
     List<String> ids = new JsonPathSelector("$.data[*]._id").selectList(page.getRawText());
     if (CollectionUtils.isNotEmpty(ids)) {
         for (String id : ids) {
             page.addTargetRequest("http://angularjs.cn/api/article/"+id);
         }
     }
 }
目标数据

有了URL,实际上解析目标数据就非常简单了,因为JSON数据是完全结构化的,所以省去了我们分析页面,编写XPath的过程。这里我们依然使用JsonPath来获取标题和内容。

 page.putField("title", new JsonPathSelector("$.data.title").select(page.getRawText()));
 page.putField("content", new JsonPathSelector("$.data.content").select(page.getRawText()));
这个例子完整的代码请看AngularJSProcessor.java

总结
在这个例子中,我们分析了一个比较经典的动态页面的抓取过程。实际上,动态页面抓取,最大的区别在于:它提高了链接发现的难度。我们对比一下两种开发模式:

后端渲染的页面

下载辅助页面=>发现链接=>下载并分析目标HTML

前端渲染的页面

发现辅助数据=>构造链接=>下载并分析目标AJAX

对于不同的站点,这个辅助数据可能是在页面HTML中已经预先输出,也可能是通过AJAX去请求,甚至可能是多次数据请求的过程,但是这个模式基本是固定的。

但是这些数据请求的分析比起页面分析来说,仍然是要复杂得多,所以这其实是动态页面抓取的难点。

本节这个例子希望做到的是,在分析出请求后,为这类爬虫的编写提供一个可遵循的模式,即发现辅助数据=>构造链接=>下载并分析目标AJAX这个模式。

PS:

WebMagic 0.5.0之后会将Json的支持增加到链式API中,以后你可以使用:

page.getJson().jsonPath("$.name").get();
这样的方式来解析AJAX请求了。

同时也支持

page.getJson().removePadding("callback").jsonPath("$.name").get();
这样的方式来解析JSONP请求。

原文链接 http://webmagic.io/docs/zh/posts/chx-cases/js-render-page.html

public class JobProcessor implements PageProcessor { private String url = "https://search.51job.com/list/000000,000000,0000,00,9,99,java,2,1.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare="; @Override public void process(Page page) { //先获取script标签,为了拿到我们想要的那个script标签,使用正则表达式筛选(看script中包含什么独有的内容) String jobinfo = page.getHtml().css("script").regex(".*SEARCH_RESULT.*").get(); // 解析拿到json字符串 jobinfo = jobinfo.substring(jobinfo.indexOf("{"), jobinfo.lastIndexOf("}") + 1); // 创建json对象 JSONObject jsonObject = (JSONObject) JSONObject.parse(jobinfo); // 根据分析拿到放置信息的数组,这里根据你获取的json字符串格式来判断是转化为数组还是直接操作字符串 JSONArray resArr = jsonObject.getJSONArray("engine_search_result"); } private Site site = Site.me() .setCharset("utf-8") .setTimeOut(10 * 1000) .setRetryTimes(3) .setRetrySleepTime(3000); @Override public Site getSite() { return site; } //initialDelay任务启动后多久执行方法 //fixedDelay每隔多久执行方法 @Scheduled(initialDelay = 1000, fixedDelay = 100 * 1000) public void process() { Spider.create(new JobProcessor()) .addUrl(url) .thread(10) .run(); }}123456789101112131415161718192021222324252627282930313233343536372.从Ajax接口获取数据,也是重点记录的一种情况首先查看网页源代码发现没有我们想要的内容f12打开控制台,选择network选项卡,选择XHR刷新页面,获取Ajax请求信息,找到返回数据是我们需要的Ajax接口,如果无法直接访问,即直接复制Ajax请求路径到浏览器打开无法获取数据,则需要该分析Ajax的请求头这里简单分析一个链接https://aiqicha.baidu.com/yuqing/latestLyricalAjax?p=1&type=list&_=16293758710001很明显的看到前面都是固定的,p=1&type=list是分页,1629375871000明显是时间戳。url分析完,我们就知道抓取页面时怎么拼接url了。其他的一些请求头设置如下,有的Ajax接口有访问限制,需要设置请求头。@Componentpublic class JobProcessor implements PageProcessor { private String url = "https://aiqicha.baidu.com/yuqing/latestLyricalAjax?p=1&type=list&_="+System.currentTimeMillis()/1000+"000"; @Override public void process(Page page) { //这里就可以获取所需数据了,具体页面解析这里就不说了 System.out.println(page.getHtml()); //这里使用webmagic-extension中自带的JsonPathSelector来获取 List<String> list = new JsonPathSelector("$.data[*]").selectList(page.getRawText()); } //这里的Ajax接口有访问限制,所以需要设置请求头,请求头的设置根据分析的请求头来 private Site site = Site.me() .setCharset("gbk") .setTimeOut(10 * 1000) .setRetryTimes(3) .addHeader("Accept-Encoding", "/") .addHeader("X-Requested-With","XMLHttpRequest") .addHeader("Referer","https://aiqicha.baidu.com/yuqing/latestlyricallist") .setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36") .setRetrySleepTime(3000);
最新发布
04-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值