eclipse+springboot开发的爬虫项目

编程入门 行业动态 更新时间:2024-10-25 14:30:27

eclipse+springboot开发的<a href=https://www.elefans.com/category/jswz/34/1770264.html style=爬虫项目"/>

eclipse+springboot开发的爬虫项目

前段时间被导师要求做一个小项目,要求是在eclipse上开发一个springboot爬虫项目,首先我算是半个小白,其次eclipse用的比较少,所以在环境配置过程中不可避免的遇到了一些问题。

文章目录

  • 环境配置
    • eclipse
    • Spring Tool Suite
    • 创建第一个SpringBoot项目
    • 整合Swagger2
    • 整合Lombok
    • 关于日志的jar包冲突问题
  • 爬虫编写
    • 前置知识
    • 开源框架
    • 代码实现

环境配置

eclipse

本着“用新不用旧”的原则,果断抛弃了以前用的氧气版本,选择了2019版本(不知道为什么2019版本就看不到代号了,其实放弃氧气版本主要是因为marketplace里搜不到STS,也不知道为啥)。

Spring Tool Suite

也就是STS,也有翻译为springsource tool suite的,这是一个eclipse为spring专门定制的插件,用来快速创建一个springboot项目,可以在marketplace里搜索安装。这里卡了我很久,因为下载太慢,后来去官网下载还是太慢,所以只能去找了百度云资源,链接在这里: (提取码:iai0)。
注意要下载与自己版本对应的包,然后install到eclipse里面,如何知道自己的eclipse版本呢,Help->About Eclipse IDE就行了。当然如果你安装的时候还是很慢,可以把压缩包里的IDE文件一个一个的分批安装。

创建第一个SpringBoot项目

这个有很多博客都详细解说了,可以创建一个maven项目然后导入springboot的依赖,也可以直接用刚刚安装的sts创建一个spring starter project。这里贴一个别人的教程。值得一提的是,在创建项目中要注意勾选web,否则可能启动会报错。
如果使用sts创建的springboot项目,那么pom文件里的内容是写好的,如果是maven项目那就自己配置一下咯。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0" xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo02</artifactId><version>0.0.1-SNAPSHOT</version><name>demo01</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.7.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

创建完之后就可以写一个测试的接口,首先新建两个包,controller和config。

我这里另外创建了一个client包用来放启动类,其实没有必要,另外这里有个小坑。
现在controller包里面创建一个Demo01Controller,然后随意的写上一个接口。

@RestController
@RequestMapping("/test")
public class Demo01Controller {@GetMapping("hello")public String hello() {return "hello world";}
}

tips:@RestController注解自动添加@Controller和@ResponseBody

写完之后就可以右键启动类点击spring boot app启动项目,控制台里出现如下信息就表示启动成功。

tips:这个springboot项目是由内置tomcat启动的,默认是8080端口,端口可以自行修改。
启动之后,可以用postman来测试一下。

现在说说我之前提到的一个小坑,那就是如果把启动类单独放在一个包下面,那么这时候接口测试就很有可能会报404,原因就在于启动类虽然没有写@ComponentScan注解,但是它的bean扫描依然是默认的其所在包及其子包下,所以你需要指定它的包扫描路径来解决这个问题。

@SpringBootApplication
@ComponentScan("com.example.demo")
public class Demo01Application {public static void main(String[] args) {SpringApplication.run(Demo01Application.class, args);}
}

整合Swagger2

swagger2是现在比较常用的一个API说明和测试框架,这里贴一个介绍它的博客,这里面有详细的介绍和配置信息,跟着做就好了。
首先是导入依赖,导入之后maven update一下。

<!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.7.0</version></dependency>

然后在config包下创建一个Swagger2Config配置类,下面是我的配置信息。

@Configuration
@EnableSwagger2
public class Swagger2Config {/*** 创建API应用 apiInfo() 增加API相关信息* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,* 本例采用指定扫描的包路径来定义指定要建立API的目录。* * @return*/@Beanpublic Docket webApiConfig() {return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.example.demo.controller")).paths(PathSelectors.any()).build();}/*** 创建该API的基本信息(这些基本信息会展现在文档页面中) 访问地址:http://项目实际地址/swagger-ui.html* * @return*/private ApiInfo webApiInfo() {return new ApiInfoBuilder().title("API文档").description("本文档描述了课程中心微服务接口定义").version("1.0").contact(new Contact("Gollsy", "localhost", "E-mail Address")).build();}
}

然后在controller里面添加API说明信息,如下。

@RestController
@RequestMapping("/test")
@Api(tags = "测试模块")
public class Demo01Controller {@ApiOperation(value = "测试接口")@GetMapping("hello")public String hello() {return "hello world";}
}

最后启动项目,进入网址则可看到swagger页面。

点击 “try it out”,查看测试结果。

到这里,swagger的搭建就基本完成了。

整合Lombok

我有预感我之后可能并不会使用到Lombok插件,但是在之前的学习的过程中我还是遇到了lombok注解失效的问题,所以记录下来以防某一天忘掉。eclipse整合lombok要麻烦一些,在开发过程中即便在pom.xml里导入了jar包,你可能依然无法使lombok注解生效,这是非常令人恼火的。解决办法就是进入到你maven本地仓库里找到org.projectlombok包,命令行或右键运行lombok-[version].jar,然后安装到你的eclipse.exe里,最后重启eclipse就可以使得lombok注解生效。
当然,你也可以参考这篇博客进行设置。

关于日志的jar包冲突问题

我并不是非常了解logback或者log4j,通常都是从之前的项目中拉过来改改就用,之前或许也遇到了这样的问题,很显然我当时并没有在意。
警告是这样的:

(并没有及时截图,找的一个相似的报错)
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See .html#multiple_bindings for an explanation.

简单来说就是路径里包含了多个SLF4J的绑定,如果你需要知道错误的根本原因,可以去看看别人的解析。
解决方法则很简单,你可以在idea中非常直观的在maven依赖树中找到报错信息中提到的jar包然后将它exclude掉,也可以在eclipse中通过pom.xml的Dependency Hierarchy视图来将冲突的jar包给exclude掉。

爬虫编写

关于需要爬取的网页内容,导师并没有很多的限制,只要是上海证券交易所(www.sse)、深圳证券交易所(www.szse)和中国货币网(www.chinamoney)的新闻页面即可。其实还有东方财富网的内容,但是刚打开首页差点把我劝退,再打开源码一看,尤其是那满屏的css样式,我整个人就直接原地360度螺旋起飞,虽然仔细分析起来并不复杂,但是数据太多,我又有选择困难症,所以就先放着,等项目完成了再把内容补上。

前置知识

如果你跟我一样是第一次写爬虫或者作为初学者想对爬虫有更多基础性的了解,那不妨看看钱洋和姜元春的《网络数据采集技术——Java网络爬虫实战》,里面从http协议基础到开源框架的使用都有着比较详细的介绍,而且时间比较新。

开源框架

WebMagic是我接触到的第一个开源爬虫框架,其功能覆盖了网络爬虫的整个生命周期,包括URL提取、网页内容下载、网页内容解析和数据存储,并且支持多线程和URL去重。
WebMagic 依赖的 jar 包有 HttpClient(网页请求开源库)、Jsoup/Xsoup(HTML
解析)、slf4j/log4j(配置日志)、Fastjson(JSON 数据解析)、jedis/commons-pool(Redis
数据库操作)等。WebMagic 的核心模块包括 URL 维护/调度模块(Scheduler)、页面下载模块
(Downloader)、URL 提取和页面分析模块(Processor)、数据解析模块(Selector)、
数据存储模块(Pipeline)、代理操作模块(Proxy)。
虽然maven仓库中的webmagic最新版本还是在2017年,但是其作者在github里依然进行着优化,你可以在作者网站里阅读使用白皮书,并尝试写一个demo。

代码实现

好消息是,这三个网站都有公告(披露)板块的内容,还都是列表+详情的基本页面组合,所以爬虫的逻辑很简单,列表页爬链接——详情页爬内容——保存到本地文件。
后两步一旦掌握规律会很简单,而第一步如何在列表页中发现链接才是比较困难的一步,要知道并不是每个网站都会乖乖在前端写好链接,这对网站内容的更新并不有利。而有趣的是,关于如何在列表页发现链接,这三个网站的难度是逐渐递增的。
在WebMagic里,实现一个基本的爬虫只需要编写一个类,实现PageProcessor接口即可。这个类基本上包含了抓取一个网站,你需要写的所有代码。
PageProcessor有两个抽象方法

public interface PageProcessor {/*** process the page, extract urls to fetch, extract the data and store** @param page page*/public void process(Page page);/*** get the site settings** @return site* @see Site*/public Site getSite();
}

site负责爬取网页站点的相关配置,包括编码、抓取间隔、重试次数等,process是定制爬虫逻辑的核心接口,在这里编写抽取逻辑。如果你想要知道更多参数的详细信息,可以到官方手册中查看。
首先编写一个SsePageProcessor来实现PageProcessor,在编写代码之前我们需要先分析一下网页的内容。
对于上海证券交易所的公告网页进行分析,如图
分析源码我们可以发现,详情页的链接都乖乖的写在a标签里,这是最容易的一种,页面下载来后定位到相应的div块然后逐一取出a标签里href属性,放到待爬队列中即可,webmagic会帮助进行url去重。

然而我们先不忙着写代码,我们在开发者工具中分析一下打开页面的时候浏览器到底发出了哪些请求,即便我没什么经验,但我依然对这么一条地址表示极大的兴趣。

我们看一下它的响应体或者单独重新请求一下这个地址,结果如下图

没错,是一个更加直接明了的列表页(具体地址与分页有关),爬取这个页面虽然并不比直接爬取列表页会容易多少,但是保持这样的思路会对之后的网站爬取有帮助。
接下来就开始爬取网页的内容吧。

public class SsePageProcessor implements PageProcessor {@Overridepublic void process(Page page) {// TODO Auto-generated method stub// 详情页的内容String content = "";
//		列表页的url是形如.htm
//		或者.htm的形式,这由分页来决定
//		而详情页的url则形如.shtml
//		正则表达式中的"(suspension/)?"则是我看到的个例,某些详情页的url中满足这样的格式,当然也可以用正则来匹配列表页的url,我这么写只是为了锻炼一下自己写正则的能力if (page.getUrl().regex("/" + "(suspension/)?" + "c/.*").match()) {page.putField("url", page.getUrl());
//			通过xpath初步解析页面,获得class为article-infor的div块Selectable selectable = page.getHtml().xpath("//div[@class='article-infor']");
//			通过jsoup来进一步解析页面(因为我比较擅长jsoup,只要能解析页面就行)Document doc = Jsoup.parse(selectable.toString());content += doc.select("h2").text() + "\r\n时间:"+ doc.select("i").text()+ "\r\n";
//			获取详情页的公告内容for (Element ele : doc.select("div[class='allZoom']").select("p")) {content += ele.text();}
//			添加到page中的content字段page.putField("content", content);} else {
//			解析列表页,获取包含链接的div块Selectable selectable = page.getHtml().xpath("//div[@id='sse_list_1']");
//			通过正则在页面中查找显式的链接
//			写两个正则是因为发现合在一起似乎并不管用page.addTargetRequests(selectable.links().regex("/.*").all());page.addTargetRequests(selectable.links().regex("/.*").all());}}@Overridepublic Site getSite() {return Site.me().setCharset("utf8").setRetryTimes(3).setSleepTime(100);}
}

我们不忙着启动这个爬虫,还要处理另外一件事,那就是处理后的页面存放在哪。webmagic提供了两种方式,一种是输出到控制台,或者你也可以抽取数据存放到数据库里,另一种是存放到文件中,我被要求的是第二种,所以我们需要来定制一个pipeline。官网手册中给出了许多demo,我们只需要把轮子拿过来即可,并没有什么难度。

public class MyFilePipeline extends FilePersistentBase implements Pipeline {private Logger logger = LoggerFactory.getLogger(this.getClass());public MyFilePipeline() {this.setPath("/data/webmagic/");}public MyFilePipeline(String path) {this.setPath(path);}@Overridepublic void process(ResultItems resultItems, Task task) {String path = this.path + PATH_SEPERATOR + task.getUUID() + PATH_SEPERATOR;try {
//			通过迭代器来获取到结果集中的元素,主要是键值对的形式存储Iterator var5 = resultItems.getAll().entrySet().iterator();while (true) {
//				判断是列表页还是详情页,列表页我并不会在结果集中存放数据if (var5.hasNext()) {PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(this.getFile(path + DigestUtils.md5Hex(resultItems.getRequest().getUrl()) + ".txt")),"UTF-8"));while (var5.hasNext()) {
//						开始遍历Entry<String, Object> entry = (Entry) var5.next();if (entry.getValue() instanceof Iterable) {Iterable value = (Iterable) entry.getValue();printWriter.println((String) entry.getKey() + ":\r\n");Iterator var8 = value.iterator();
//							对于子元素的遍历while (var8.hasNext()) {Object o = var8.next();printWriter.print(o);}} else {printWriter.println((String) entry.getKey() + ":\r\n" + entry.getValue());}}printWriter.close();}break;}} catch (IOException var10) {this.logger.warn("write file error", var10);}}
}

另外还有两个“需要”定制的组件downloader和scheduler。downloader组件负责下载页面,一般情况下并需要定制,但就像我前面所说maven仓库中webmagic版本有些旧,它在抓取https时会出现SSLException,导致https的url无法爬取,事实上webmagic作者已经在github中更新了这个问题,如果你需要更加详细的解释,可以戳这里。我的解决方式就是先写一个HttpClientGenerator复制原来的代码,然后将buildSSLConnectionSocketFactory()方法改成如下:

private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() {try {return new SSLConnectionSocketFactory(createIgnoreVerifySSL(),new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }, null, new DefaultHostnameVerifier()); // 优先绕过安全证书} catch (KeyManagementException e) {logger.error("ssl connection fail", e);} catch (NoSuchAlgorithmException e) {logger.error("ssl connection fail", e);}return SSLConnectionSocketFactory.getSocketFactory();}

然后再复制HttpClientDownloader到自己的项目中,修改里面的HttpClientGenerator为刚才修改过的,之后在启动爬虫的时候再设置一下downloader,这个我们稍后再提。
然后就是scheduler,这个组件是用来管理url队列的,webmagic中内置了常用的几种scheduler,一般情况下我们并不需要定制,但是官方的demo中进行了配置,就贴出来看一下。

public class MyQueueScheduler extends DuplicateRemovedScheduler {private BlockingQueue<Request> queue = new LinkedBlockingQueue<Request>();/*** 定义 进入队列的总数*/private Integer count;private Integer nowCount;public MyQueueScheduler(Integer count, Integer nowCount) {this.count = count;this.nowCount = nowCount;}@Overridepublic void pushWhenNoDuplicate(Request request, Task task) { // 当url不重复时,添加到scheduler中。那么什么时候是不重复的呢?就要看其父类DuplicateRemovedSchedulerif (nowCount < count) {queue.add(request);nowCount++;}}@Overridepublic synchronized Request poll(Task task) {return queue.poll();}
}

接下来我们就可以来启动爬虫,导师要求定时爬取消息,所以我写了一个定时任务。

@Component
@EnableScheduling
public class MyTask {private DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");private String date = sdf.format(new Date());private Logger logger = LoggerFactory.getLogger(this.getClass());private String outputDir = "D:\\webmagic";/*** @title: sseTask* @description: 上海证券交易所* @return-type: void 返回类型*/@Scheduled(cron = "0 0 6,12 * * ? ") // 每天6点和12点开始定时任务public void sseTask() {// 日志打印logger.info(date, ":%s", "*********sseTask任务启动");int count = 3;// 打印3页的内容/** 开始构造列表页的url **/String url = "";while (count > 0) {String tempUrl = url;if (count > 1) {tempUrl += "_" + count;}tempUrl += ".htm";count--;/** 启动爬虫 **/Spider.create(new SsePageProcessor()).setDownloader(new HttpClientDownloader())// 重新设置downloader.addUrl(tempUrl).addPipeline(new MyFilePipeline(outputDir))// 设置pipeline的本地地址.setScheduler(new MyQueueScheduler(30, 0))// 设置url队列的管理组件.thread(5)// 开5个线程爬.run();}}

其他两个的流程大体都是类似的,构造列表页的url,设置详情页的正则判断,分别爬取所需的信息,最后存放到本地文件中。
找到详情页的url是非常简单的,如何在列表页中找到详情页才是亟需解决的问题。
如图是深圳证券交易所公告板块的列表页,以及其所对应的源码。


列表页中每条详情页的url都是在script中构造出来的,所以我无法再直接用jsoup来通过节点的属性来获取了。
解决这样的问题可以用Selenium这样的浏览器内核来获取到渲染后的页面,这对于小规模或一次性的需求会非常省时省力,然而在大规模的项目中就会出现效率的问题。
但是在这里使用selenium显然是大材小用,我是说,这url都写在源码里了,所以改变一下思路,使用最原始的方法——正则匹配。
我先用webmagic自带的regex()获取到所有包含详情有人url的script块,然后对每个scrip块使用Pattern-Matcher来截取我所需要的url片段。

@Overridepublic void process(Page page) {// TODO Auto-generated method stubString content = "";if (!page.getUrl().regex(".*").match()) {page.putField("url", page.getUrl());Selectable selectable = page.getHtml().xpath("//div[contains(@class,'news-detail-con g-conbox')]");// System.out.println(selectable);Document doc = Jsoup.parse(selectable.toString());content += doc.select("h2").text() + "\r\n"+ doc.select("div[class='time']").text()+ "\r\n";for (Element ele_1 : doc.select("p[class='MsoNormal']")) {for (Element ele_2 : ele_1.select("span")) {content += ele_2.text();}}page.putField("content", content);} else {Selectable selectable = page.getHtml().regex("var curHref = '.*'");String doc = selectable.toString();Pattern pattern = Patternpile("\\./.*\\.html");List<String> requests = new ArrayList<String>();Matcher matcher = pattern.matcher(doc);while (matcher.find()) {requests.add("/" + matcher.group().substring(2));}page.addTargetRequests(requests);}}

第三个网站是中国货币网,如图是列表页的源码。

很奇怪,我希望是我截错了,所以我怀着试一试的心态在地址栏里输入其中一个链接之后,跳出来如下的页面。

<ul class="san-list-a" id="shchgg-bb-new-list"></ul>
<div class="section-fo"><div class="san-row"><div class="san-col-4 san-col-l-12"></div><div class="san-col-8 san-col-l-12 AR AC-s AC-xs"><ul class="san-pagination" id="shchgg-bb-new-pagination"></ul></div></div>
</div><script type="text/javascript">
$(function(){var strHtml="";var p = "{{pageSize}}";p=parseInt(p, 10);for(var k=0;k<p;k++){if(k%2==0){strHtml+= '<li class="li-item odd">&nbsp;</li>';}else{strHtml+= '<li class="li-item even">&nbsp;</li>';}}$("#shchgg-bb-new-list").html(strHtml);renderData("1");
});function renderData(pageNo) {$.ajax({url:NT_URL + "/cm-s-notice-query/contents",// url:"/" + "/cm-s-notice-query/contents",type:"POST",data:{"pageNo":pageNo,"pageSize":"{{pageSize}}","channelId":"{{channelId}}"},dataType:'json',cache:false,success:function(data){var pgTotal = data.data.pageTotalSize;var rdTotal = data.data.total;// 渲染页面creatHtml(data.records,{{pageSize}});// pagination2("shchgg-bb-new-pagination",pgTotal,rdTotal,pageNo, renderData);pagination("shchgg-bb-new-pagination",pgTotal,rdTotal,pageNo);}});
}/* 展示数据 */
function creatHtml(data,size){var html = '';var len = data.length;if(len==0){for(var k=0;k<size;k++){html += fillInBlankLines(k);}			}else{$.each(data,function(i,n){var channelId = n.channelId;var title = n.title;var showTitle = n.title;if(null!=title) {title = sanCharEntity(title);showTitle = subStrDot(sanCharEntity(showTitle),106,103);}var releaseDate = n.releaseDate;			var href = n.url;var color = n.titleColor;var contentId = n.contentId;var bold = n.bold;// 前缀判断var prefixStr = "";var channelId1 = $("#li-news-1").attr("data-channel");	// 市场公告通知var channelId2 = $("#li-news-2").attr("data-channel");	// 中心成员公告var channelId3 = $("#li-news-3").attr("data-channel");	// 成员信息公告if(channelId1 ==channelId) {prefixStr = "市场";} else if(channelId2 ==channelId) {prefixStr = "中心";} else if(channelId3 ==channelId) {prefixStr = "成员";}var style="";if(color!=null){style="color:"+color+";";	}if(bold){style += "font-weight:bold;";}var arrObj = contentShowInfoSEO(contentId, n.txt, n.attSize, n.suffix, n.draftPath);var downloadHref = arrObj[2];var suffix =arrObj[0];if(!href){href = arrObj[1]+"#cp=scggbbscgg";}var downloadHtml = '';if(suffix){downloadHtml = '<a class="link-img" href="'+downloadHref+'" title="点击下载">'+suffix+'</a>';}if(i%2==0){html += '<li class="li-item odd"><div class="san-grid">'+'<div class="san-grid-r text-date"><span>'+releaseDate+'</span></div>'+// '<div class="san-grid-l li-item-l"><span class="san-tag-i m-r-5">'+prefixStr+'</span></div>'+ '<div class="san-grid-m li-item-m"><span class="san-tag-i m-r-5">'+prefixStr+'</span>'+'<a href="'+href+'" target="_blank" style="'+style+'" title='+title+'>'+title+'</a>'+downloadHtml + '</div></div></li>';}else{html += '<li class="li-item even"><div class="san-grid">'+'<div class="san-grid-r text-date"><span>'+releaseDate+'</span></div>'+// '<div class="san-grid-l li-item-l"><span class="san-tag-i">'+prefixStr+'</span></div>'+'<div class="san-grid-m li-item-m"><span class="san-tag-i m-r-5">'+prefixStr+'</span>'+'<a href="'+href+'" target="_blank" style="'+style+'" title='+title+'>'+title+'</a>'+downloadHtml + '</div></div></li>';}//数据小于15条补空行if(len<size&&i==(len-1)){for(var k=len;k<size;k++){html += fillInBlankLines(k);}				}});}$("#shchgg-bb-new-list").html(html);
}/* 补空行&#65533; */
function fillInBlankLines(k){var html = "";if(k%2==0){html = '<li class="li-item odd">&nbsp;</li>';}else{html = '<li class="li-item even">&nbsp;</li>';}return html;
}
</script>
</body></html>

这是一个前端渲染的页面,光凭网页源码是肯定找不到我们所需要的详情页的链接的,解决方法就像我之前在深圳证券交易所那边提到的使用浏览器内核来获得渲染过后的网页源码,或者,因为js渲染页面的数据也是从后端拿到,而且基本上都是AJAX获取,所以分析AJAX请求,找到对应数据的请求,也是比较可行的做法。createHtml(data,size)作为为数不多出现注释的地方,肯定要给个面子先分析一下,两个参数一看就像是数据和分页信息的,继续往下看到"href=n.url",再结合之前的$.each()我们可以知道,每个详情页的url都会是从某个数据请求里传来的,那么接下来的任务就简单了,找到这个数据请求。第一步,找到createHtml里的data参数是何时传进来的——renderData(pageNo)里的creatHtml(data.records,{{pageSize}});,第二步,这个data.records是哪里来——通过$.ajax()向url:NT_URL + "/cm-s-notice-query/contents"里请求得到的,第三步,我们来看一看这个/cm-s-notice-query/contents里是什么,如下

{"head":{"version":"2.0","provider":"CWAP","req_code":"","rep_code":"200","rep_message":"","ts":1593954571179,"producer":"","tstext":"2020-07-05 21:09:31"},"data":{"total":0,"pageTotalSize":0},"records":[]}

是一串json数据,可惜我们需要的data.records里却没有数据,再回去看源码

 data:{"pageNo":pageNo,"pageSize":"{{pageSize}}","channelId":"{{channelId}}"},

我意识到自己忘了参数,那么前两个都很好获得,第三个channelId怎么获得呢?
于是我进入开发者工具重新请求了一下页面然后找到这个数据请求头,如图右下角

最后我将参数拼接到url中重新请求这个数据,结果返回如下数据

代码的实现就很简单了,webmagic提供了解析json数据的工具。

public void process(Page page) {// TODO Auto-generated method stubString content = "";if (page.getUrl().regex(".*").match()) {String url = "";List<String> requests = new ArrayList<String>();List<String> draftPaths = new JsonPathSelector("$.records[*].draftPath").selectList(page.getRawText());if (CollectionUtils.isNotEmpty(draftPaths)) {for (String draftPath : draftPaths) {requests.add(url + draftPath);}}page.addTargetRequests(requests);} else {page.putField("url", page.getUrl());Selectable selectable = page.getHtml().xpath("//div[@class='main-body']");Document doc = Jsoup.parse(selectable.toString());content += doc.select("div[class='title-heading']").text() + "\r\n时间:"+ doc.select("div[class='san-col-4 san-col-m-6 AC-xl AC-l']").select("span").text()+ "\r\n";for (Element ele : doc.select("div[class='article-a-content']").select("p").select("span")) {try {String temp = new String(ele.text().getBytes(), "GBK").replace('?', ' ').replace(' ', ' ');content += temp;} catch (Exception e) {e.printStackTrace();}}page.putField("content", content);}}

大功告成,这样我就可以将需要的信息全部保存在本地,当然结构后续还会更改。


更多推荐

eclipse+springboot开发的爬虫项目

本文发布于:2024-03-10 04:00:59,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1726959.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:爬虫   项目   eclipse   springboot

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!