首页/新闻资讯/正文详情

从CSDN热榜抓取到技术趋势分析:Python爬虫雷达系统实战

发布时间:2026/9/25 6:25:01 来源:云帆数科 栏目:资讯中心
从CSDN热榜抓取到技术趋势分析:Python爬虫雷达系统实战
CSDN 的热榜每天刷一遍十个标题里有八个换新面孔剩下的两个也变了时间戳。嘴上说着技术圈日新月异心里其实一直存个疑问这些榜单数据背后到底哪些技术方向是真热哪些只是昙花一现与其天天手动翻榜做记录不如把它做成一个自动化工具顺手再完成一个技术趋势分析雷达——这也就是今天这篇文章的主角。先说清楚这个项目能干什么用 Python 定时抓取 CSDN 平台的文章、ReadMore 热榜和分类页数据把标题、作者、发布时间、阅读量、收藏量这些字段存进本地数据库再通过关键词提取和热度评估生成多维度趋势报告。说白了就是从被动刷榜变成主动监测哪些技术关键词最近涨得猛哪位作者的产出持续稳定哪个方向的热度在消退跑完脚本一目了然。适合想练手爬虫全流程、对数据分析感兴趣或者单纯想用数据辅助选题定位的开发者。废话不多说直接上完整方案。1. 项目概述与整体设计思路1.1 为什么叫雷达而不是爬虫脚本很多人看到爬虫两个字第一反应就是能把网站数据都扒下来其实爬虫只解决了采集问题真正的难点在于后续的数据组织和信息提取。之所以把这个项目叫作雷达是因为它的核心并不是采集本身而是监测维度广度覆盖多个频道页、多个时间段保证数据面的完整定向针对指定技术关键词做热度追踪相当于雷达的波束聚焦跟踪连续多天运行同一关键词的趋势曲线、增幅、热度排名全都留存。从功能形态上看它更像一套持续运行的监测系统脚本只是最外层的壳。因此在设计之初就要把数据模型、采集流程、分析逻辑分开来写避免把所有代码压在一个文件里后期改一个字段就要动全局。2. 核心细节解析与实操要点2.1 目标站点分析CSDN 页面结构拆解CSDN 的首页热榜和数据流虽然有部分接口是异步加载的但文章列表页比如博客榜阅读榜收藏榜的结构相对稳定是新手学习爬虫的理想目标。直接打开列表页右键查看页面源码你会发现每篇文章都用.article-item这样的容器包裹标题在h4 a里作者在.nickname或类似 class 里时间戳通常是 Unix 格式阅读数、收藏数散落在右侧的指标列里。一个非常容易踩的坑CSDN 的页面上经常混有专栏付费课广告推荐这类非普通文章条目它们的结构略有差异比如没有作者昵称或者阅读数显示为购买人数。因此解析时不能粗暴地取第一个匹配结果而是先判断父节点有没有广告类标识再做字段提取。另一个细节是时间字段。有的列表页只显示昨天3天前这种相对时间如果直接存库后续按天数聚合趋势时会乱套。我的做法是优先取文章详情页的发布时间接口拿不到就根据相对时间倒推日期并标记为estimated。反正数据是给自己用的明来源总比瞎猜强。2.2 技术选型为什么不用 Scrapy 而用 requests BeautifulSoup说实话单独看抓取效率Scrapy 的并发和调度能力确实强但这类中小型监测项目用 Scrapy 反而有点杀鸡用牛刀——你得维护 spider、middleware、pipeline 那一整套体系配置成本不低出了问题排查链路还长。我最终选的是requests BeautifulSoup就三个理由够轻脚本短小启动快几十行核心逻辑就能跑通全流程好调试哪儿解析出问题直接 print 一下异常段落的 HTML 就能定位接近本质绕过框架能真正理解 HTTP 请求、响应解析、会话维持这一整套底层机制对以后转 Scrapy 或者写异步爬虫都是打底子的过程。动态渲染的内容比如某些排行榜的异步刷新确实拿不到这也没关系——雷达监测的是趋势稳定获取的静态数据比偶发的动态字段更重要。后文我会讲用 Playwright 这类工具补盲区的方案但那属于扩展内容主流程不依赖它。2.3 存储方案SQLAlchemy 入库设计项目的数据是多次运行累积的这就不能用一次抓取一次写入的临时方案敷衍过去。我用 SQLAlchemy 作为 ORM定义了四张核心表articles文章基础信息标题、作者、链接、发布时间、所属频道article_stats单次采集时的阅读数、收藏数、评论数与文章 ID 关联保留历史keyword_mentions每篇文章经过分词后命中关键词的明细带时间戳trend_log每天聚合出的关键词热度、排名变化、增幅供展示层调用表字段设计上有个核心原则统计表和基础信息表分离。也就是每次采集只往article_stats里插入一条新记录而不是去覆盖articles里的数值。这样一天跑三次一周下来就有 21 条统计数据拉趋势图时只需要按date聚合不用来回翻覆写痕迹。3. 实操过程与核心环节实现3.1 环境准备与依赖安装基础环境是 Python 3.9装上这几件套pip install requests beautifulsoup4 lxml sqlalchemy pandas schedule pyecharts这里补充说明各个库的具体用途requests负责发起 HTTP 请求拿页面 HTMLbeautifulsoup4 lxml解析 HTML提取目标字段sqlalchemy定义数据模型、连接 SQLite也可以用 MySQL但本地趋势分析 SQLite 足够pandas做数据清洗、聚合、透视表schedule定时运行采集任务pyecharts生成交互式趋势图表支持导出网页版报告。不用刻意追求一条命令装最新版关键是要固定一套已经跑通的版本组合避免哪天更新破坏了 API 兼容性。3.2 采集模块的完整代码先看请求部分的代码我习惯把请求头、会话、重试机制统一封装import requests from fake_useragent import UserAgent from time import sleep import random class CSDNFetcher: def __init__(self): self.session requests.Session() self.ua UserAgent() self.base_headers { Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, Upgrade-Insecure-Requests: 1, } def get_page(self, url): headers self.base_headers.copy() headers[User-Agent] self.ua.random # 随机小延时避免高频请求被限流 sleep(random.uniform(0.5, 1.5)) try: resp self.session.get(url, headersheaders, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding return resp.text except requests.exceptions.RequestException as e: print(f[请求失败] {url} - {e}) return None两个经验要点一是resp.encoding resp.apparent_encoding必须显式设置否则中文标题偶尔会变成乱码二是raise_for_status()配合 try/except 能在对方返回 4xx/5xx 时及时兜底不会让异常穿透到解析层。3.3 解析逻辑与字段提取解析部分用 BeautifulSoup 比较顺手。以最新文章频道为例核心结构是这样的from bs4 import BeautifulSoup def parse_article_list(html): soup BeautifulSoup(html, lxml) articles [] for item in soup.select(div.article-item): # 跳过广告位结构上经常没有作者字段 author_tag item.select_one(.nickname) if not author_tag: continue title_tag item.select_one(h4 a, .title a) if not title_tag: continue url title_tag.get(href, ).strip() if not url.startswith(http): url https://blog.csdn.net url articles.append({ title: title_tag.get_text(stripTrue), author: author_tag.get_text(stripTrue), url: url, publish_time: parse_relative_time(item.select_one(.time, .date)), read_num: extract_number(item.select_one(.read-num)), collect_num: extract_number(item.select_one(.collect-num)), }) return articles有个小技巧get_text(stripTrue)会把首尾空白都清理干净比text.strip()更省事。parse_relative_time和extract_number这两个函数非常关键具体实现如下import re from datetime import datetime, timedelta def parse_relative_time(node): if not node: return None text node.get_text(stripTrue) if re.search(r\d{4}-\d{2}-\d{2}, text): return datetime.strptime(text, %Y-%m-%d) if re.search(r昨天, text): return datetime.now().date() - timedelta(days1) if 分钟前 in text: minutes int(re.search(r\d, text).group()) return datetime.now() - timedelta(minutesminutes) # 更多规则自行补充…… return datetime.now().date() def extract_number(node): if not node: return 0 text node.get_text(stripTrue) match re.search(r(\d(\.\d)?)\s*([万千]?), text) if not match: return 0 num float(match.group(1)) unit match.group(3) if unit 万: num * 10000 elif unit 千: num * 1000 return int(num)CSDN 的数字经常显示成1.2万35672.1千这类格式直接int()会报错。统一用extract_number转成整数再入库后面排序和聚合就非常舒服。3.4 SQLAlchemy 数据模型与入库流程接下来是建表。SQLAlchemy 的模型定义如下from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, Float from sqlalchemy.orm import declarative_base, sessionmaker from datetime import datetime Base declarative_base() engine create_engine(sqlite:///trend_radar.db, echoFalse) SessionLocal sessionmaker(bindengine) class Article(Base): __tablename__ articles id Column(Integer, primary_keyTrue) title Column(String(500), nullableFalse) author Column(String(100)) url Column(String(500), uniqueTrue) publish_time Column(DateTime) channel Column(String(50)) created_at Column(DateTime, defaultdatetime.now) class ArticleStats(Base): __tablename__ article_stats id Column(Integer, primary_keyTrue) article_id Column(Integer, indexTrue) read_num Column(Integer) collect_num Column(Integer) comment_num Column(Integer) collect_time Column(DateTime, defaultdatetime.now) Base.metadata.create_all(engine)写入时要注意批量事务。推荐先用session.add_all()收集一批再统一commit()不要抓一条写一条那样不仅慢SQLite 还容易锁库。对于重复抓取的文章靠url唯一约束去重先查后插SELECT存在就跳过。3.5 文本处理与关键词热度统计采集不是目的分析才是。接下来要把文章标题和摘要做中文分词统计目标关键词的命中频率。我用的分词工具是jieba关键词表可以手动维护例如[人工智能, 大模型, Spring Boot, Kubernetes, 数据可视化]。逻辑非常简单import jieba keywords [人工智能, 大模型, Spring Boot, Kubernetes, 数据可视化, Python] def extract_keyword_hits(title): if not title: return [] words jieba.lcut(title.lower()) hits [] for kw in keywords: if kw.lower() in .join(words) or kw.lower() in title.lower(): hits.append(kw) return hits这一步抓的是标题级别的命中——因为标题本身信息密度很高通常一个词能上标题就代表它是这篇文章的绝对主题。当然也可以扩展到摘要和正文我会在后面进阶优化里细说。3.6 趋势判定与可视化有了历史统计数据关键的判定逻辑就是这个关键词是否正在上升。我用三个维度绝对热度当日命中该关键词的文章数周同比当前 7 天命中数对比前 7 天命中数的变化率排名波动该关键词在关键词榜单里的排名变化上升/下降/持平。汇总出一张trend_log表后可视化交给 pyechartsfrom pyecharts.charts import Line, Radar from pyecharts import options as opts def generate_radar_chart(data): radar Radar() radar.add_schema( schema[ opts.RadarIndicatorItem(name热度, max_100), opts.RadarIndicatorItem(name增量, max_100), opts.RadarIndicatorItem(name排名, max_100), opts.RadarIndicatorItem(name作者活跃, max_100), opts.RadarIndicatorItem(name新颖度, max_100), ] ) radar.add(技术趋势雷达, data) radar.render(trend_radar.html)这里把雷达的比喻落到实际产物上了。五个维度分别评价关键词的综合表现既有绝对热度也有增量和排名颜值和实用性都在线。3.7 定时任务调度采集任务不能手动跑一次就完事。我用schedule库做了定时调度每 6 小时采集一次全天数据。核心代码import schedule import time def job(): run_spider() update_trends() generate_reports() print(f[任务完成] {datetime.now().isoformat()}) schedule.every(6).hours.do(job) while True: schedule.run_pending() time.sleep(60)实际放服务器上跑时我更喜欢用系统 crontab 而不是 Python 内循环因为避免进程意外退出也顺手多了一层进程隔离。定时任务的字段对齐很简单0 */6 * * * cd /path/to/project python main.py logs/spider.log 21。4. 常见问题与排查技巧实录4.1 高频异常对照表跑了一段时间踩过的坑可以整理成一个速查表现象可能原因解决办法请求返回 403UA/频率被识别为爬虫换随机 UA增加延时检查是否触发验证码中文标题乱码响应编码判断错误显式设置resp.encoding resp.apparent_encoding解析结果为空白列表选择器失效或页面结构更新打印页面 HTML 片段重新检查class名入库重复记录同一链接多次抓取给 URL 加唯一约束先查后插阅读数显示为 0页面数字格式为1.2万使用extract_number统一转换相对时间无法解析昨天3天前等非标准格式统一在parse_relative_time里维护规则4.2 数据入库慢的排查思路如果你的采集量很大比如一次抓几千条会发现 SQLAlchemy 往 SQLite 写入越来越慢。这个往往是 SQLite 的锁问题。我的经验是开启 WAL 模式PRAGMA journal_modeWAL;批量写入比如每 500 条做一次commit()减少不必要的二次查询尽量只靠article_id做关联。还有一个小坑是session.query(Article).filter_by(url...).first()这种查重操作其实开销不小数据量大以后可以用 Python 内存 set 维护已有 URL先内存判断再查库速度能提升一个量级。4.3 动态加载内容的补充方案CSDN 个别榜单比如热榜的前几名有时候是 JS 动态渲染的Requests 直接拿不到。我实践下来有两个方案方案一分析 XHR 请求找到背后的 JSON 数据接口直接请求 JSON方案二上 Playwright 或 Selenium无头浏览器渲染后再解析代价是资源占用高、速度慢。趋势分析本身不要求每一条都拿到只要稳定性有保障缺失个别动态条目可以接受。所以我默认只用 Requests 拿静态列表动态内容作为补充源不进入核心统计流程。4.4 合规与频率控制心得爬虫合规是老生常谈但还是得再提一次。抓取前先看robots.txt约束自己的请求频率不要高并发打对方服务器数据仅限学习和内部使用不要重发布。我做这套雷达项目采集频率控制在 0.5~1.5 秒一个请求一次跑下来不到两分钟对目标站点的压力很小数据更新的及时性也完全够用。5. 实测结果与进阶优化方案5.1 真实运行数据展示我在本地连续跑了一周每天 6 小时调度一次采集数据大约 3000 条。用 pandas 做聚合以后几个有意思的观察是大模型关键词的命中量在周三达到峰值周末回落属于典型的工作日型内容周期Kubernetes的热度稳定但增量不高属于基础但长期稳定的方向某位写作数据可视化的作者在一周内三次进入热榜作者层面的稳定性在做细分选题时有参考价值。这一周数据告诉我们单看一天的热榜排名容易误判看 7 天聚合趋势才能看出真实方向。雷达图的五维可视化也正好把这些信息浓缩在一个界面里。5.2 进阶优化从标题到正文内容现在的关键词命中只基于标题覆盖面有限。进一步可以把摘要和正文前几段也纳入分析但代价是采集量大幅上升每篇文章多请求一个详情页频率不变的情况下采集时间会翻倍。折中做法是只对进入 Top N 的文章做详情页抓取和正文分析其他文章保留标题级别。这样既控制了请求量又提高关键词统计的准确性。5.3 进阶优化增量采集与断点恢复如果服务器中途挂了或者某次采集网络异常错过的时段会形成数据空洞。建议在article_stats表加一个collect_batch字段每次任务生成一个批次号跑完以后只要查哪个批次号缺失就知道哪段时间没采到方便补采。这一点我在早期没做后来补数据的时候吃了大亏重跑一轮把全表都刷新了历史规律被污染只能从建库日重新积累。5.4 进阶优化告警与主动推送雷达不应该只做报表还应该具备主动骚扰能力。我在系统里加了简单的告警规则当某个关键词的周同比增长超过 30%或者新面孔 keyword 首次进入 Top 20 时自动发邮件/推送到微信机器人。这个功能让监测真正变成预警不必每天翻图表。6. 实操过程中的个人心得最后聊聊我自己在反复调试过程中的几个体会吧。第一个体会是不要把爬虫写得像一次性脚本。最初我也是图省事把所有逻辑写在 Jupyter Notebook 里跑完就扔掉。后来真做趋势观察才发现真正的价值在持续运行三个月的数据积累而不是单次抓取的结果。数据表设计、批次管理、日志留痕这些看起来不酷的东西恰恰决定了项目能跑多远。第二个体会是频率克制比技术花活更重要。反爬从来不是一道墙而是你访问的节奏是否像个正常人。做过一次每秒并发 10 次的测试版半小时后被限流连正常页面都访问不了。从那以后我写任何爬虫都会先算跑一次需要多少请求、间隔多少秒在采集效率和可持续性之间找平衡点。第三个体会是趋势分析的准确性不在于抓得多全而在于比较口径一致。用同一个解析规则、同一套关键词表、同一套时间窗口去反复跑数据才有可比性。中途改进解析逻辑是很正常的事但改进前要把旧数据和新的批次隔开否则得出的趋势结论可能只是你代码变更的假趋势而不是真实的技术趋势。最后分享一个小技巧给每个采集批次生成一个 UUID 批次号连同运行时间、参数版本一起存到collect_meta表里。哪天分析结果异常了查一下批号对应版本迅速定位是否因为代码改动不用抓瞎。这套套路救过我很多次也推荐给你的爬虫项目。数据不会说谎但口径不一致的数据会误导人。做技术趋势雷达最重要的不是代码写得有多花哨而是保持采集、存储、分析全链条的一致性。把这套系统跑起来三个月后再回头看你对技术热点的判断会比单看热榜准确得多。

相关推荐

脉冲神经网络SNN入门:从LIF神经元到类脑芯片与低功耗计算
脉冲神经网络SNN入门:从LIF神经元到类脑芯片与低功耗计算

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

从零开始学硬件:用人体解剖学构建硬件系统知识地图
从零开始学硬件:用人体解剖学构建硬件系统知识地图

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

截图固定到屏幕怎么实现?贴图工具原理与Snipaste实操指南
截图固定到屏幕怎么实现?贴图工具原理与Snipaste实操指南

1. 截图固定这件事,比你想的更有讲究很多人第一次听到“把截图固定在电脑页面上”这个需求,脑子里冒出来的第一反应是——截图不就是截完保存成图片文件吗?还能固定在页面上?这听起来像是个小众需求,但只要你真正用过一… · 2026/9/25 6:24:48

电商API接口接入前准备清单:鉴权、沙箱与数据同步避坑指南
电商API接口接入前准备清单:鉴权、沙箱与数据同步避坑指南

先说点实在的。做电商系统的接口对接,很多人上来就打开文档写代码,结果三天两头被鉴权失败、字段对不上、回调地址不通这些问题卡住。我见过不少团队,明明天天都在跟订单、商品、库存打交道,真到要对接平台API的时候,反… · 2026/9/25 6:57:00

汇川PLC开发必知:Inoproshop指令库管理与Modbus TCP实战
汇川PLC开发必知:Inoproshop指令库管理与Modbus TCP实战

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

【企业智能体开发】将企业文档处理为可检索知识
【企业智能体开发】将企业文档处理为可检索知识

小林问 A301 投屏无画面,Agent 要查操作指引。企业网盘里却有三份看起来相似的资料:去年设备的说明、今年更新的会议室手册,以及一份尚未审批的草稿。如果只把所有文字丢进向量库,检索可能恰好找回旧手册,回答却显得非常自信。文档检索能否用于服务台,取决于资料进入索引… · 2026/9/25 6:57:00

Atlas 300V 24G部署YOLOv5s全流程:驱动、CANN到推理实践
Atlas 300V 24G部署YOLOv5s全流程:驱动、CANN到推理实践

如果你最近在关注AI推理硬件,肯定绕不开Atlas这个词。特别是“Atlas 300V 24G”这张卡,经常出现在视频分析、目标检测这类场景的配置单里。不少刚接触昇腾平台的朋友会问一句:Atlas 300V 24G是运算加速卡吗?答案是肯定的&#xff… · 2026/9/25 6:57:00

图解物理与环境安全:从门禁到介质,筑牢信息安全底座
图解物理与环境安全:从门禁到介质,筑牢信息安全底座

1. 当网络安全失效时,最容易被低估的物理入口1.1 一次让我彻底改变认知的事故复盘先讲一件我早年间经历的事。当时一家企业每年花大几百万买防火墙、上态势感知、做攻防演练,网络安全水平在同行里算是头部。结果某天核心业务数据库被人拖走了一整份备份文… · 2026/9/25 6:56:54

CLI驱动的LLM Agent代码评审工作流:基于Git Diff的开源实践
CLI驱动的LLM Agent代码评审工作流:基于Git Diff的开源实践

1. 项目概述:这不是一个工具,而是一套可落地的开源代码评审工作流“open-code-review”这个名称乍看像某个具体软件包或GitHub仓库名,但结合当前开发者社区的真实语境——尤其是高频出现的open-code-review、CLI、LLM Agent、git diffs这组关… · 2026/9/25 6:56:54

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

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

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

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

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

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

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码