【天地图网站源码】【溯源码操作视频】【python源码编译库】seletor源码解读

时间:2024-11-27 00:32:37 编辑:刷源码java 来源:amd gpu源码下载

1.@Import({ AutoConfigurationImportSelector.class})
2.为什么 querySelectorAll 返回的码解不是 Array?
3.jquery unbind 和off的区别
4.RocketMQ第五讲
5.R爬虫必备基础——CSS+SelectorGadget

seletor源码解读

@Import({ AutoConfigurationImportSelector.class})

       ï¼ˆ2)@Import({ AutoConfigurationImportSelector.class}):将AutoConfigurationImportSelector这个类导入到spring容器中,AutoConfigurationImportSelector可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中

       ç»§ç»­ç ”究AutoConfigurationImportSelector这个类,通过源码分析这个类中是通过selectImports这个方法告诉springboot都需要导入那些组件:

       ![image-](./images/image-.png)

       æ·±å…¥ç ”究loadMetadata方法

       ![image-](./images/image-.png)      

       æ·±å…¥getCandidateConfigurations方法

       ä¸ªæ–¹æ³•ä¸­æœ‰ä¸€ä¸ªé‡è¦æ–¹æ³•loadFactoryNames,这个方法是让SpringFactoryLoader去加载一些组件的名字。

       ![image-](./images/image-.png)

       ç»§ç»­ç‚¹å¼€loadFactory方法

       ```java

         public static ListloadFactoryNames(Class factoryClass, @Nullable ClassLoaderclassLoader) {

              //获取出入的键

               String factoryClassName = factoryClass.getName();

               return(List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,Collections.emptyList());

            }

          private static Map>loadSpringFactories(@Nullable ClassLoader classLoader) {

               MultiValueMap result =(MultiValueMap)cache.get(classLoader);

               if (result != null) {

                   return result;

               } else {

                   try {

                       //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装为Enumeration类对象

                        Enumeration urls =classLoader != null ?classLoader.getResources("META-INF/spring.factories") :ClassLoader.getSystemResources("META-INF/spring.factories");

                        LinkedMultiValueMap result =new LinkedMultiValueMap();

                       //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中

                        while(urls.hasMoreElements()) {

                            URL url =(URL)urls.nextElement();

                            UrlResource resource = newUrlResource(url);

                            Properties properties =PropertiesLoaderUtils.loadProperties(resource);

                            Iterator var6 =properties.entrySet().iterator();

                           while(var6.hasNext()) {

                                Entry entry= (Entry)var6.next();

                                String factoryClassName= ((String)entry.getKey()).trim();

                                String[] var9 =StringUtils.commaDelimitedListToStringArray((String)entry.getValue());

                                int var =var9.length;

                               for(int var = 0;var < var; ++var) {

                                    String factoryName= var9[var];

                                   result.add(factoryClassName, factoryName.trim());

                                }

                            }

                        }

                       cache.put(classLoader, result);

                        return result;

        ```

       ä¼šåŽ»è¯»å–一个sprin  g.factories的文件,读取不到会表这个错误,我们继续根据会看到,最终路径的长这样,而这个是spring提供的一个工具类

       ```java

        public final class SpringFactoriesLoader {

           public static final String FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";

        }

        ```

       å®ƒå…¶å®žæ˜¯åŽ»åŠ è½½ä¸€ä¸ªå¤–部的文件,而这文件是在

       ![image-](./images/image-.png)

       ![image-](./images/image-.png)

       @EnableAutoConfiguration就是从classpath中搜寻META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的配置类,并加载到IOC容器中      

        ä»¥åˆšåˆšçš„项目为例,在项目中加入了Web环境依赖启动器,对应的WebMvcAutoConfiguration自动配置类就会生效,打开该自动配置类会发现,在该配置类中通过全注解配置类的方式对Spring MVC运行所需环境进行了默认配置,包括默认前缀、默认后缀、视图解析器、MVC校验器等。而这些自动配置类的本质是传统Spring MVC框架中对应的XML配置文件,只不过在Spring Boot中以自动配置类的形式进行了预先配置。因此,在Spring Boot项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,我们也可以对这些自动配置类中默认的配置进行更改 

       **总结

**

          因此springboot底层实现自动配置的步骤是:

       1. springboot应用启动;

       2. @SpringBootApplication起作用;

       3. @EnableAutoConfiguration;

       4. @AutoConfigurationPackage:这个组合注解主要是@Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;

       5.

        @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程

       åˆšå­¦äº†æ‹‰å‹¾æ•™è‚²çš„《Java工程师高薪训练营》,看到刚学到的点就回答了。希望拉勾能给我推到想去的公司,目标:字节!!

为什么 querySelectorAll 返回的不是 Array?

       查询所有元素涉及到的规范与数组规范不同,因此查询所有元素返回的码解不是数组类型。

       从规范角度看,码解查询所有元素属于 DOM 规范,码解而数组属于 ECMAScript 规范。码解DOM 规范强调平台中立性,码解天地图网站源码但并未在规范中提及与数组相关的码解概念。

       实际上,码解查询所有元素返回的码解是 NodeList 类型,这与数组有本质区别。码解Array 类型来源于 ECMAScript 规范,码解而 NodeList 则是码解 DOM 中用于表示一组节点的类。

       具体到源码层面,码解Chromium 的码解实现中,查询所有元素返回的码解是 StaticElementList 类型。该类封装了选择器字符串并提供了节点集合,但与 JavaScript 数组的实现不同。

       JavaScript 数组由 V8 引擎实现,其长度属性通过特定的偏移量在 JSArray 对象上获取。与此相反,溯源码操作视频StaticElementList 的长度属性计算逻辑与 JavaScript 数组完全不同。

       综上,查询所有元素返回的 NodeList 不是 Array 类型的原因,主要在于两者所属的规范体系不同,以及在实现细节上的差异。

jquery unbind 和off的区别

       1:unbind():为每个匹配元素的特定事件绑定事件处理函数。

       ã€€ã€€unbind(type,[data],fn)

       ã€€ã€€type: 含有一个或多个事件类型的字符串,由空格分隔多个事件。

       ã€€ã€€æ¯”如"click"或"submit",还可以是自定义事件名。

       ã€€ã€€data:作为event.data属性值传递给事件对象的额外数据对象

       ã€€ã€€fn:绑定到每个匹配元素的事件上面的处理函数

       2:off()在选择元素上绑定一个或多个事件的事件处理函数。

       ã€€ã€€off(events,[selector],[data],fn)

       ã€€ã€€events:一个或多个用空格分隔的事件类型和可选的命名空间,如"click"或"keydown.myPlugin" 。

       ã€€ã€€selector:一个选择器字符串用于过滤器的触发事件的选择器元素的后代。如果选择的< null或省略,当它到达选定的元素,事件总是触发。

       ã€€ã€€data:当一个事件被触发时要传递event.data给事件处理函数。

       ã€€ã€€fn:该事件被触发时执行的函数。 false 值也可以做一个函数的简写,返回false。

        对比发现off 和unbind的 差别不大, 但是off多一个[selector] 可以过滤指定元素。

       å¦å¤–,查看jQuery源码可知,.unbind()是通过.off()来实现的

RocketMQ第五讲

       broker是RocketMQ的核心,核心工作就是接收生成这的消息,进行存储。同时,收到消费者的请求后,从磁盘读取内容,把结果返回给消费者。

        消息主体以及元数据的存储主体,存储Producer端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G ,文件名长度为位,左边补零,剩余为起始偏移量,比如代表了第一个文件,python源码编译库起始偏移量为0,文件大小为1G=;当第一个文件写满了,第二个文件为,起始偏移量为,以此类推。消息主要是顺序写入日志文件,当文件满了,写入下一个文件;

        CommitLog文件中保存了消息的全量内容。不同的Topic的消息,在CommitLog都是顺序存放的。就是来一个消息,不管Topic是什么,直接追加的CommitLog中。

        broker启动了一个专门的线程来构建索引,把CommitLog中的消息,构建了两种类型的索引。ConsumerQueue和Index。正常消费的时候,是幸福饭店源码下载根据Topic来消费,会用到ConsumerQueue索引。

        也可根据返回的offsetMsgId,解析出ip,端口和CommitLog中的物理消息偏移量,直接去CommitLog中取数据。

        引入的目的主要是提高消息消费的性能,由于RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要遍历commitlog文件中根据topic检索消息是非常低效的。Consumer即可根据ConsumeQueue来查找待消费的消息。

        其中,ConsumeQueue(逻辑消费队列)作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。consumequeue文件可以看成是基于topic的commitlog索引文件,故consumequeue文件夹的组织方式如下:topic/queue/file三层组织结构,具体存储路径为:$HOME/store/consumequeue/{ topic}/{ queueId}/{ fileName}。同样consumequeue文件采取定长设计,每一个条目共个字节,人民银行 源码分别为8字节的commitlog物理偏移量、4字节的消息长度、8字节tag hashcode,单个文件由W个条目组成,可以像数组一样随机访问每一个条目,每个ConsumeQueue文件大小约5.M。

        IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。Index文件的存储位置是: { fileName},文件名fileName是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为M,一个IndexFile可以保存 W个索引,IndexFile的底层存储设计为在文件系统中实现HashMap结构,故rocketmq的索引文件其底层实现为hash索引。

        按照Message Key查询消息的时候,会用到这个索引文件。

        IndexFile索引文件为用户提供通过“按照Message Key查询消息”的消息索引查询服务,IndexFile文件的存储位置是: { fileName},文件名fileName是以创建时的时间戳命名的,文件大小是固定的,等于+W 4+W = 个字节大小。如果消息的properties中设置了UNIQ_KEY这个属性,就用 topic + “#” + UNIQ_KEY的value作为 key 来做写入操作。如果消息设置了KEYS属性(多个KEY以空格分隔),也会用 topic + “#” + KEY 来做索引。

        其中的索引数据包含了Key Hash/CommitLog Offset/Timestamp/NextIndex offset 这四个字段,一共 Byte。NextIndex offset 即前面读出来的 slotValue,如果有 hash冲突,就可以用这个字段将所有冲突的索引用链表的方式串起来了。Timestamp记录的是消息storeTimestamp之间的差,并不是一个绝对的时间。整个Index File的结构如图, Byte 的Header用于保存一些总的统计信息,4 W的 Slot Table并不保存真正的索引数据,而是保存每个槽位对应的单向链表的头。 W 是真正的索引数据,即一个 Index File 可以保存 W个索引。

        “按照Message Key查询消息”的方式,RocketMQ的具体做法是,主要通过Broker端的QueryMessageProcessor业务处理器来查询,读取消息的过程就是用topic和key找到IndexFile索引文件中的一条记录,根据其中的commitLog offset从CommitLog文件中读取消息的实体内容。

        RocketMQ中有两个核心模块,remoting模块和store模块。remoting模块在NameServer,Produce,Consumer和Broker都用到。store只在Broker中用到,包含了存储文件操作的API,对消息实体的操作是通过DefaultMessageStore进行操作。

        属性和方法很多,就不往这里放了。

        文件存储实现类,包括多个内部类

        · 对于文件夹下的一个文件

        上面介绍了broker的核心业务流程和架构,关键接口和类,启动流程。最后介绍一下broker的线程模型,只有知道了线程模型,才能大概知道前面介绍的那些事如何协同工作的,对broker才能有一个立体的认识。

        RocketMQ的RPC通信采用Netty组件作为底层通信库,同样也遵循了Reactor多线程模型,同时又在这之上做了一些扩展和优化。关于Reactor线程模型,可以看看我之前写的这篇文档: Reactor线程模型

        上面的框图中可以大致了解RocketMQ中NettyRemotingServer的Reactor 多线程模型。一个 Reactor 主线程(eventLoopGroupBoss,即为上面的1)负责监听 TCP网络连接请求,建立好连接,创建SocketChannel,并注册到selector上。RocketMQ的源码中会自动根据OS的类型选择NIO和Epoll,也可以通过参数配置),然后监听真正的网络数据。拿到网络数据后,再丢给Worker线程池(eventLoopGroupSelector,即为上面的“N”,源码中默认设置为3),在真正执行业务逻辑之前需要进行SSL验证、编解码、空闲检查、网络连接管理,这些工作交给defaultEventExecutorGroup(即为上面的“M1”,源码中默认设置为8)去做。而处理业务操作放在业务线程池中执行,根据 RomotingCommand 的业务请求码code去processorTable这个本地缓存变量中找到对应的 processor,然后封装成task任务后,提交给对应的业务processor处理线程池来执行(sendMessageExecutor,以发送消息为例,即为上面的 “M2”)。

        上面的图和这段画是从官方文档抄过来的,但是文字和图对应的不是很好,画的也不够详细,但是主要流程是这个样子。以后有时间了,我重新安装自己的理解,画一张更详细的图。

        AsyncAppender-Worker-Thread-0:异步打印日志,logback使用,应该是守护线程

        FileWatchService:

        NettyEventExecutor:

        NettyNIOBoss_:一个

        NettyServerNIOSelector_:默认为三个

        NSScheduledThread:定时任务线程

        ServerHouseKeepingService:守护线程

        ThreadDeathWatch-2-1:守护线程,Netty用,已经废弃

        RemotingExecutorThread(1-8):工作线程池,没有共用NettyServerNIOSelector_,直接初始化8个线程

        AsyncAppender-Worker-Thread-0:异步打印日志,logback使用,共九个:

        RocketmqBrokerAppender_inner

        RocketmqFilterAppender_inner

        RocketmqProtectionAppender_inner

        RocketmqRemotingAppender_inner

        RocketmqRebalanceLockAppender_inner

        RocketmqStoreAppender_inner

        RocketmqStoreErrorAppender_inner

        RocketmqWaterMarkAppender_inner

        RocketmqTransactionAppender_inner

        SendMessageThread_:remotingServer.registerProcessor(RequestCode.SEND_MESSAGE

        PullMessageThread_:remotingServer.registerProcessor(RequestCode.PULL_MESSAGE

        ProcessReplyMessageThread_:remotingServer.registerProcessor(RequestCode.SEND_REPLY_MESSAGE

        QueryMessageThread_:remotingServer.registerProcessor(RequestCode.QUERY_MESSAGE

        AdminBrokerThread_:remotingServer.registerDefaultProcessor

        ClientManageThread_:remotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT

        HeartbeatThread_:remotingServer.registerProcessor(RequestCode.HEART_BEAT

        EndTransactionThread_:remotingServer.registerProcessor(RequestCode.END_TRANSACTION

        ConsumerManageThread_:remotingServer.registerProcessor(RequestCode.GET_CONSUMER_LIST_BY_GROUP,RequestCode.UPDATE_CONSUMER_OFFSET,RequestCode.QUERY_CONSUMER_OFFSET

        brokerOutApi_thread_:BrokerController.registerBrokerAll(true, false, true);

        ==================================================================

        BrokerControllerScheduledThread:=>

        BrokerController.this.getBrokerStats().record();

        BrokerController.this.consumerOffsetManager.persist();

        BrokerController.this.consumerFilterManager.persist();

        BrokerController.this.protectBroker();

        BrokerController.this.printWaterMark();

        log.info("dispatch behind commit log { } bytes", BrokerController.this.getMessageStore().dispatchBehindBytes());

        BrokerController.this.brokerOuterAPI.fetchNameServerAddr();

        BrokerController.this.printMasterAndSlaveDiff();

        BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());

        BrokerFastFailureScheduledThread:=>

        FilterServerManagerScheduledThread:=>

        FilterServerManager.this.createFilterServer();

        ClientHousekeepingScheduledThread:=>

        ClientHousekeepingService.this.scanExceptionChannel();

        PullRequestHoldService

        FileWatchService

        AllocateMappedFileService

        AcceptSocketService

        BrokerStatsThread1

R爬虫必备基础——CSS+SelectorGadget

        CSS,全称叫作Cascading Style Sheets,即层叠样式表。“层叠”是指当在HTML中引用了数个样式文件,并且样式发生冲突时,浏览器能依据层叠顺序处理。“样式”指网页中文字大小、颜色、元素间距、排列等格式。HTML定义了网页的结构,但是只有HTML页面的布局并不美观,可能只是简单的节点元素的排列,为了让网页看起来更好看一些,这里借助了CSS。CSS是目前唯一的网页页面排版样式标准,有了它的帮助,页面才会变得更为美观。如下图的右侧,即为CSS。

        就局部放大来看,下图所示就是一个CSS样式。大括号前面是一个CSS选择器,此选择器的意思是首先选中id为head_wrapper且class为s-ps-islite的节点,然后再选中其内部的class为s-p-top的节点。大括号内部写的就是一条条样式规则,例如position指定了这个元素的布局方式为绝对布局,bottom指定元素的下边距为像素,width指定了宽度为%占满父元素,height则指定了元素的高度。也就是说,我们将位置、宽度、高度等样式配置统一写成这样的形式,然后用大括号括起来,接着在开头再加上CSS选择器,这就代表这个样式对CSS选择器选中的元素生效,元素就会根据此样式来展示了。在网页中,一般会统一定义整个网页的样式规则,并写入CSS文件中(其后缀为css)。在HTML中,只需要用link标签即可引入写好的CSS文件,这样整个页面就会变得美观、优雅。

        在爬虫过程中都需要爬取目标的节点,我们知道网页由一个个节点组成,CSS选择器会根据不同的节点设置不同的样式规则,那什么是节点?又怎样来定位节点呢?下面围绕这两个问题进行介绍。

        在HTML中,所有标签定义的内容都是节点,它们构成了一个HTML DOM树。我们先看下什么是DOM,DOM是W3C(万维网联盟)的标准,其英文全称Document Object Model,即文档对象模型。它定义了访问HTML和XML文档的标准:W3C文档对象模型(DOM)是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。W3C DOM标准被分为如下3个不同的部分:

        核心DOM:针对任何结构化文档的标准模型。

XML DOM:针对XML文档的标准模型。

HTML DOM:针对HTML文档的标准模型。

        根据W3C的HTML DOM标准,HTML文档中的所有内容都是节点。整个文档是一个文档节点;每个HTML元素是元素节点;HTML元素内的文本是文本节点;每个HTML属性是属性节点;注释是注释节点。HTML DOM将HTML文档视作树结构,这种结构被称为节点树,如下图所示。

        节点树中的节点彼此拥有层级关系。我们常用父(parent)、子(child)和兄弟(sibling)等术语描述这些关系。父节点拥有子节点,同级的子节点被称为兄弟节点。在节点树中,顶端节点称为根(root)。除了根节点之外,每个节点都有父节点,同时可拥有任意数量的子节点或兄弟节点。下图展示了节点树以及节点之间的关系。

        在CSS中,我们使用CSS选择器来定位节点。例如,下图中div节点的id为container,那么就可以表示为#container,其中#开头代表选择id,其后紧跟id的名称。另外,如果我们想选择class为wrapper的节点,便可以使用.wrapper,这里以点(.)开头代表选择class,其后紧跟class的名称。另外,还有一种选择方式,那就是根据标签名筛选,例如想选择二级标题,直接用h2即可。这是最常用的3种表示,分别是根据id、class、标签名筛选,请牢记它们的写法。

        另外,CSS选择器还支持嵌套选择,各个选择器之间加上空格分隔开便可以代表嵌套关系,如#container .wrapper p则代表先选择id为container的节点,然后选中其内部的class为wrapper的节点,然后再进一步选中其内部的p节点。另外,如果不加空格,则代表并列关系,如div#container .wrapper p.text代表先选择id为container的div节点,然后选中其内部的class为wrapper的节点,再进一步选中其内部的class为text的p节点。这就是CSS选择器,其筛选功能还是非常强大的。另外,CSS选择器还有一些其他语法规则,具体如下表所示。

        但是,这样每次都要浪费部分时间去寻找定位点,这样既不很方便,也不高效,那么如何提高爬虫中这部分工作的效率呢?今天我给大家分享一个爬虫的利器,它就是:SelectorGadget 插件。

        point and click CSS selectors,强大的谷歌插件CSS生成器,有助于我们快速找到html的节点信息,它也支持Xpath表达式。Selector Gadget是一个开源的Chrome扩展程序,可以轻松地在复杂的网站上生成和选择CSS选择器。安装扩展程序后,转到任意页面并启动它。网站右下方会打开一个方框。单击您希望选择器匹配的页面元素(它将变为绿色)。然后SelectorGadget将为该元素生成一个最小的CSS选择器,并突出显示(黄色)选择器匹配的所有内容。现在单击突出显示的元素将其从选择器中删除(红色),或单击未突出显示的元素将其添加到选择器。通过这个选择和拒绝过程,SelectorGadget可以帮助您找到满足您需求的完美CSS选择器。

        首先需要安装一下这个神器。在谷歌浏览器中的应用商店里,搜索到SelectorGadget插件,点击“添加至Chrome”即可。如果不能打开Chrome应用商店,可以通过网上的其他途径获取该插件,之后再手动添加至谷歌浏览器即可。手动添加方法是:打开谷歌浏览器扩展程序,并开启开发者模式,将该插件拖拽到浏览器里,如果不成功,可以选择“加载已解压的扩展程序”,将该文件夹先压缩再解压添加进去。

        同时在页面栏上能看到红框处的SelectorGadget标志。

        咱们以 / 网页为例,首先点击网页上方的SelectorGadget,然后在网页下方弹出SelectorGadget框。接下来,咱们尝试一下如何使用?比如我们想定位“拆弹专家2”的节点,直接点击它,则会在框内显示其节点——.item-title(如下图)。绿色:单击希望选择器匹配的页面元素。黄色:生成的这个CSS选择器【.item-title】能匹配的所有内容,从下图可以发现该CSS选择器可以抓取哪些信息。当你把鼠标放到这些高亮的元素上时,会显示红色,代表要将其从选择器中删除;而单击未突出显示的元素将其添加到选择器。

        在实际构建CSS表达式过程中,需要搭配网页开发工具里的copy selector功能使用,非常方便。比如:定位抓取以下网页( /allmovies )红框内的所有电影名称信息。

        具体操作流程如下:在某一电影名称下右击——检查,定位到电影名称的网页源码处,然后右击—copy—copy selector,再打开SelectorGadget,在框内粘贴CSS表达式,按Enter键,发现在网页中准确定位出该电影名称。但我们需要定位所有的电影名称,在已有的CSS表达式【(body > div:nth-child(5) > div.inner-wrapper > div.inner-2col-main > div > ul > li:nth-child(1) > a > span.item-title)】上进行修改,这里可以发现【li:nth-child(1)】只选取了第一个,而我们所需选取所有,因此去掉后面的【(1)】即可。在具体实践中,如何修改还有有赖于小伙伴对网页结构和CSS语法的理解。最终,获取以上红框内电影名称的CSS选择器表达式为:【body > div:nth-child(5) > div.inner-wrapper > div.inner-2col-main > div > ul > li:nth-child > a > span.item-title】。在实际应用中,不同节点标签之间用空格分隔:【body div:nth-child(5) div.inner-wrapper div.inner-2col-main div ul li:nth-child a span.item-title】。

        但在SelectorGadget使用中,发现其构建的CSS表达式往往很复杂,大部分情况下其实也是可以自己构建CSS表达式的。构建CSS选择表达式的关键在于清楚整个网页结构,找到标签之间的属于关系、属性值属于哪个标签等,就可以比较快速的构建。而且浏览器很智能,鼠标在源码上的位置,可以在网页显示上呈现阴影,这个功能简直太香了。以上红框内所有电影名字的CSS表达式也可以表示为:【div.movlist ul li a span.item-title】

        然后用SelectorGadget验证,如下图,发现高亮选中的部分就是我想要定位的信息,说明这个CSS表达式正确。

        以上简单介绍了如何构建CSS表达式,在爬虫过程中定位抓取特定节点数据是非常重要的一步,有了这步才有下游的精准数据提取与清洗。希望本次教程能给有需要的小伙伴一点小小帮助!

        更多内容可关注公共号“YJY技能修炼”~~~

        往期回顾

        R爬虫在工作中的一点妙用

        R爬虫必备基础——HTML和CSS初识

        R爬虫必备基础——静态网页+动态网页

        R爬虫必备——rvest包的使用

搜索关键词:spring源码模板