尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
用Python分析流媒体听歌记录:从导出到可视化打造个人年度音乐报告
你在音乐App里听了几千个小时的歌但年底盘点一到热门榜单翻来覆去就那么几首你想看的私藏数据根本没有。最直接的办法就是用Python把“听歌记录”这个原始数据拿出来自己算一周通勤路上到底在循环谁、深夜失眠时反复听哪几首、哪张专辑被你默默刷了一百遍。这篇文章不是讲怎么用现成的年度报告而是从第一步导出数据开始带你完整走一遍清洗、统计、可视化的流程让那些只有你自己才懂的收听习惯变成一张张清清楚楚的表格和图表。这件事谁需要只要你在用支持个人数据导出的流媒体音乐平台又稍微会一点Python就能跟着做。不会Python也没关系代码我会一段段贴出来注释写到能直接抄作业的程度。最终你会得到自己专属的音乐分析脚本以后每年都能跑一遍。1. 先想清楚分析听歌数据到底在分析什么1.1 别急着写代码先列需求很多人一上来就打开Jupyter Notebook读了几个JSON就开始画图结果画出来的东西自己都觉得没意思。原因是没想明白“我要从数据里证明什么”。听歌数据能回答的问题其实非常具体这一年里我播得最多的5首歌是什么这个“最多”是按播放次数算还是按收听时长算我到底为哪个歌手花了最多时间是那种“没特别关注但每次随机到都不切”的艺人还是“单曲循环一整天”的那种不同时间段我的口味一样吗上班路上和睡前完全是两个听歌人格有多少歌被我听了不到5秒就划走了哪些歌又是“点开就舍不得停”的我的收听习惯有没有随着季节变夏天和冬天的歌单差异大不大这些问题背后对应着不同的字段和处理逻辑。播放次数和累计时长的排序结果经常不一样某首2分钟的歌听了50次累计100分钟另一首10分钟的长篇播客听了20次累计200分钟。只看次数会低估长内容只看时长会放过那些每天都要点一遍的短旋律。好在我们的数据里msPlayed字段记录的是“实际播放的毫秒数”所以两个角度都能算。1.2 数据从哪来导出数据 vs 开放接口做个人数据分析有两条路第一条是去账户设置里申请“导出我的数据”。平台会把你的历史记录打包成一个ZIP文件里面包括听歌历史、歌单、搜索记录、支付记录等。优点是数据完整能覆盖很多年前的记录缺点是申请后要等几天收到的是“申请日之前”的数据最新几天的记录不在包里。第二条是调用平台开放接口。接入后可以拉取最近一段时间比如最近30天的播放记录。优点是实时、能反复跑缺点是历史深度有限而且需要进行授权认证。我自己的建议是玩深度分析、做年度盘点走导出路线做日常打卡、每周看自己新听了什么走接口路线。两个方案在本次分析里我都会覆盖后面会给出对应的代码骨架。2. 准备数据导出包和接口申请2.1 去账户设置里申请导出数据不同平台的位置不一样但大体在“账号设置 → 隐私与数据 → 请求我的数据”这类入口下。有的平台支持选择数据范围我建议选“完整历史记录”别只选“账号信息”。既然要分析就把整个历史都拿回来。提交申请后一般是邮件通知快的几个小时慢的可能要一周。平台打包时会把数据拆成很多个JSON文件而不是一个巨型文件这么做的原因我猜是为了降低单文件大小、方便断点下载。这也意味着我们在分析时要用glob模式把文件一口气读进来而不是手动读一个。2.2 导出包里的目录结构解压之后一般会看到这些内容MyData/ ├── Identity.json ├── StreamingHistory/ │ ├── StreamingHistory0.json │ ├── StreamingHistory1.json │ └── StreamingHistory2.json ├── Playlist/ │ ├── Playlist1.json │ └── Playlist2.json ├── Payments.json ├── SearchQueries.json └── Userdata.json这里最容易踩的坑是看到有多个StreamingHistory0/1/2就只读了一个文件然后得出“我一年就听了200首歌”的荒谬结论。这些文件是分段的必须全部读取再拼接。另外我拿到包里其实还有Identity.json这类涉及个人信息的文件。分析歌单完全用不到它所以我的建议是只在内存里加载需要的列原始文件保持原样别去修改或上传到任何在线服务。注意导出文件在下载后最好放到一个本地独立目录比如D:\music-analysis\data。不要把整个解压包放在代码目录里否则后面读取文件时容易把其他无关JSON也扫进来。2.3 如果你选择API路线获取访问令牌接口路线要解决的第一个问题是“授权”。平台普遍采用OAuth 2.0流程。大致的做法是去平台开发者后台创建应用拿到Client ID和Client Secret。构造一个授权链接让用户登录并同意授权。授权后回调地址里会带一个code用这个code换access_token和refresh_token。以后每次请求接口都用access_token过期时用refresh_token刷新。这一步看似麻烦但换成代码其实不长。下面的示例用了requests库import requests CLIENT_ID 你的Client ID CLIENT_SECRET 你的Client Secret # 第一次手动获取code后换token token_url https://api.example.com/token resp requests.post(token_url, data{ grant_type: authorization_code, code: 上一步拿到的code, redirect_uri: http://localhost:8080/callback, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }) tokens resp.json() refresh_token tokens[refresh_token] access_token tokens[access_token] # 之后刷新token resp requests.post(token_url, data{ grant_type: refresh_token, refresh_token: refresh_token, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }) access_token resp.json()[access_token]拉取最近播放记录就是从一个“最近播放”的接口读取返回的JSON里items数组就是每首歌包含歌曲名、歌手名、播放时间、播放时用的客户端类型等信息。接口的返回结果是按时间倒序的最多返回最近一两个月的记录所以适合做高频小分析不适合做年度总结。2.4 先看看原始数据长什么样导出包的StreamingHistory0.json打开后是下面这样的结构[ { ts: 2024-03-18T08:21:45Z, username: music_lover, platform: android, ms_played: 226417, conn_country: CN, ip_addr: x.x.x.x, user_agent_clean: unknown, master_metadata_track_name: 歌曲示例名称, master_metadata_album_artist_name: 歌手示例, master_metadata_album_album_name: 专辑示例, spotify_track_uri: spotify:track:xxxxxxxx } ]你可能注意到里面有ip_addr这类敏感字段。做音乐分析完全用不到IP地址所以我读数据的时候就把它丢掉了一秒钟都不留在内存里。ts字段是带时区的ISO格式末尾的Z代表UTC时间这很重要——后面如果不做时区转换你在北京时间凌晨听的歌会被算成前一天下午听的图表直接错位。2.5 用pandas读取所有历史文件把多个StreamingHistory*.json读进来的标准写法是这样import json import glob import pandas as pd files sorted(glob.glob(data/StreamingHistory/StreamingHistory*.json)) records [] for f in files: with open(f, r, encodingutf-8) as fp: records.extend(json.load(fp)) df pd.DataFrame(records) # 只保留分析需要的列 df df[[ts, ms_played, master_metadata_track_name, master_metadata_album_artist_name, master_metadata_album_album_name, platform]] print(len(df))这一步跑完df就是一张包含所有历史记录的表。我实测下来如果一个人有上万条记录性能完全没问题如果到了几十万条建议再用usecols的方式进一步瘦身。后面所有分析都在这张表上操作。3. 清洗数据把“脏记录”变成可分析的表3.1 时间字段与时区是最容易错的一步原始数据里的ts是UTC时间不能拿来直接画“几点钟我在听歌”的分布图。因为中国用户在东八区晚8点听歌对应的UTC是中午12点直接按小时统计会把所有时间都往前挪8小时。正确的做法是df[ts] pd.to_datetime(df[ts]) df[ts] df[ts].dt.tz_convert(Asia/Shanghai) df[date] df[ts].dt.date df[hour] df[ts].dt.hour df[weekday] df[ts].dt.dayofweek # 0周一6周日如果你在国外或者不同时区可以把Asia/Shanghai换成America/New_YorkEurope/London等。怎么知道自己的时区直接用pytz.all_timezones查或者用ZoneInfo标准库。3.2 忽略真正“跳过”的记录不是所有记录都代表完整收听。很多人按了下一首只听了3秒系统也会记一条。如果把几百条“只听了几秒”的记录算进歌曲排行榜那些经常被跳过的歌反而可能上榜这就失真了。我一般设定一个阈值ms_played小于5000毫秒5秒的记录视为跳过不计入“收听时长统计”和“次数统计”。played_df df[df[ms_played] 5000].copy()这个阈值你可以自己调。听播客的话建议设高一点比如30秒以上才算有效收听听短歌曲的话5秒也够合理。分析“跳过率”时反而是要保留所有记录算出每首歌被跳过的比例。3.3 重复记录为什么会出现听歌记录通常不会故意重复但实际数据里偶尔会出现完全一样的ts 歌名 时长这可能是网络重试或同步导致。要不要删我的经验是先看比例如果重复记录占比小于0.1%可以不管如果很大就用“完全重复行”的规则去重df df.drop_duplicates(subset[ts, master_metadata_track_name, ms_played])但注意drop_duplicates不能滥用。同一首歌一天听三遍是三行合法的记录它们彼此之间只是“歌名”一样时间戳不同不会被误删。3.4 给字段重新命名写代码时阻力小很多原始字段名太长如果每次都打master_metadata_album_artist_name手会酸。我的习惯是读进来之后立刻重命名df df.rename(columns{ master_metadata_track_name: track, master_metadata_album_artist_name: artist, master_metadata_album_album_name: album, ms_played: ms }) df[seconds] df[ms] / 1000 df[minutes] df[seconds] / 60 df[hours] df[seconds] / 3600后面就越写越顺手。4. 第一轮统计找出你的“年度之歌”4.1 按累计播放时长排名播放次数多不等于喜欢累计时长才是更真实的指标。特别是那种10分钟的长篇音乐剧歌曲听一次顶普通歌三四次。track_stats played_df.groupby([artist, track], as_indexFalse).agg( play_count(ms, count), total_ms(ms, sum) ) track_stats[total_hours] track_stats[total_ms] / 3600000 top_tracks track_stats.sort_values(total_ms, ascendingFalse).head(20) print(top_tracks[[artist, track, play_count, total_hours]])这样得到的排行通常比你记忆中的“年度歌曲”更有代表性。因为人的记忆偏爱情感冲击大的歌曲而数据会把那些“循环但也谈不上多爱”的歌曲也捞出来。4.2 按歌手汇总谁占用了你最多时间歌手级别的汇总和歌曲类似只是把track从分组里去掉artist_stats played_df.groupby(artist, as_indexFalse).agg( play_count(ms, count), total_ms(ms, sum) ) artist_stats[total_hours] artist_stats[total_ms] / 3600000 top_artists artist_stats.sort_values(total_ms, ascendingFalse).head(10)这里有个细节一个歌手名可能因为大小写、空格、换行出现不同的写法。比如“Adele”和“adele”在平台内部可能规范但导出的数据不一定完全一致。稳妥起见可以在分组前统一一下df[artist] df[artist].str.strip().str.title()4.3 跳过率哪些歌总是被你划走一首歌“播放次数多”不等于“受欢迎”也可能是“经常被随机播到然后又跳过”。把跳过率算出来能看到很多有意思的信息。# 用全量数据不过滤5秒阈值 df[is_skipped] (df[ms] 5000).astype(int) skipped_stats df.groupby([artist, track]).agg( total_plays(ms, count), skipped_count(is_skipped, sum), total_ms(ms, sum) ) skipped_stats[skip_rate] skipped_stats[skipped_count] / skipped_stats[total_plays] frequent skipped_stats[skipped_stats[total_plays] 5] high_skip frequent.sort_values(skip_rate, ascendingFalse).head(15)如果某首歌播放了20次其中15次被跳过那它的“真实热度”就是伪热度。反过来跳过率接近0的歌可能是你从不会主动切歌的“安全区”。4.4 播放时长分布短歌曲吃亏吗如果你发现前10名全被5分钟以上的歌曲占据短歌完全没机会不用惊讶。这是算法设计的取舍。补充一个按播放次数排名的榜单两个榜单对照看更有意思top_by_count track_stats.sort_values(play_count, ascendingFalse).head(20)短歌曲在次数榜上会冒头长内容在时长榜上占优。喜欢挖掘自己真实口味的人我推荐多关注“时长榜”因为时长不会被偶然点开一次就污染。5. 听歌与时间一天里的音乐人格5.1 你的“早上八点”在听什么把时间戳转成本地时区后按小时分组再结合歌曲信息就能生成“几点钟我都在听什么”的视图。hourly played_df.groupby([hour, artist, track], as_indexFalse)[ms].sum() hourly_top ( hourly .sort_values([ms], ascendingFalse) .groupby(hour) .head(1) .sort_values(hour) )跑出来的结果一般非常直观早高峰时间段你可能听节奏感强的歌下午容易摇滚晚上突然变成民谣。用这个结果去反推“我的通勤心情”比年底报告里的“最常听的5首歌”有温度多了。5.2 星期维度工作日的音乐和周末完全不一样把星期几也拆出来看工作日和周末的行为差异weekday_hour played_df.groupby([weekday, hour], as_indexFalse)[ms].sum() pivot weekday_hour.pivot(indexweekday, columnshour, valuesms).fillna(0)这个pivot是一个7行×24列的矩阵非常适合后面画热力图。值越大代表那个时间段听得越久。我实测的数据里周末的听歌高峰会在上午10点到下午2点之间工作日的峰值则在晚上8点以后。5.3 按月统计你的听歌总量变化曲线一年里听歌时长的波动能侧面反映你的生活节奏。考试月、加班月、心情低落月总有几个月的数字明显掉下去。played_df[month] played_df[ts].dt.to_period(M) monthly played_df.groupby(month)[ms].sum() monthly_hours monthly / 3600000 print(monthly_hours)如果你连续看了三年数据还能发现季节性的规律每年1月可能因为新年计划而出现歌单大换血11月因为年度总结而狂听老歌。这些都是可以聊的细节。6. 把分析结果画出来图表进阶6.1 用matplotlib画一个简单的歌曲Top10代码不长最容易被忽视的是字体设置。如果你直接画中文大概率会出现方框乱码。最好先用matplotlib指定支持中文的字体。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Microsoft YaHei] # Windows plt.rcParams[axes.unicode_minus] False top10 top_tracks.head(10).iloc[::-1] # 倒序让第一名在上面 fig, ax plt.subplots(figsize(10, 8)) ax.barh(top10[track], top10[total_hours], color#1DB954) ax.set_xlabel(收听时长小时) ax.set_title(我的年度播放时长 Top10) plt.tight_layout() plt.savefig(top10.png, dpi200)用barh比bar更适合歌手名和歌名很长的情况横向条形图不容易被裁剪。6.2 热力图星期 × 小时的收听强度热力图非常适合展示“一周里的音乐生物钟”。用imshow实现也不复杂甚至不需要额外引入seaborn。import numpy as np data pivot.values # 7行24列 fig, ax plt.subplots(figsize(12, 4)) im ax.imshow(data, aspectauto, cmapviridis) ax.set_xticks(range(24)) ax.set_yticks(range(7)) ax.set_yticklabels([周一, 周二, 周三, 周四, 周五, 周六, 周日]) ax.set_xlabel(小时) fig.colorbar(im, label收听毫秒数) plt.savefig(weekly_heatmap.png, dpi200)这种图一出来整个分析报告的高级感立刻上来了。它比几十行数字更直观。6.3 词云歌手名组成的图如果想发朋友圈词云是最容易出效果的。用wordcloud库from wordcloud import WordCloud artist_string .join( played_df[artist].value_counts().index[:100] ) wc WordCloud( width1200, height600, background_colorwhite, font_path/System/Library/Fonts/PingFang.ttc, # 按你的系统改 ).generate(artist_string) wc.to_file(artist_cloud.png)这里有个坑WordCloud默认按空格分词如果你只传歌手名列表中文会被拆成单个字。要保留歌手名语义可以用括号把每个歌手名包起来或者干脆只处理英文歌手名。我个人的做法是英文部分不做处理中文部分用font_path指定中文字体并把每个歌手名里的空格去掉这样字体渲染才正确。7. 常见问题与排查技巧实录7.1 时间戳报错Unknown string format不同导出文件的格式可能不完全一样有的ts末尾带Z有的带00:00。用pd.to_datetime解析时个别值会报错。最简单的处理是用errorscoercedf[ts] pd.to_datetime(df[ts], errorscoerce) df df.dropna(subset[ts])但丢掉的记录会损失数据建议报错后再检查一下具体是哪几行也许是某条记录本身缺少字段。7.2 为什么我的时长全部少了8小时这就是时区问题。数据里的时间是UTC你没有转换成Asia/Shanghai又直接取了dt.hour。看起来是“晚8点听歌”变成了“中午12点听歌”。如果只按天统计没察觉按小时统计时全部错位。解决办法就是前面说的tz_convert。还有一个容易忽视的点如果你的数据列是object类型而不是datetime64[tz]用dt.tz_convert会报错。先pd.to_datetime再转换时区。7.3 导出文件拆了30个读取内存爆了如果你有多年历史分段文件会特别多全部读进列表再pd.concat确实费内存。遇到这种情况可以只读取需要的列records [] for f in files: with open(f, r, encodingutf-8) as fp: data json.load(fp) for row in data: records.append(( row.get(ts), row.get(ms_played), row.get(master_metadata_track_name), row.get(master_metadata_album_artist_name), )) df pd.DataFrame(records, columns[ts, ms, track, artist])也可以边读边用pd.read_json分块处理chunks [] for f in files: df_temp pd.read_json(f) chunks.append(df_temp[[ts, ms_played, master_metadata_track_name]]) df pd.concat(chunks, ignore_indexTrue)7.4 接口返回的歌曲ID是加密字符串怎么办接口返回里通常会有一个“资源ID”形如spotify:track:xxx。如果要做歌曲去重用这个ID比用歌名更可靠因为同名翻唱非常多。但导出数据里这个字段可能被截断为track URI两种格式要统一。分析时把URI中的前缀去掉用中间那段ID作为主键df[track_id] df[uri].str.replace(.*?:, , regexTrue)7.5 为什么有些歌曲名显示为None导出数据里可能混入一些“本地文件”或失效曲目它们没有元数据所以track、artist都是空值。这些记录对分析没意义直接过滤df df.dropna(subset[track, artist])7.6 我想看播客数据怎么办如果导出包里包含播客播放记录那些记录里的track_name可能是播客节目名或单集标题。你可以在分析时加一个episode标记列然后单独跑播客统计避免和音乐混在一起。我自己的方法是用URI前缀判断spotify:episode:开头的是播客spotify:track:开头的是音乐。这样最干净。8. 从脚本到产品把分析做成可重复的流程8.1 整理成一个analyze.py当你跑通一次分析后不要满足于在Notebook里反复运行。把代码整理成一个Python脚本入口接收参数数据目录、输出目录、年份、时区。这样以后每年拿到新的导出包跑一行命令就能出图。脚本结构大概是def load_histories(data_dir): ... def clean_history(df, tz): ... def calculate_top_artists(df): ... def calculate_top_tracks(df): ... def plot_heatmap(df): ... def plot_top_tracks(df): ... if __name__ __main__: # 主流程写函数时每个函数的输入输出尽量是DataFrame这样后续想增加新分析项只要加一个函数就行不需要改主流程。8.2 加一个“年度报告”模式我第二次跑这个分析时直接把脚本升级成了“个人年度音乐报告生成器”。它会在指定的年份范围内筛选数据自动生成年度歌手Top10年度歌曲Top10月份收听时长折线图星期×小时热力图平均每天听歌时长“深夜电台”Top10凌晨0点到3点播最多的歌这些内容全部输出到一个以时间戳命名的文件夹里再手动挑几张发朋友圈。8.3 还能怎么扩展分析完这轮方向其实很多把同一首歌的不同版本专辑版、现场版合并计算看单曲真实热度。计算“新歌发现率”某年内第一次播放的歌手占比判断自己是否越来越守旧。把播放时间和地理位置如果导出数据里有国家字段结合起来看旅行时听的歌和平时有什么不同。用历史数据训练一个简单的推荐模型虽然不能和平台官方推荐比但至少知道自己的口味重心。如果只是个人娱乐做前两项就足够了想写博客或做作品集后两项能撑起一篇完整的技术文章。一点实操心法这套流程我自己跑过很多轮最想提醒你的一点是不要追求“一步到位的完美脚本”而是先跑出一个最简单的Top5再慢慢加功能。第一次拿到的原始数据可能比想象中脏时区、空值、分段文件每个问题都值得单独处理。也别想着把所有分析都塞进一个脚本先做单次分析等确认结果可靠后再封成函数和类。有人会担心隐私觉得把个人听歌记录拿去做分析会发生什么。实际上整个过程完全在本地执行不调任何在线服务模型也好、图表也好最终产物就是几张图表和一个脚本。只要你不上传到公开仓库这些数据不会离开你的电脑。这个分寸自己守住就好。最后再分享一个实用小技巧导出包里如果包含SearchQueries.json可以顺便看一眼自己的搜索记录。很多时候你主动搜过的歌反而比被动听到的歌更能反映真实偏好。把搜索数据和播放数据一对比也许会发现一个有趣的事实——你的白天人格喜欢激烈节奏深夜账号却总在搜舒缓纯音乐。这种反差就是做个人数据分析最迷人的地方。
RELATED

相关推荐

电子数据取证教学套件实战:E01镜像分析与NTFS $MFT定位

电子数据取证教学套件实战:E01镜像分析与NTFS $MFT定位

简介:本资源是华中科技大学网络空间安全学院电子取证课程的实践教学配套材料,面向网络安全专业本科生及电子取证初学者,聚焦真实案件场景下的数据固定、镜像分析、隐写检测与证据链构建等核心能力训练。压缩包共743个文件,以554个…

📅 2026/10/13 4:12:14
MySQL用户名怎么看?一文讲清mysql.user表与User@Host

MySQL用户名怎么看?一文讲清mysql.user表与User@Host

开篇:一条SQL值不了几个钱,搞不清“用户名”才是要命的有次在给一个做电商的朋友排查线上数据库连接错误,他一脸笃定地告诉我:“用户名就是数据库的名字吧?我建库的时候就叫shop_db,那用户应该也叫shop_db。…

📅 2026/10/13 4:12:14
华中科技大学电子取证教学靶场:结构化实训与司法级验证

华中科技大学电子取证教学靶场:结构化实训与司法级验证

简介:本资源是华中科技大学网络空间安全学院电子取证课程的实践教学配套材料,面向网络安全专业本科生及电子取证初学者,聚焦真实案件场景下的数据固定、镜像分析与证据链构建等核心能力训练。压缩包共743个文件,含554个HTML页面&a…

📅 2026/10/13 4:12:14
MORE NEWS

更多资讯

📰

Qt Core 模块深度解析:元对象系统、事件循环与多线程实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

FMEA失效模式分析:原理、应用与行业实践

1. 失效模式与影响分析(FMEA)概述失效模式与影响分析(Failure Mode and Effects Analysis,简称FMEA)是一种系统化的可靠性分析方法,最早由美国军方在20世纪40年代开发用于武器装备的质量控制。如今它已成为…

📰

国内环境快速部署Dify:镜像拉取与docker-compose改造指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

WSL下STM32烧录难题:ST-Link USB直通与OpenOCD实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

低功耗气体传感模组物联网设计:从选型到功耗优化的实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

CH379M双通道读卡器方案:SD/TF卡并行读写与国产化迁移实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬