WebMagic爬取新年图片
虎年就快到了,最近有没有想要做ppt,写总结缺少素材的小伙伴?苦于没有背景素材啊,我来教你一招,爬取大量春节图片,助你在工作生活中春节气氛满满,虎虎生风啊~~
首先说明啊,我爬取的都是免费网站,并且绝不会用作商用的啊,同学们一定要有版权意识啊。
推荐个免费的网站:
下面开始步入正文。
一、认识webmagic
本文使用java开发,webmagic作为爬虫工具,简单快捷,下面简单认识下什么是webmagic:
官方文档:http://webmagic.io/
1.1 简介
WebMagic的设计参考了业界最优秀的爬虫Scrapy,而实现则应用了HttpClient、Jsoup等Java世界最成熟的工具。
此项目分为两个模块:
- 核心部分(webmagic-core):包含基础爬虫和抽取器。
- 扩展部分(webmagic-extension):提供一些更方便的编写爬虫的工具。包括注解格式定义爬虫、JSON、分布式等支持。
所以我们在使用时,要使用全部功能,需要引用如下两个组件:
<!--WebMagic-->
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>0.7.5</version>
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-log4j12</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
</dependency>
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>0.7.5</version>
</dependency>
在上面的pom中,注释部分去除了slf4j-log4j12的依赖,因为这里的依赖与我的springboot项目冲突了。如果发现有相关的报错,可以像我一样处理。
1.2 架构
WebMagic的结构分为四大组件:
- Downloader:从互联网上下载页面
- PageProcessor:负责解析页面,抽取有用信息,以及发现新的链接
- Scheduler:负责管理待抓取的URL,以及一些去重的工作。通常不需要自己定制。
- Pipeline:负责抽取结果的处理,包括计算、持久化到文件、数据库等。
还有一个引擎:
- Spider:将四大组件整合起来。
源自官方的架构图如下:
webmagic.png1.3 数据流转对象
- Request
Request是对URL地址的一层封装,一个Request对应一个URL地址。
它是PageProcessor与Downloader交互的载体,也是PageProcessor控制Downloader唯一方式。
除了URL本身外,它还包含一个Key-Value结构的字段extras。你可以在extra中保存一些特殊的属性,然后在其他地方读取,以完成不同的功能。
- Page
Page代表了从Downloader下载到的一个页面(可能是HTML,也可能是JSON或者其他文本格式的内容)。
Page是WebMagic抽取过程的核心对象。后面会在示例中使用常用的方法。
- ResultItems
ResultItems相当于一个Map,它保存PageProcessor处理的结果,供Pipeline使用。
它的API与Map很类似,值得注意的是它有一个字段skip,若设置为true,则不应被Pipeline处理。
1.4 爬虫引擎 - Spider
Spider是webmagic的核心组件,其包含了上述介绍的四大组件,同时提供手动设置属性的方法。
除此之外,Spider也是整个爬虫的创建、启动、终止的控制器。
官方实例如下,含义都在注释当中:
public static void main(String[] args) {
Spider.create(new VulnPageProcessor())
//从https://github.com/code4craft开始抓
.addUrl("https://github.com/code4craft")
//设置Scheduler,使用Redis来管理URL队列
.setScheduler(new RedisScheduler("localhost"))
//设置Pipeline,将结果以json方式保存到文件
.addPipeline(new JsonFilePipeline("D:\\data\\webmagic"))
//开启5个线程同时执行,此处不能小于2
.thread(5)
//启动爬虫
.run();
}
二、动手爬取新年图片
2.1 添加maven依赖
上面已经介绍过了,直接添加maven依赖
<!--WebMagic-->
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>0.7.5</version>
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-log4j12</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
</dependency>
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>0.7.5</version>
</dependency>
2.2 定制PageProcessor
2.2.1 获取图片地址
我们想要处理爬取到的网站内容,就需要实现自己的PageProcessor,如下所示:
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;
/**
* @description: 新年图片处理器
* @author:weirx
* @date:2022/1/5 14:45
* @version:3.0
*/
public class ChineseNewYearImgPageProcessor implements PageProcessor {
@Override
public void process(Page page) {
}
@Override
public Site getSite() {
return null;
}
}
如上所示,有两个方法需要实现:
-
getSite()
此方法返回一个Site 对象,此对象包含抓取网站的相关配置,包括编码、抓取间隔、重试次数等。
我们需要定义好Site和属性,在此方法返回即可,代码如下所示:
private Site site = Site.me()
// 重试次数
.setRetryTimes(3)
//编码
.setCharset(StandardCharsets.UTF_8.name())
// 超时时间
.setTimeOut(1000)
// 休眠时间
.setSleepTime(1000);
@Override
public Site getSite() {
return site;
}
- process(Page page)
核心方法,数据抽取逻辑在此实现。当前页面后续跳转url也需要在此处添加。
我们看下页面源码的样式组成,我们根据图上画圈的标签样式表获取元素,img内的就是我们需要的图片:
image.png具体实现,详细解释看注释:
@Override
public void process(Page page) {
//获取当前页面的所有满足条件的img
List<String> srcset = page.getHtml()
//在元素div class = kG7WW下的内容
.css("div.kG7WW")
//在上一层基础上,查找div.VQW0y下的img标签,获取img标签的srcset属性
.css("div.VQW0y > img", "srcset")
// 获取符合条件的所有元素,返回List<String>
//注意:此处是所有的img,并非第一个,取第一个使用get()
.all();
// 根据当前网页的内容,需要去重图片路径,此处直接使用list
Set distinct =new HashSet<>();
// java8 流式 并发遍历
srcset.parallelStream().forEach(s -> {
// 数据处理,截取我们需要的数据
distinct.add(s.substring(0, StringUtils.indexOf(s,"?")));
});
//打印结果
distinct.forEach(System.out::println);
}
好了,这样我们就完成一个PageProcessor的编写,最后我们写一个main方法,使用Spider启动这个爬虫:
public static void main(String[] args) {
Spider.create(new ChineseNewYearImgPageProcessor())
//此处从收集中国年(chinese new year)的页面作为首页
.addUrl("https://unsplash.com/s/photos/chinese-new-year")
//开启5个线程同时执行,此处不能小于2
.thread(5)
//启动爬虫
.run();
}
启动看结果如下,将图片的地址都打印出来了:
2022-01-05 15:41:30.681 [springAppName_IS_UNDEFINED: N/A] [pool-1-thread-1] INFO u.c.webmagic.downloader.HttpClientDownloader - downloading page success https://unsplash.com/s/photos/chinese-new-year
https://images.unsplash.com/photo-1581792408272-a9227ce6b363
https://images.unsplash.com/photo-1601402420504-a3f1b910679e
https://images.unsplash.com/photo-1578073273382-f847b29d2192
https://images.unsplash.com/photo-1517315029683-c6497375b3da
https://images.unsplash.com/photo-1517009832553-cfcd067adf81
https://images.unsplash.com/photo-1612201598945-f66a763965bd
https://images.unsplash.com/photo-1597533456003-27f3ce4d0d62
https://images.unsplash.com/photo-1579626362137-b6d68a1ebba6
https://images.unsplash.com/photo-1549767742-ccfdeb07b71d
https://images.unsplash.com/photo-1541379889336-70f26e4c4617
https://images.unsplash.com/photo-1587133966114-7f69b8803e1b
https://images.unsplash.com/photo-1580524765545-f159d96bb9a9
https://images.unsplash.com/photo-1589803196808-b395a6f32a9a
https://images.unsplash.com/photo-1600582201908-183d607504c3
https://images.unsplash.com/photo-1565457210787-a4e17b40f04e
https://images.unsplash.com/photo-1612201578303-25f1712d20cc
https://images.unsplash.com/photo-1544032745-e96acb1caa9c
https://images.unsplash.com/photo-1518894347072-3bfedb006095
https://images.unsplash.com/photo-1571306130639-22a185b29822
https://images.unsplash.com/photo-1587314857323-5e93cc3d718e
get page: https://unsplash.com/s/photos/chinese-new-year
2022-01-05 15:41:31.901 [springAppName_IS_UNDEFINED: N/A] [main] INFO us.codecraft.webmagic.Spider - Spider unsplash.com closed! 1 pages downloaded.
支持我们已经拿到需要的图片地址了。迈入了成功的第一步。
2.2.2 获取下一页
在获取了首页的内容后,我们尝试获取下一页更多的图片。
这个网站的分页按钮叫做【Load more photos】,所以我们需要在网页的内容当中,获取到这个按钮所在的标签,并获取到其跳转的地址:
<div class="gDCZZ">
<button type="button" class="CwMIr DQBsa p1cWU jpBZ0 AYOsT Olora I0aPD dEcXu">Load more photos</button>
</div>
发现这个标签并没有地址,我前端有很水,所以直接在浏览器控制台找到如下分页地址:
https://unsplash.com/napi/search/photos?query=chinese%20new%20year&per_page=20&page=2&xp=
我们每查一次就手动将页数加1好了。
说句题外话,我们都拿到了api接口,其实也就不需要爬虫了对吧,但是爬虫才是我们本篇文章学习重点,所以继续搞。
webmagic也支持json的解析,而鉴于上面的原因我们就需要使用json的方式了,因为此网页点击下一页按钮后,并不是整体页面的跳转,是通过返回数据驱动视图变化,所以我们需要解析接口返回的api数据了。
接下来我们对下一页的代码进行完善,如下所示:
- 增加全局变量pageNum
/**
* 定义一个全局页数,方便下次获取下一页
*
* 此处使用AtomicInteger是为了防止多线程造成数据重复,webmagic在获取页时是多线程的
*/
private AtomicInteger pageNum = new AtomicInteger(2);
- 添加下一页地址到url管理容器,在process方法增加下面代码:
// 我们看是否有下一页的内容,有的话就将下一页也添加到url管理容器,
//https://unsplash.com/napi/search/photos?query=chinese%20new%20year&per_page=20&page=2&xp="
String url = "https://unsplash.com/napi/search/photos?query=chinese%20new%20year&per_page=20" + "page=" + pageNum;
List<String> nextPage = new ArrayList<>();
nextPage.add(url);
page.addTargetRequests(nextPage);
// 页数+1
pageNum.incrementAndGet();
启动,第二次download时发现page的html是空的,但是rawText正是我们需要的数据:
json下面我们就要解析这个json数据了,需要判断是json还是html,完整的process方法如下:
@Override
public void process(Page page) {
//获取当前页面的所有满足条件的img
List<String> srcset = page.getHtml()
//在元素div class = kG7WW下的内容
.css("div.kG7WW")
//在上一层基础上,查找div.VQW0y下的img标签,获取img标签的srcset属性
.css("div.VQW0y > img", "srcset")
// 获取符合条件的所有元素,返回List<String>
//注意:此处是所有的img,并非第一个,取第一个使用get()
.all();
if (CollectionUtil.isEmpty(srcset)){
JSONObject jsonObject = JSONObject.parseObject(page.getRawText());
JSONArray results = jsonObject.getJSONArray("results");
List<String> list = new ArrayList<>();
results.forEach(o->{
String url = JSONObject.parseObject(o.toString()).getJSONObject("urls").getString("raw");
String substring = url.substring(0, StringUtils.indexOf(url, "?"));
list.add(substring);
});
list.forEach(System.out::println);
}else {
// 根据当前网页的内容,需要去重图片路径,此处直接使用list
Set distinct =new HashSet<>();
// java8 流式 并发遍历
srcset.parallelStream().forEach(s -> {
// 数据处理,截取我们需要的数据
distinct.add(s.substring(0, StringUtils.indexOf(s,"?")));
});
//打印结果
distinct.forEach(System.out::println);
}
// 我们看是否有下一页的内容,有的话就将下一页也添加到url管理容器,
//https://unsplash.com/napi/search/photos?query=chinese%20new%20year&per_page=20&page=2&xp="
String url = "https://unsplash.com/napi/search/photos?query=chinese%20new%20year&per_page=20" + "page=" + pageNum;
List<String> nextPage = new ArrayList<>();
nextPage.add(url);
page.addTargetRequests(nextPage);
// 页数+1
pageNum.incrementAndGet();
}
至此,我们成功拿到所有的图片地址了,运行后将会不停的爬取。
2.3 pipeline保存
WebMagic用于保存结果的组件叫做Pipeline。其实在PageProcessor当中就可以完成数据持久化,增加Pipeline的根本原因是为了职责划分。
我们需要实现Pipeline接口,然后实现其process方法,通过ResultItems获取我们的结果,而ResultItems当中的结果是我们在PageProcessor当中处理完后使用page.putField手动添加的。
添加结果代码,因为我们有两种方式,json和html:
page.putField("jsonImg", list);
----
page.putField("htmlImg", list);
保存图片到本地,pipeline:
import cn.hutool.core.collection.CollectionUtil;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
/**
* @description: 保存图片到文件
* @author:weirx
* @date:2022/1/5 17:14
* @version:3.0
*/
public class SaveImgPipeline implements Pipeline {
@Override
public void process(ResultItems resultItems, Task task) {
List<String> jsonImg = (List<String>) resultItems.getAll().get("jsonImg");
List<String> htmlImg = (List<String>) resultItems.getAll().get("htmlImg");
if (CollectionUtil.isNotEmpty(jsonImg)) {
jsonImg.forEach(this::saveImg);
}
if (CollectionUtil.isNotEmpty(htmlImg)) {
htmlImg.forEach(this::saveImg);
}
}
/**
* description: 保存图片到本地
*
* @param imgUrl
* @return: void
* @author: weirx
* @time: 2022/1/5 17:46
*/
private void saveImg(String imgUrl) {
try {
URL url = new URL(imgUrl);
// 打开连接
URLConnection con = url.openConnection();
// 输入流
InputStream is = con.getInputStream();
Files.copy(is, Paths.get("C:\\Users\\P50\\Desktop\\pic\\" + System.currentTimeMillis() + ".png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
至此,我们一个简单的爬取新年图片的爬虫就完成了,看看我的成果吧:
成果三、完整代码
代码又开始就要有结束,所以我们前面缺少了一个结束,比如我只想获取10页的图片,那么我可以增加页数的判断,当到达第十页了,就调用stop方法,关于这部分不细说了,直接附上全部的代码,总共两个bean:
- ChineseNewYearImgPageProcessor
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description: 新年图片处理器
* @author:weirx
* @date:2022/1/5 14:45
* @version:3.0
*/
public class ChineseNewYearImgPageProcessor implements PageProcessor {
/**
* 全局spider,方便使用stop方法
*/
private static Spider spider = Spider.create(new ChineseNewYearImgPageProcessor())
//此处从收集中国年(chinese new year)的页面作为首页
.addUrl("https://unsplash.com/s/photos/chinese-new-year")
.addPipeline(new SaveImgPipeline())
//开启5个线程同时执行,此处不能小于2
.thread(11);
/**
* 定义一个全局页数,方便下次获取下一页
* <p>
* 此处使用AtomicInteger是为了防止多线程造成数据重复,webmagic在获取页时是多线程的
*/
private AtomicInteger pageNum = new AtomicInteger(2);
/**
* 初始化Site配置
*/
private Site site = Site.me()
// 重试次数
.setRetryTimes(3)
//编码
.setCharset(StandardCharsets.UTF_8.name())
// 超时时间
.setTimeOut(1000)
// 休眠时间
.setSleepTime(1000);
@Override
public void process(Page page) {
//获取当前页面的所有满足条件的img
List<String> srcset = page.getHtml()
//在元素div class = kG7WW下的内容
.css("div.kG7WW")
//在上一层基础上,查找div.VQW0y下的img标签,获取img标签的srcset属性
.css("div.VQW0y > img", "srcset")
// 获取符合条件的所有元素,返回List<String>
//注意:此处是所有的img,并非第一个,取第一个使用get()
.all();
if (CollectionUtil.isEmpty(srcset)) {
JSONObject jsonObject = JSONObject.parseObject(page.getRawText());
JSONArray results = jsonObject.getJSONArray("results");
List<String> list = new ArrayList<>();
results.forEach(o -> {
String url = JSONObject.parseObject(o.toString()).getJSONObject("urls").getString("raw");
String substring = url.substring(0, StringUtils.indexOf(url, "?"));
list.add(substring);
});
page.putField("jsonImg", list);
} else {
// 根据当前网页的内容,需要去重图片路径,此处直接使用list
Set distinct = new HashSet<>();
// java8 流式 并发遍历
srcset.parallelStream().forEach(s -> {
// 数据处理,截取我们需要的数据
distinct.add(s.substring(0, StringUtils.indexOf(s, "?")));
});
page.putField("htmlImg", new ArrayList<>(distinct));
}
// 我们看是否有下一页的内容,有的话就将下一页也添加到url管理容器,
//https://unsplash.com/napi/search/photos?query=chinese%20new%20year&per_page=20&page=2&xp="
String url = "https://unsplash.com/napi/search/photos?query=chinese%20new%20year&per_page=20" + "page=" + pageNum;
List<String> nextPage = new ArrayList<>();
nextPage.add(url);
page.addTargetRequests(nextPage);
// 页数+1
pageNum.incrementAndGet();
//如果已经爬取10页,则停止爬取
if (pageNum.get() > 10) {
spider.stop();
}
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
spider.run();
}
}
- SaveImgPipeline
import cn.hutool.core.collection.CollectionUtil;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
/**
* @description: 保存图片到文件
* @author:weirx
* @date:2022/1/5 17:14
* @version:3.0
*/
public class SaveImgPipeline implements Pipeline {
@Override
public void process(ResultItems resultItems, Task task) {
List<String> jsonImg = (List<String>) resultItems.getAll().get("jsonImg");
List<String> htmlImg = (List<String>) resultItems.getAll().get("htmlImg");
if (CollectionUtil.isNotEmpty(jsonImg)) {
jsonImg.forEach(this::saveImg);
}
if (CollectionUtil.isNotEmpty(htmlImg)) {
htmlImg.forEach(this::saveImg);
}
}
/**
* description: 保存图片到本地
*
* @param imgUrl
* @return: void
* @author: weirx
* @time: 2022/1/5 17:46
*/
private void saveImg(String imgUrl) {
try {
URL url = new URL(imgUrl);
// 打开连接
URLConnection con = url.openConnection();
// 输入流
InputStream is = con.getInputStream();
Files.copy(is, Paths.get("C:\\Users\\P50\\Desktop\\pic\\" + System.currentTimeMillis() + ".png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
四、扩展
4.1 集成springboot
-
添加@Component
上面的代码放在springboot当中其实就没有问题了,只不过我们使用的是普通的main方法启动。当在springboot中,我们只需要在想使用的时候调用启动方法即可:
- 注意在PageProcessor的实现上增加@Component注解,交给spring容器管理
- 使用时直接使用@Autowired注入即可
@Autowired
private ChineseNewYearImgPageProcessor ChineseNewYearImgPageProcessor;
- @Autowired失效
需要注意的是,在ChineseNewYearImgPageProcessor当中如果想要使用spring容器中的bean,不能直接使用@Autowired注解,这样获取不到想要的bean,可以使用ApplicationContextProvider.getBean()的方式。
XxxxxxxxxxxImpl xxxxxxxxxxxImpl = ApplicationContextProvider.getBean(XxxxxxxxxxxImpl.class)
-
定时任务@Scheduled
想要使用spring的@Scheduled定时任务执行爬虫任务,直接如下方式就可以,需要注意的是,我在此处使用的异步调用方式,因为调度任务的线程默认只有一个,如何直接调用,会导致其他线程无法使用调度,当然可以通过配置指定调度任务线程数,自行百度即可。@Autowired private ChineseNewYearImgPageProcessor processor; @Scheduled(cron = "0 */3 * * * ?") public void execute() { log.info("start crawl vuln patch!"); CompletableFuture.runAsync(() -> processor.start()); }
另外Spider本来就支持异步执行,前面我们启动使用的是run方法,它的start()方法就是异步的。
使用还是有差别的,通过阅读源码可以发现:
- 当时用run()启动时:webmagic会自动判断当前爬虫是否已经启动
- 而使用start():直接以异步方式启动,并不会判断当前爬虫是否启动,此时会造成重复启动,最终抛出如下的异常:
2022-01-06 17:03:06.355 [vuln-prioritization-tech: N/A] [pool-599-thread-11] WARN u.c.webmagic.downloader.HttpClientDownloader - download page http://www.cnnvd.org.cn/web/xxk/bdxqById.tag?id=6575 error
java.net.SocketException: 打开的文件过多
at java.net.Socket.createImpl(Socket.java:460)
at java.net.Socket.getImpl(Socket.java:520)
at java.net.Socket.setSoTimeout(Socket.java:1141)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:120)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at us.codecraft.webmagic.downloader.HttpClientDownloader.download(HttpClientDownloader.java:83)
at us.codecraft.webmagic.Spider.processRequest(Spider.java:419)
at us.codecraft.webmagic.Spider.access$000(Spider.java:61)
at us.codecraft.webmagic.Spider$1.run(Spider.java:322)
at us.codecraft.webmagic.thread.CountableThreadPool$1.run(CountableThreadPool.java:74)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
4.2 minio保存图片
绝大多数情况下,我们的图片不会保存到本地的,要有其对应的文件服务器,我这里给大伙提供这个minio的教程,也是我前面写的文章,希望大家用得到。
关注我的minio文集,有环境搭建和springboot集成:https://www.jianshu.com/nb/51056774。
五、随便说说
相信学完本篇文章,大家一定还是有点收获的吧。点赞关注啊。
关于本篇文章的所有内容,有错误或不理解,欢迎留言一起讨论。
现在2022年了,希望大家牛气满满,钱包也满满、好好学习、天天向上!!!