
简介面向新闻资讯类应用开发与数据挖掘学习者一套完整的Python网络爬虫与推荐算法项目资源实现了新浪新闻数据采集与个性化推荐。爬虫部分可抓取新闻标题、正文、图片、视频链接并保留页面排版推荐部分集成权重衰减、标签推荐、区域推荐与热点推荐四种策略适合作为毕业设计或课程设计参考。资源包共含2000个文件约144.59MB其中1607个Py文件为爬虫与算法核心131个HTML、115个JS与20个Vue文件构成前端展示界面38个C和27个H文件提供底层扩展支持另有多种配置文件辅助运行。目前已有184人学习对熟悉爬虫工作流程、反爬应对策略以及推荐算法实际落地均有直接帮助也可为新闻聚合类项目提供可复用的完整源码。1. Python网络爬虫与推荐算法新闻推荐平台从爬取到推送的完整落地做新闻类应用最头疼的往往不是功能开发而是内容从哪来。手动搬运效率低版权上也不稳妥直接买数据源又贵中小团队很难承受。这个名为“Python网络爬虫与推荐算法新闻推荐平台”的项目解决的正是“数据采集 内容分发”这两件事。它用Python爬取新浪新闻的标题、正文、图片和视频链接保留原始排版再通过权重衰减、标签匹配、区域偏好和热点追踪四路推荐逻辑把新闻推给对应的用户。如果你正在做课程设计、毕设或者想快速搭一个带推荐能力的新闻内容站这份资源里的抓取思路和推荐策略可以直接复用到自己的项目里。先说结论这包代码的实用程度高但也不是解压就能跑。爬虫部分依赖新浪页面的DOM结构推荐部分依赖你手上有用户行为数据。接下来我会按“爬虫原理与实现 → 推荐算法拆解 → 项目串联 → 常见坑 → 效果验证”的顺序把这份资源从头到尾拆开讲清楚。2. 爬虫模块用Requests和BeautifulSoup拆解新浪新闻页面2.1 爬虫工作流里最容易被忽视的一环URL队列管理爬虫的基础流程不算复杂收集URL、请求页面、解析内容、存储数据。真正拉开差距的是URL队列怎么维护。很多新手写爬虫上来就是一个循环遍历URL列表抓完就结束了。但这个项目要抓的是新浪新闻整站URL数量不是几十个而是几千上万个这时候队列的先进先出、去重、失败重试就变得非常关键。# 用一个简单的FIFO队列 set去重来管理待抓取URL from collections import deque import hashlib class URLQueue: def __init__(self): self.queue deque() self.seen set() def push(self, url): # 用md5做去重指纹比直接存URL省内存 fp hashlib.md5(url.encode(utf-8)).hexdigest() if fp not in self.seen: self.seen.add(fp) self.queue.append(url) def pop(self): return self.queue.popleft() if self.queue else None def __len__(self): return len(self.queue)这里用hashlib生成URL指纹去重是我比较推荐的做法。URL本身可能很长几万条存内存里不仅慢还占空间MD5定长32位就稳定多了。deque的popleft()是O(1)操作比列表的pop(0)快一个数量级。2.2 请求与解析Requests BeautifulSoup如何保留新闻排版新浪新闻的正文排版比较规矩标题在h1正文在div#article图片在img标签里。这个项目的亮点是连视频链接也一并提取并且把图片的src替换成绝对地址这样前端展示时图片不会裂。import requests from bs4 import BeautifulSoup def parse_news_page(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 } resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) title soup.find(h1).get_text(stripTrue) content_div soup.find(div, idarticle) # 将所有图片的相对路径补全为绝对路径 for img in content_div.find_all(img): src img.get(src) if src and src.startswith(//): img[src] https: src paragraphs [p.get_text(stripTrue) for p in content_div.find_all(p)] video_links [v.get(src) for v in content_div.find_all(video)] return { title: title, content: \n.join(paragraphs), html: str(content_div), # 保留原始排版 videos: video_links, source_url: url }这段代码里有几个值得注意的细节。resp.encoding utf-8是必须的新浪页面偶发检测编码不准的情况不手动指定会出现中文乱码。get_text(stripTrue)会去掉段落首尾空白但不会去掉段落内部的换行所以正文拼接用\n.join()能保留段落结构。html字段直接存了content_div的HTML字符串这一点很聪明——后续展示时直接插入前端即可排版和图片位置完全保留。2.3 反爬应对设置合理的抓取频率是长期运行的前提新浪新闻这类门户网站的反爬强度不算高不像电商平台那样有复杂的验证码和滑块但没有防护也不现实。实践中会遇到两种情况请求频率过高时返回403或者返回一个验证页面。最有效的对策就是限制请求间隔以及轮换User-Agent。import time import random def safe_request(url, max_retries3): ua_list [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/119.0.0.0, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/604.1, Mozilla/5.0 (X11; Linux x86_64) Firefox/115.0 ] for attempt in range(max_retries): try: headers {User-Agent: random.choice(ua_list)} resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: return resp elif resp.status_code 403: time.sleep(random.uniform(3, 6)) # 被封后等久一点 except requests.exceptions.RequestException as e: time.sleep(2) return None代码里random.uniform(3, 6)的时间窗既能起到限速作用又不会让抓取变得太慢。如果你要跑大批量我一般习惯在两次请求之间加0.5到1秒的随机延时再加上这里的重试机制基本可以长时间稳定运行。3. 推荐算法权重衰减、标签匹配、区域与热点的组合策略3.1 权重衰减为什么用户读过的新闻不能反复推荐推荐系统里有一个基础问题热门内容天然容易获得更多曝光但一直推热门会让用户厌倦。这个项目引入的权重衰减策略解决了这个问题。核心思路是每个新闻条目都有一个初始权重用户交互点击、阅读、收藏会改变权重随着时间推移权重会逐渐降低。def weight_decay(initial_weight, age_hours, decay_rate0.05): 权重衰减计算 :param initial_weight: 新闻初始权重 :param age_hours: 新闻发布至今的小时数 :param decay_rate: 每小时的衰减率 return initial_weight * (1 - decay_rate) ** age_hours # 示例一条初始权重10的新闻发布24小时后 print(weight_decay(10, 24, 0.05)) # 输出约 2.9224小时后权重从10掉到2.9248小时后低于1基本就不会出现在推荐列表里了。这个指数衰减的设计很实用既保证了新内容能出头又不会让旧内容突然消失——它们只是缓慢沉底。需要调的参数是decay_rate0.03适合新闻更新慢的场景0.08适合秒级更新的热点追趋。3.2 标签推荐基于内容的匹配如何绕过冷启动标签推荐本质上是一种基于内容的推荐Content-based Filtering。先把新闻打标签再把用户的历史阅读标签聚合起来算相似度。这里的关键在于标签权重的计算用到的是TF-IDF的思路。from collections import Counter import math def compute_tag_score(user_tags, news_tags): 用户标签与新闻标签的匹配得分 user_tags: {科技: 5, 体育: 2, 财经: 1} news_tags: {科技: 3, 娱乐: 1} score 0.0 for tag, weight in news_tags.items(): if tag in user_tags: score user_tags[tag] * weight return score用户看科技类新闻越多这个标签的权重就越高之后来一条科技新闻就能得到更高的匹配分。这种方式实现简单而且没有协同过滤的冷启动问题——新用户只要点过一条新闻第二天就有推荐结果。3.3 区域推荐和热点推荐组合策略的权重配比区域推荐用的是IP解析或用户手动选择的地域信息把本地新闻排在前面。热点推荐则是统计全站最近一小时的阅读量、评论数、分享数做归一化后乘以一个时间衰减系数。实际项目中我的推荐得分公式一般是final_score 0.4 * label_score 0.3 * hot_score 0.2 * region_score 0.1 * freshness_score权重配比可以调但有个原则标签匹配是长期兴趣热点是短期刺激区域是补充新鲜度是底线。如果你把热点权重调得太高推荐列表容易被爆款霸占用户会流失标签权重太高则容易形成信息茧房。这个项目默认的配比是比较均衡的。4. 从爬取到推荐新闻平台的数据流与模块串联4.1 数据存储结构设计MySQL与JSON如何各司其职爬虫抓取的数据和推荐算法使用的数据结构不一样。抓下来的新闻是明确的、结构化的适合存MySQL用户的实时行为日志是流式的、字段不固定的适合先落地成JSON再导入。建表是我拿到项目后第一步就会调整的内容。默认表结构里 news 表存新闻user 表存用户behavior 表存行为日志recommend_result 表存推荐结果快照。这里的created_at和expire_at一定要加索引推荐算法的SQL查询会频繁按时间过滤。CREATE TABLE news ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, content TEXT, html_content MEDIUMTEXT, image_urls TEXT, video_urls TEXT, tags VARCHAR(255), region VARCHAR(50), source_url VARCHAR(500), weight FLOAT DEFAULT 10.0, created_at DATETIME, expire_at DATETIME, INDEX idx_created (created_at), INDEX idx_region_tags (region, tags) );4.2 定时调度用APScheduler串联爬虫和推荐任务爬虫和推荐不需要同时运行。推荐算法依赖用户行为数据而行为数据是在爬虫抓取新闻、用户阅读之后才产生的。所以我的调度策略是爬虫每小时跑一次推荐算法每15分钟跑一次——这样用户每次刷新页面时数据滞后不超过15分钟。from apscheduler.schedulers.blocking import BlockingScheduler def crawl_job(): print(开始爬取新浪新闻...) # 爬取首页和分类页解析新闻详情入库 def recommend_job(): print(重新计算推荐列表...) # 读取news表和behavior表计算final_score写入recommend_result if __name__ __main__: scheduler BlockingScheduler() scheduler.add_job(crawl_job, interval, hours1) scheduler.add_job(recommend_job, interval, minutes15) scheduler.start()4.3 前端展示与推荐接口串起来推荐结果最终通过API返回给前端。接口不需要太复杂一个/api/recommend?user_idxxx的GET请求就能搞定。这个项目里的后端是Flask写的非常轻量。from flask import Flask, jsonify, request app Flask(__name__) app.route(/api/recommend, methods[GET]) def recommend(): user_id request.args.get(user_id) result get_recommend_list(user_id, limit20) return jsonify({code: 0, data: result}) def get_recommend_list(user_id, limit): # 伪代码从recommend_result表里按得分倒序取 # 同时过滤掉用户已经读过的新闻ID return [...]5. 避坑指南爬虫和推荐算法最常见的炸雷点5.1 新浪页面结构变更导致爬虫突然失效现象某天开始爬虫能正常返回HTML倒是解析出来的标题和正文全部为空。原因新浪改版了前端模板div#article的id被改成了div.article-content或者正文被包裹在别的容器里。这种情况在门户网站尤其常见平均每半年就会改一次。解决不要硬编码选择器把解析规则抽成配置项。每次启动爬虫前先做一个冒烟测试——抓一个已知URL验证解析结果非空失败了就发告警。我在这个项目里加了一个check_parser()函数每次任务开始前自动验证。5.2 图片地址是//开头导致前端无法显示现象网页里图片全部裂开浏览器控制台报Failed to load resource查看元素发现图片地址是//n.sinaimg.cn/xxx.jpg。原因页面源码里图片用了协议相对地址Protocol-relative URL如果HTML是在本地HTML文件里打开的浏览器无法确定用http还是https。解决在爬虫阶段就统一替换成绝对路径https:前缀也就是前面parse函数里做的那件事。别指望前端再处理一次越早做越省事。5.3 推荐列表全是热点新闻用户标签完全没起作用现象不管用户平时喜欢看什么推荐结果前十条全是同一批高热度新闻。原因热点推荐的分值是通过阅读量计算的数值天然很大几千几万标签匹配的分值通常只有个位数。没有做归一化就直接加权求和热点把其他因子的贡献淹没了。解决对热度、标签、区域三个因子都做Min-Max归一化把分值压到0到1之间再加权。极端情况下如果热度的量级实在太大用log1p()先压缩分布也行。5.4 爬虫被服务器断连重试反而加剧封禁现象加了重试机制后被封IP的时间反而变长了。原因重试逻辑写得过于激进——收到403后立刻重试连续重试三次对服务器来说等于同一IP连续发起四次异常请求。解决403之后不要立即重试先sleep 30秒以上并且降低该域名下的抓取频率。正确的重试策略是指数退避第一次失败等1秒第二次2秒第三次4秒最多重试三次就放弃。5.5 权重衰减参数设得太狠推荐列表“一日游”现象上午发布的新闻到了下午就从推荐位消失了用户反馈看不到中午读过的新闻续集。原因decay_rate设了0.224小时衰减到0.1以下任何新闻都活不过一天。正常情况下新闻的生命周期应该在48到72小时。解决把decay_rate回调到0.03-0.05之间。让一条初始权重10的新闻在24小时后仍有2.9以上48小时后仍能进入候选池。6. 推荐效果验证与参数调优离线评估和A/B测试怎么做推荐系统上线后最怕的就是“看起来在推荐实际没有效果”。怎么验证推荐质量我在这份资源上做过两类验证方法一类是离线评估一类是在线A/B测试都不复杂。离线评估最简单的方式是准确率K。实现逻辑是取用户历史行为日志的最后一条新闻作为测试集用在这条新闻之前的行为数据训练推荐模型然后计算推荐列表里是否包含这条新闻。如果推荐列表长度是20命中就算1没命中算0统计所有用户平均命中率。这个指标不需要多少数据量100个用户就能看出趋势。def precision_at_k(recommend_list, actual_items): K默认为20命中数除以K hits len(set(recommend_list) set(actual_items)) return hits / 20 if len(recommend_list) 20 else hits / len(recommend_list)更进一步的验证办法是多样性检查。如果推荐列表里80%的新闻都是同一个标签那说明标签权重配比失衡了。直观的做法是统计每条新闻的标签分布算出Shannon熵熵值越低说明推荐列表越单一。我一般建议熵值至少大于1.5低于这个值用户很快会产生审美疲劳。在线A/B测试的做法更直观但需要前端配合把用户随机分为A组和B组A组用新推荐参数0.40.30.20.1B组用旧参数跑一周后对比CTR点击率和平均阅读时长。这里有个容易被忽略的细节——实验分组要基于用户ID哈希而不是登录先后顺序不然新老用户差异会污染结果。我最后想分享一个习惯每次调参前先把当前版本的全部参数快照保存下来。推荐算法的参数调试和爬虫不一样爬虫改了选择器立竿见影推荐参数调整后通常要两三天才看得出效果。如果没有参数快照两周后你可能根本记不清当前线上跑的是哪组参数。从那以后我每次调模型都会强制走一遍“参数记录 → 修改配置 → 部署 → 观察指标”的流程并且把每次实验的指标变化写进注释里。这个项目从爬虫到推荐链路完整拿来改造成自己的新闻产品底子是完全够用的。希望帮到你。本文还有配套的精品资源点击获取