1. 从标题拆解看真实需求一个爬虫项目背后的三层信息这个标题信息量其实挺大拆开来看至少包含三个独立但相关的层面第一层是技术实践用Python写一个针对影视资源站点的爬虫第二层是时间标签强调2024年最新意味着要面对当前网站普遍采用的反爬策略第三层是求职场景标题后半段提到的面试经历反映的是很多人在学爬虫时的一个真实焦虑——学了能不能用、面试会不会问、问到什么程度。我打算把这三层都聊透但重心放在爬虫本身的技术实现上面试那部分放在最后作为一个延伸话题来谈因为技术底子扎实了面试表现是水到渠成的事。先说清楚这个项目到底在做什么。所谓追剧网爬虫本质上是一个针对影视剧集信息聚合类站点的数据采集程序。这类站点通常会把热门剧集的名称、更新状态、集数列表、播放源地址等信息集中展示爬虫要做的就是把这些结构化或半结构化的数据提取出来整理成可用的格式。听起来不复杂但2024年的站点环境和五年前完全不是一回事——动态渲染、接口加密、请求频率检测、行为指纹识别这些手段已经非常普遍。所以这个项目的核心难点不在于能不能爬到而在于怎么爬得稳、爬得干净、爬得不给自己惹麻烦。适合谁来参考这份内容我的判断是有Python基础语法概念、了解requests库基本用法、但还没有完整做过一个真实站点采集项目的朋友最合适。如果你已经能独立处理JS逆向和验证码识别那这篇里的部分内容对你来说偏基础但关于工程化组织和合规边界的讨论仍然值得一看。如果你完全没写过代码建议先把列表、字典、函数、异常处理这几个概念过一遍再回来不然实操环节会卡得比较难受。注意本文所有技术讨论均以学习数据采集原理、锻炼工程能力为目的。实际采集行为必须遵守目标站点的服务条款与相关法律法规控制请求频率不采集和传播受版权保护的内容不将数据用于商业用途。这是从业者的基本底线也是保护自己的第一道防线。2. 项目整体设计与技术选型思路2.1 为什么选requests加解析库而不是一上来就上框架很多人一提到爬虫就想到Scrapy觉得不上框架就不够专业。我的经验恰恰相反对于单一站点、中等数据量、结构相对固定的采集任务requests配合解析库的组合在开发效率和调试便利性上明显占优。Scrapy的优势在于大规模分布式采集、多站点统一调度、内置去重和重试机制但它的学习曲线和调试成本对新手不友好——一个中间件配置错了报错信息能让你找半天。这个项目我选择的是requests负责网络请求、BeautifulSoup负责HTML解析、lxml作为解析引擎加速、pandas负责数据整理和导出。这套组合的逻辑是请求层保持轻量可控解析层兼顾容错和速度输出层直接对接常用的表格格式。如果目标站点的数据是通过接口返回的JSON那解析环节可以直接用内置的json模块连BeautifulSoup都省了。选型背后有一个关键判断先摸清数据是怎么来的再决定用什么工具。具体做法是打开浏览器开发者工具切到Network面板刷新页面观察请求列表。如果剧集列表直接出现在HTML文档里说明是服务端渲染用requests拿到的源码里就有数据如果HTML里只有一个空壳容器数据是后续通过XHR请求填充的那就得去找对应的接口。这个判断决定了整个项目的技术路线花十分钟做这个侦查能省掉后面几个小时的弯路。2.2 分层架构把采集流程拆成四个独立环节我把整个程序拆成四层请求层、解析层、存储层、调度层。请求层封装会话管理、请求头构造、重试逻辑和频率控制解析层负责从响应内容中提取目标字段并对缺失字段做容错存储层处理数据清洗、去重和落盘调度层管理待采集的URL队列和翻页逻辑。这样拆的好处是每一层可以单独测试和替换——比如某天发现站点换了页面结构只需要改解析层请求和存储逻辑完全不用动。分层还有一个实际好处调试的时候能快速定位问题出在哪一环。请求层出问题表现为状态码异常或响应内容为空解析层出问题表现为字段提取不到但响应正常存储层出问题表现为数据重复或格式错乱。这种症状到环节的映射关系在排查问题时非常省时间。2.3 反爬预判2024年站点常见的四道门槛在动手写代码之前我习惯先列一张可能遇到的拦截手段清单然后逐项准备应对方案。当前影视类站点常见的门槛大致有四类第一是请求头校验检查User-Agent、Referer、Accept-Language等字段是否像正常浏览器第二是频率限制短时间内同一IP请求过多会触发限流或临时封禁第三是动态渲染核心数据由前端脚本异步加载第四是参数签名接口请求需要携带由特定算法生成的签名参数。对应的准备策略是请求头方面准备一套完整的浏览器指纹字段并随机轮换频率方面设置请求间隔并加入随机抖动动态渲染方面优先找接口找不到再考虑浏览器自动化方案参数签名方面先观察请求参数规律简单的可以直接构造复杂的需要分析前端脚本逻辑。这四类门槛的应对难度是递增的实际项目中往往只需要处理前两类就能跑通。3. 核心细节解析请求构造与解析逻辑的关键点3.1 请求头不是随便填填就行很多人写爬虫时请求头只填一个User-Agent就完事这在简单站点上能跑但在有校验的站点上大概率吃闭门羹。一个看起来像人的请求头至少应该包含User-Agent、Accept、Accept-Language、Accept-Encoding、Connection这几个字段如果站点有Referer校验还要带上来源页地址。我的做法是直接从浏览器开发者工具里复制一个真实请求的完整头信息存成一个字典模板运行时对User-Agent做随机轮换。这里有个细节值得说Accept-Encoding字段如果声明支持gzip或br压缩requests会自动解压但如果你手动设置了错误的编码声明可能导致响应内容乱码。稳妥的做法是保留浏览器默认的编码声明让requests自己处理解压。另外Connection字段设成keep-alive可以复用TCP连接在连续请求同一站点时能明显降低延迟但要注意配合会话对象使用单独用requests.get每次都是新连接这个字段就失去意义了。3.2 会话对象的价值与使用姿势用requests.Session()创建会话对象好处有三个自动保持Cookie、复用底层连接、统一管理默认请求头。对于需要先访问首页获取某些初始化Cookie、再访问数据页的站点会话对象几乎是必需品。我通常的写法是创建一个session实例把公共请求头通过session.headers.update()一次性设置好后续所有请求都走这个session代码干净且不易遗漏字段。需要提醒的是会话对象不是万能的。如果站点通过其他维度比如浏览器指纹、TLS握手特征做识别光靠会话保持Cookie解决不了问题。另外会话对象在多线程环境下不是线程安全的如果要做并发采集每个线程应该持有独立的会话实例或者改用连接池方案。这个坑我在早期项目里踩过多线程共用一个session导致Cookie错乱排查了大半天才发现原因。3.3 解析策略先定位容器再提取字段解析HTML时最容易犯的错误是直接对着整个文档写选择器结果页面结构稍有变动就全线崩溃。更稳的做法是分两步走先定位到包含目标数据的容器节点再在容器内部提取具体字段。比如剧集列表通常在一个特定的ul或div容器里先把这个容器找出来然后遍历它的子节点提取每一条记录。这样即使页面其他部分改版只要容器结构不变解析逻辑就不用动。字段提取时要有容错意识。不是每条记录都包含全部字段——有的剧集没有评分有的没有简介有的封面图地址是懒加载占位符。我的处理方式是每个字段提取时都给一个默认值提取失败就填默认值并记录一条警告日志而不是让整个程序抛异常中断。数据完整性固然重要但采集过程中因为个别字段缺失就全盘失败是得不偿失的。3.4 数据清洗爬下来只是半成品原始采集到的数据往往带着大量噪音字段前后有空白字符、集数信息混着更新时间的文字、播放源地址里带着跟踪参数。清洗环节要做的事情包括去除首尾空白、用正则提取结构化信息、过滤无效记录、统一字段格式。比如更新至第12集这样的文本需要用正则把数字12提取出来存成整型字段同时保留原始文本以备核对。去重也是清洗的重要一环。同一部剧可能在列表页出现多次比如同时在热门和最新两个板块采集时会产生重复记录。我的做法是以剧集详情页的URL作为唯一标识做去重因为名称可能重复但URL是唯一的。去重逻辑放在存储层用集合记录已处理的URL新记录入库前先查集合这样即使调度层重复下发了任务也不会产生脏数据。4. 实操过程从零跑通一个采集流程4.1 环境准备与依赖安装基础环境是Python 3.9以上版本依赖库四个requests、beautifulsoup4、lxml、pandas。安装命令很直接pip install requests beautifulsoup4 lxml pandaslxml是BeautifulSoup的解析引擎比内置的html.parser快不少而且对不规范HTML的容错更好。pandas只在最后导出数据时用到如果只想输出JSON或CSV用内置的csv和json模块也能实现看个人习惯。我建议新手把pandas装上因为后续做数据统计和格式转换时它会省很多事。4.2 第一步用开发者工具摸清数据来源打开目标站点的剧集列表页按F12打开开发者工具切到Network面板筛选Fetch/XHR类型然后刷新页面。观察列表里出现的请求逐个点击查看Response内容找到返回剧集数据的那个请求。记下它的URL、请求方法、请求头关键字段和参数结构。如果Response里是规整的JSON那恭喜你解析工作简化成字典取值如果是HTML片段那就用BeautifulSoup处理。这一步还有一个重要观察点翻页是怎么实现的。有的站点翻页是URL参数变化比如page2有的是POST请求带偏移量有的是滚动加载触发新请求。搞清楚翻页机制才能设计调度层的循环逻辑。我一般会手动翻两三页对比请求URL和参数的差异规律很快就能看出来。4.3 第二步封装请求函数并加入频率控制请求函数的核心逻辑是构造完整请求头、发起请求、检查状态码、返回响应内容、异常时重试。频率控制用time.sleep加随机抖动实现基础间隔设1到2秒每次请求前随机浮动0.5秒左右。这个间隔不是拍脑袋定的——太短容易触发限流太长采集效率低1到2秒是多数中小型站点的安全区间。如果站点响应特别快且没有明显限流迹象可以适当缩短但不要低于0.5秒。重试逻辑我设置最多三次每次重试前等待时间递增比如2秒、4秒、8秒这样既能应对偶发的网络抖动又不会在站点明确拒绝时死缠烂打。状态码403和429要特别对待——403通常意味着被识别需要检查请求头429意味着频率超限应该加大间隔而不是立即重试。这两种情况立即重试只会让情况更糟。import requests import time import random session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, }) def fetch(url, retries3): for attempt in range(retries): try: time.sleep(1 random.uniform(0, 0.5)) resp session.get(url, timeout10) if resp.status_code 200: resp.encoding resp.apparent_encoding return resp.text elif resp.status_code in (403, 429): time.sleep(5 * (attempt 1)) else: time.sleep(2 * (attempt 1)) except requests.RequestException: time.sleep(2 * (attempt 1)) return None这段代码里resp.encoding的设置容易被忽略。requests有时会猜错编码导致中文变成乱码用apparent_encoding让chardet库根据内容推断编码准确率更高。如果站点明确声明了编码比如响应头里有charset那用resp.encoding直接指定更稳妥。4.4 第三步解析列表页并提取字段假设已经确认数据在HTML文档里解析逻辑大致如下先找到列表容器遍历其中的条目节点对每个节点提取标题、详情页链接、更新状态、封面图地址等字段。选择器的写法取决于具体页面结构这里给一个通用骨架from bs4 import BeautifulSoup def parse_list(html): soup BeautifulSoup(html, lxml) items [] container soup.select_one(div.list-container) if not container: return items for node in container.select(div.item): title_node node.select_one(h3.title) link_node node.select_one(a) status_node node.select_one(span.status) items.append({ title: title_node.get_text(stripTrue) if title_node else , detail_url: link_node.get(href, ) if link_node else , status: status_node.get_text(stripTrue) if status_node else , }) return items每个字段都做了空值判断这是解析层的铁律。select_one找不到节点时返回None直接调get_text会抛AttributeError加个判断就能避免整批数据因为一条异常记录而丢失。stripTrue参数去掉文本首尾空白省去后续清洗的一步。4.5 第四步翻页调度与数据落盘翻页逻辑用一个循环实现从第一页开始每解析完一页就构造下一页的URL直到没有下一页或者达到预设的页数上限。页数上限一定要设这是防止程序失控的重要保险——万一翻页判断逻辑有bug导致无限循环上限能兜住。我一般设50页作为默认值实际需要更多再调。数据落盘用pandas的DataFrame采集过程中先把记录追加到列表全部完成后一次性转成DataFrame导出CSV。这样做的好处是导出格式规整、编码问题少而且后续要做去重和统计都很方便。如果数据量大到内存放不下那就改成边采集边追加写入CSV用csv模块的writer逐行写牺牲一点便利性换内存安全。import pandas as pd all_items [] for page in range(1, 51): url fhttps://example.com/list?page{page} html fetch(url) if not html: break items parse_list(html) if not items: break all_items.extend(items) df pd.DataFrame(all_items).drop_duplicates(subset[detail_url]) df.to_csv(drama_list.csv, indexFalse, encodingutf-8-sig)encoding用utf-8-sig是为了让Excel打开CSV时中文不乱码这个小细节在实际使用中能省掉很多为什么打开是乱码的困惑。5. 常见问题与排查技巧实录5.1 请求返回200但内容是空的或者不对这是最常见的问题症状是状态码正常但拿到的HTML里没有目标数据。原因通常有三种一是数据由前端异步加载HTML里本来就没有二是站点识别出爬虫后返回了一个空壳页面状态码故意给200迷惑你三是编码问题导致内容看起来是乱码。排查顺序是先在浏览器里查看页面源码不是Elements面板是View Source确认数据是否在原始HTML里如果在对比爬虫拿到的内容和浏览器源码的差异如果不在回到Network面板找接口。5.2 采集到一半突然全部失败如果前几十条请求正常之后突然全部返回403或超时基本可以确定触发了频率限制或IP层面的封禁。这时候不要急着改代码重试先停下来等一段时间半小时到一小时期间检查请求间隔是否过短、是否有并发请求叠加。我的经验是多数中小型站点的限流窗口在几分钟到几十分钟之间等待后恢复的概率很高。恢复后把间隔调大一倍再跑观察是否还会触发。5.3 解析字段时好时坏同一套选择器有的页面能提取到字段有的提取不到通常是页面结构存在多种变体。比如置顶的剧集卡片结构和普通卡片不一样或者广告位混在列表里导致节点层级错位。解决办法是打印出提取失败的节点HTML对比正常节点的差异然后调整选择器使其兼容两种结构或者在遍历时跳过不符合预期的节点。容错逻辑在这里的价值就体现出来了——个别失败不影响整体事后可以针对失败记录单独补采。5.4 数据重复与遗漏并存重复通常来自列表页的多板块展示遗漏通常来自翻页逻辑的边界处理。去重方案前面说过用唯一URL做标识。遗漏的排查方法是手动数一下目标站点某分类下的总条目数和采集结果对比差多少、差在哪几页。常见原因是最后一页的判断逻辑有误或者某页请求失败后直接跳过了而没有重试。把每页的采集条数打印出来哪一页异常一目了然。问题现象最可能原因排查动作处理方案状态码200但无数据异步加载或空壳响应查看页面源码对比找接口或调整请求头中途全部403频率限制或IP封禁检查请求间隔与并发暂停等待后降频重试字段时有时无页面结构多变体打印失败节点HTML调整选择器兼容变体数据重复多板块重复展示统计URL重复率按唯一URL去重数据遗漏翻页边界或请求失败逐页统计条数补采失败页并修正边界5.5 几个容易被忽略的实操心得第一个心得日志比print好用得多。用logging模块把每次请求的URL、状态码、耗时、解析条数记录下来出问题时翻日志比在代码里到处加print高效十倍。日志级别设INFO异常单独记WARNING跑完之后一眼就能看出哪一页出了问题。第二个心得先小规模验证再全量跑。写完解析逻辑后先只跑第一页人工核对提取的字段是否准确、有没有错位、特殊字符处理是否正常。确认无误再放开页数限制。我见过太多人直接全量跑跑完发现字段全错位白等半小时。第三个心得把目标站点的结构变化当成常态。今天能跑的代码下个月可能就失效了。所以解析逻辑要写得松耦合选择器尽量用稳定的特征比如语义化的class名而不是依赖层级位置比如div div:nth-child(3) span。后者在页面加一个广告位之后就全乱了。6. 关于面试场景的一点延伸标题后半段提到的面试经历其实反映了一个普遍现象很多人把做过爬虫项目写进简历但被追问细节时说不清楚。面试官问爬虫项目真正想考察的不是你会不会用requests而是你对网络协议的理解、对异常情况的处理思路、对工程边界的把握。比如遇到反爬怎么办这个问题好的回答是先分类讨论请求头、频率、动态渲染、签名再说明每类的应对思路和取舍理由而不是上来就说换代理或者上selenium。再比如数据量大了怎么优化这个问题考察的是架构意识请求层做连接复用和并发控制、解析层做增量处理、存储层做批量写入和去重。能把这些环节讲清楚比堆砌工具名词有说服力得多。至于面试结果一轮就结束的情况有很多种原因技术只是其中一环不必过度归因。把项目做扎实、把原理想明白该来的机会自然会来。我个人在实际操作中的体会是爬虫这个方向入门容易但做好难难的不是写代码而是对目标站点的理解和对边界的把握。每次做新项目我花在侦查和方案设计上的时间往往比写代码还多但这份投入是值得的——前期想清楚了后期调试的时间能省下一大半。另外采集行为一定要有节制控制频率、尊重站点规则、不碰敏感数据这既是合规要求也是让自己能长期做这件事的前提。
企业数字化 ERP 产品动态
相关推荐
3.1 OJ:程序员必备的算法竞赛训练平台 1. 什么是3.1 OJ?3.1 OJ这个名称乍一看可能会让人摸不着头脑,但作为一名在编程竞赛领域摸爬滚打多年的老手,我可以负责任地告诉你,这绝对是一个值得每个程序员关注的宝藏资源。简单来说,3.1 OJ是一个在线评测系统(Onli… · 2026/11/5 8:58:33
电力电子变流器研发的三维协同仿真方法论 1. 变流器研发的三维协同方法论在电力电子领域,变流器研发从来不是单一工具能够完成的任务。从业十年间,我见证过太多工程师在仿真工具选择上的纠结——有人执着于Simulink的全能,有人迷信PLECS的专业,还有人陷入RT-LAB的实时性不… · 2026/11/5 1:28:28
垂直行业模型与通用大模型怎么选?从知识密度到落地成本全解析 1. 先搞清楚一件事:行业模型不是"更小的通用模型"几个月前跟一位制造业的CIO聊天,他开门见山就问:"我们现在用通用大模型做客服问答,效果还行,但供应商一直推私有化部署的行业模型,一张口就… · 2026/11/5 18:43:26
EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析 1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被… · 2026/11/5 16:49:35
Rust原来很简单二:所有权系统 Part 2 核心篇 —— 所有权系统
本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理
6… · 2026/11/5 16:26:08
基于Hadoop+Spark的民宿智能推荐系统设计与实现 1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享… · 2026/11/5 14:59:25
ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 14:10:41
UART接口24位ADC搭配RS485,解决远距离温度监控难题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 14:10:41
UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 14:10:41
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07