实验四 Python综合实践

20241207 2025-2026-2 《Python程序设计》实验四综合实践实验报告

——基于Playwright的剧本杀小红书舆情情感分析系统(被反爬制裁失败版)

——天气系统之要不要来一个说走就走的旅行~(正文在此)

说明:原系统经历了完整的开发过程,从技术选型、代码实现到多次调试优化,最终在接近完成时遭遇小红书反爬制裁,导致无法正常获取数据,不得不临时换题,希望老师手下留情T T。

课程:《Python程序设计》
班级:2412
姓名:陈琪雅
学号:20241207
实验教师:王志强
实验日期:2026年5月25日
必修/选修:公选课

失败版系统一设计过程(T T)

一、设计思路

1.1 设计背景与目的

近年来,剧本杀已经成为线下社交娱乐的重要方式之一。根据统计数据,全国剧本杀门店数量已突破数万家,玩家规模持续扩大。(真的很好玩吧)然而,市面上层出不穷的剧本杀本子常常良莠不齐,褒贬不一,不同角色的体验差距更是极大。玩家在亲身体验之前很难预想:文笔是否优秀?流程是否好玩?角色是否是超级小丑位?是否存在"坐牢感"体验?需不需要吃补贴上车?车头描述下的"好玩"是不是原价上车的骗术?(主要以情感本为例)

小红书作为当下年轻人分享生活的重要平台,汇聚了大量真实玩家的体验反馈,越来越多玩家或剧本测评选手倾向于在小红书上发表自己的体验、测评。在玩一个角色前,玩家常常选择打开小红书→搜索剧本名称→逐条翻阅几十甚至上百篇笔记→手动拼凑信息碎片,或发一条求建议的帖子,然后从评论区拼凑信息碎片。这个过程不仅耗时费力,而且主观性强,难以形成量化的评价结论。

作为剧本杀忠实爱好者,我决定开发一套剧本杀小红书舆情情感分析系统。该系统能够自动爬取指定剧本的搜索笔记和正文内容,统计剧本或角色的讨论热度,对文本进行情感倾向分析,并引入"坐牢程度"这一剧本杀玩家特别关注的评价维度,最终以可视化图表的形式向用户呈现综合评价结果。

免责声明:剧本杀黑话繁多,衡量"好玩"的标准和因素越来越复杂且具有特异性,模型和算法无法真正理解(情感本市场尤其混乱)。此外,玩家发帖时经常出现打错名字、不打全名等情况。考虑到运行成本、运算难度及反爬机制限制,系统选取的样本较少且代表性有限。总体来讲,本系统娱乐性大于准确性,结果仅供参考。不过设计思路可以迁移到搜索小红书上某件商品(化妆品等)的用户评价等场景,在挑选商品时提供实用地参考,只不过对于不同商品市场收集数据的衡量指标和情感分析算法需要进行针对性修改,工程量较大故未在本次实验中实践。

1.2 开发环境与技术栈

类别 选型 选型理由
编程语言 Python 3.11 语法简洁、生态丰富,适合爬虫与NLP开发
浏览器自动化 Playwright 1.40+ 模拟真实浏览器行为
情感分析 SnowNLP 轻量级中文情感分析库,零配置开箱即用,辅以领域词增强
GUI界面 Tkinter Python内置库,跨平台轻量级,无需额外安装
数据处理 Pandas 数据清洗与统计分析
开发工具 PyCharm 插件丰富,支持断点调试

1.3 完整功能清单

本项目共实现了以下6项核心功能:

  1. 模拟浏览器搜索爬取:输入剧本名称,自动爬取搜索结果中的笔记正文内容
  2. 基于SnowNLP的情感分析:对每条笔记进行情感极性判断(积极/中性/消极),通过领域词增强和阈值调整提升准确性
  3. "坐牢程度"量化评价:基于关键词权重累加规则,独创性地评估剧本的"坐牢风险"
  4. 综合评价与推荐等级:结合情感分与坐牢分,输出五星推荐建议
  5. 数据统计与可视化:以图表和统计数字形式直观展示分析结果
  6. GUI图形界面:提供友好的交互界面,无需命令行操作

二、系统模块与代码结构

本系统采用模块化设计,将爬虫、分析、界面展示分离,便于调试和维护。

2.1 系统架构图

┌─────────────────────────────────────────────────────┐
│                    GUI(Tkinter)                    
│                 用户输入:剧本/角色名称                  
└─────────────────────────┬───────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                  爬虫(Playwright)                 
│         ┌─────────────┐    ┌─────────────┐         
│          模拟浏览器     →     执行搜索                 
│         └─────────────┘    └─────────────┘         
│                ↓                   ↓                
│         ┌─────────────┐    ┌─────────────┐         
│           解析HTML      →     提取正文               
│         └─────────────┘    └─────────────┘        
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                  数据处理(Pandas)                  
│           ┌─────────────┐    ┌─────────────┐      
│              数据清洗      →     文本规整             
│           └─────────────┘    └─────────────┘       
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                 分析(双衡量指标)                    
│   ┌─────────────────┐      ┌─────────────────┐    
│      情感分析引擎               坐牢分析引擎           
│      SnowNLP                  关键词规则匹配          
│      方案四优化版               权重累加算法           
│   └─────────────────┘      └─────────────────┘     
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                    可视化与输出                       
└─────────────────────────────────────────────────────┘

2.2 各模块职责

模块 职责 关键技术和数据结构
主程序 用户交互界面、爬虫线程、结果展示
爬虫模块 模拟浏览器登录小红书、搜索关键词、提取笔记正文 Playwright同步API、链接提取法、Cookie管理
情感分析 对文本进行情感极性判断,含领域词增强 SnowNLP、领域积极词库、阈值调整
坐牢分析 基于关键词权重累加计算坐牢程度分数 词典映射、字符串匹配
统计模块 随机抽取正文、计算平均分、生成统计摘要 随机抽样、均值计算

三、核心模块实现

3.1 爬虫模块

3.1.1 技术选型与配置

初期我曾尝试使用xhs第三方库,但该库依赖复杂调用失败,最终改用Playwright模拟真实浏览器。Playwright能够执行JavaScript、渲染动态内容、维持登录态,绕过请求签名和加密。

运行前需要执行:

pip install playwright
playwright install chromium

3.1.2 Cookie配置与登录状态维持

小红书要求必须携带有效的登录凭证(Cookie),否则搜索结果为空。
手动从浏览器复制Cookie字符串:
进入小红书网址并登录: https://www.xiaohongshu.com/explore ,快捷键F12打开开发工具,选择网络,随机点开几个进程,寻找cookie字段并复制

# COOKIE_STR 在代码顶部配置
COOKIE_STR = "复制的cookie字段"

# 解析并设置Cookie
cookies = []
for pair in cookie_str.split('; '):
    if '=' in pair:
        name, value = pair.split('=', 1)
        cookies.append({"name": name, "value": value, 
                        "domain": ".xiaohongshu.com", "path": "/"})
page.context.add_cookies(cookies)

3.1.3 搜索与正文提取流程(链接提取法)

由于小红书页面结构频繁变化,传统CSS选择器极易失效。最终采用了链接提取法——直接从页面中提取所有包含/explore/或/discovery/item/的链接,绕过类名依赖:

def crawler_worker(keyword, cookie_str, result_queue, status_queue):
    from playwright.sync_api import sync_playwright
    import time
    import random as rd

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        # ... 设置Cookie、访问首页、验证登录 ...
        
        while current_page <= max_pages and not stopped:
            search_url = f"https://www.xiaohongshu.com/search_result?keyword={keyword}&page={current_page}"
            page.goto(search_url)
            page.wait_for_load_state("networkidle")
            time.sleep(rd.uniform(1.5, 3.0))  # 随机延迟模拟人类行为
            
            # 检测验证码
            if "扫码验证" in page.content():
                page.screenshot(path="captcha_detected.png")
                raise Exception("触发反爬验证")
            
            # ---------- 链接提取法(核心) ----------
            links = page.evaluate("""
                () => {
                    const anchors = document.querySelectorAll('a');
                    const hrefs = [];
                    anchors.forEach(a => {
                        const href = a.getAttribute('href');
                        if (href && (href.includes('/explore/') || href.includes('/discovery/item/'))) {
                            hrefs.push({
                                href: href,
                                text: a.innerText || a.textContent || ''
                            });
                        }
                    });
                    return hrefs;
                }
            """)
            
            # 解析笔记ID、标题
            for link_info in links:
                note_id = extract_id_from_link(link_info['href'])
                title = link_info['text'].strip() or f"笔记_{note_id[:8]}"
                all_notes.append({"id": note_id, "title": title, "link": href})
            
            current_page += 1
        
        # 随机抽取30篇进入详情页抓取正文
        for note in sample_notes:
            page.goto(note['link'])
            # 使用多重选择器提取正文
            desc_selectors = [
                "div[class*='desc'] span",
                "div[class*='desc']",
                "div[class*='abstract']",
                "div[class*='content']",
                "div[class*='summary']"
            ]
            # ... 提取正文、点赞数、评论数 ...

3.2 情感分析模块(方案四:综合优化版)

3.2.1 技术选型

SnowNLP是一个轻量级的中文情感分析库,基于朴素贝叶斯分类器。无需下载大型预训练模型,pip install snownlp即可直接使用。

3.2.2 方案四优化策略

针对SnowNLP对剧本杀语境理解不足的问题,实施三重优化:

# 剧本杀领域积极词(这些词在SnowNLP中被低估)
SCRIPT_KILLER_POSITIVE = ["沉浸", "反转", "神作", "封神", "满分", "催泪", "高燃", "氛围感", "演绎", "经典", "神本"]

def analyze_text_sentiment(text):
    if not text or len(text) < 5:
        return 0.5, "😐 中性", "#FFC107"
    try:
        s = SnowNLP(text)
        raw_score = s.sentiments  # SnowNLP原始分数 0~1

        # 1. 整体偏移 +0.05(抵消SnowNLP对剧本杀文本偏消极的倾向)
        adjusted_score = raw_score + 0.05

        # 2. 领域词增强:含"沉浸""反转""神作"等词额外加分
        boost = 0
        for word in SCRIPT_KILLER_POSITIVE:
            if word in text:
                boost += 0.04  # 每个词加0.04
        if boost > 0.2:
            boost = 0.2  # 最多加0.2,防止过度
        adjusted_score = min(1.0, adjusted_score + boost)

        # 3. 阈值下调:积极 0.6→0.55,消极 0.4→0.35
        if adjusted_score >= 0.55:
            return adjusted_score, "👍 积极", "#4CAF50"
        elif adjusted_score <= 0.35:
            return adjusted_score, "👎 消极", "#F44336"
        else:
            return adjusted_score, "😐 中性", "#FFC107"
    except Exception as e:
        return 0.5, "😐 中性", "#FFC107"

3.3 坐牢程度模块

情感分析无法直接回答"这个本坐不坐牢"这个玩家最关心的问题。我设计了一个基于关键词权重的规则引擎,动态累加负面词汇的权重:

PRISON_KEYWORDS = {
    3: ["坐牢", "坐大牢", "想走", "离场", "玩不下去", "挂机", "掉线", "提前结束", "折磨"],
    2: ["拖时间", "冗长", "催眠", "犯困", "无聊", "枯燥", "重复", "边缘", "想睡觉", "煎熬"],
    1: ["一般", "平淡", "没意思", "节奏慢", "角色偏", "出戏", "略长", "太慢", "无趣"]
}

def calculate_prison_score(text: str) -> int:
    score = 0
    for weight, keywords in PRISON_KEYWORDS.items():
        for kw in keywords:
            if kw in text:
                score += weight
                if score >= 10:
                    return 10
    return min(score, 10)

3.4 统计与随机抽样

为提升分析的科学性,系统从爬取的笔记中随机抽取最多30篇有正文的帖子进行正文情感分析,避免高热度帖子对结果的过度影响:

# 随机抽取最多30篇有正文的帖子
posts_with_content = [n for n in all_notes_data if n['desc'] and n['desc'] != "暂无正文"]
sample_size = min(30, len(posts_with_content))
if sample_size > 0:
    sample_posts = random.sample(posts_with_content, sample_size)
    for post in sample_posts:
        score, label, _ = analyze_text_sentiment(post['desc'])
        body_scores.append(score)
    # 计算平均分和情感占比

四、调试过程与问题解决

开发过程中我遇到了大量问题,以下按照时间顺序详细记录每次错误的发现→尝试→解决/未解决的全过程。

4.1 尝试使用 xhs 第三方库

问题:安装xhs库后运行测试代码显示,找不到库。

  • 尝试:以为Python环境问题,检查pip列表发现已经安装过,但始终无法正常使用。尝试了多种安装方法,猜测是依赖非常多且版本冲突频繁。
  • 解决:强制安装后能导入依然无法使用。学习使用库里函数显示用法错误。
    屏幕截图 2026-06-07 174409
    屏幕截图 2026-06-07 182032
    屏幕截图 2026-06-07 174501
    屏幕截图 2026-06-07 200639
    屏幕截图 2026-06-07 200725

决定改用Playwright模拟真实浏览器。

4.2 改用 Playwright 模拟浏览器

问题:总是超时/无法正常找到结果

屏幕截图 2026-06-07 201304
屏幕截图 2026-06-07 213103
扩展评论选择器列表后可以找到相应的帖子但是依然无法生成结果
屏幕截图 2026-06-15 203824
无法正常读取评论内容
屏幕截图 2026-06-15 204212
解决:选择对一条帖子不直接读取评论,直接对正文进行情感分析(可实现)
屏幕截图 2026-06-15 205026
屏幕截图 2026-06-15 210331
屏幕截图 2026-06-15 212621
屏幕截图 2026-06-15 212621

4.3 情感分析的技术选型历程

问题:SnowNLP 初次使用效果尚可但精度一般,部分明显中性或积极的帖子被SnowNLP判为负面。

  • 尝试:计划升级到Transformers预训练模型(uer/roberta-finetuned-jd-binary-chinese)。但下载模型时遇到网络问题——HuggingFace被屏蔽。

  • 未解决:尝试使用国内镜像 HF_ENDPOINT=https://hf-mirror.com仍报404。
    又尝试安装PaddleNLP的SKEP模型,但我的Microsoft账户为中文用户名,遭遇中文用户名路径问题(C:\Users\山河.paddlenlp mkdir failed),无法创建缓存目录,存在兼容性问题。反复尝试设置环境变量 PADDLE_HOME 无果。
    屏幕截图 2026-06-16 171659
    屏幕截图 2026-06-16 172502
    屏幕截图 2026-06-16 172530

  • 最终决定:回退到SnowNLP并实施SnowNLP优化。通过整体偏移、领域词增强、阈值调整手段提升准确率。

4.4 问题:正文提取到导航栏内容而非真实正文

  • 发现:命令行输出的"正文"显示为"首页 点点 ai RED 直播 发布 消息 我...",明显是页面导航元素。
  • 尝试:意识到选择器不够精准。在提取正文前执行JavaScript移除侧边栏、导航栏等干扰元素。
  • 解决:增加正文选择器优先级列表(div[class='desc'] span → div[class='desc'] → div[class='abstract'] → div[class='content'] → div[class*='summary']),优先匹配最可能包含正文的元素。

4.5 反爬升级与最终失败(核心教训)

问题:小红书触发扫码验证死循环
在持续开发和测试过程中,不断提高爬取的帖子数量等,希望获得较为全面的数据,小红书开始频繁弹出"请使用已登录该账号的小红书APP扫码验证身份"页面。即使手动扫码验证成功,几分钟后再次请求时又会重新弹出验证。

  • 尝试:
    1. 更换 Cookie(重新从浏览器复制最新值)→ 无效
    2. 更换浏览器(Edge → Chrome)→ 无效
    3. 使用无痕模式 → 无效
    4. 切换网络环境(Wi-Fi → 手机热点)→ 无效
    5. 增加随机延迟(1.5~5秒)→ 无效
  • 根本原因分析:小红书采用了多重风控策略,不仅检测请求频率,还分析浏览器指纹、行为模式(如鼠标移动轨迹、滚动速度)、Cookie 时效性等。Playwright 虽然模拟了真实浏览器,但其自动化操作模式及随着爬取数据不断尝试增加,爬虫行为仍然可以被识别。当账号被标记为"高风险"后,任何自动化请求都会触发验证,且验证成功后短时间内再次请求仍会被拦截。
    47f153ab6686cac448d8d9fca511cd62
  • 最终结果:尝试所有常规反爬绕过手段均失败,系统无法继续获取数据。

五、使用指南与运行结果(理论下不被制裁版)

5.1 环境配置与安装指南

运行本系统之前,需要安装以下依赖库:

# 基础库
pip install snownlp
pip install playwright
pip install pandas
pip install matplotlib

# 安装 Playwright 浏览器内核
playwright install chromium

5.2 使用说明

  1. 替换Cookie:用浏览器登录小红书网页版,按F12→Network→刷新,复制任意请求的Cookie值,替换代码顶部的 COOKIE_STR。
  2. 启动程序:在终端执行 python 脚本名称.py,弹出Tkinter窗口。
  3. 输入关键词:在顶部输入框中输入想查询的剧本名称(例如"如故")。
  4. 点击【开始分析】:系统会自动打开浏览器、搜索、翻页、提取正文和评论。
  5. 查看结果:
    • 命令行会输出每篇帖子的标题、链接和正文预览
    • GUI的"综合评分"标签页显示整体统计
    • "详细数据"标签页展示每篇帖子的分析细节

5.3 运行结果展示(在正文提取修改即被反爬制裁前的一次成功运行(搜索剧本杀角色:梁以忱):

屏幕截图 2026-06-15 212621
屏幕截图 2026-06-15 213347

反爬失败时的表现:页面显示"请使用已登录该账号的小红书APP扫码验证身份",程序无法继续,控制台输出"触发反爬验证"错误信息。后多次尝试刷新cookie均失败。
debug_page_1

六、设计不足与优化空间

1.样本量小且非随机

理论需要随机抽取,但实际其实只爬取搜索结果的前30篇笔记,而小红书搜索结果受算法排序影响,会优先展示热度高的笔记,造成选择性偏差。
优化方向:增加爬取深度(滚动更多次或翻更多页),或实现随机抽取策略。(试验前已被反爬制裁)

2.情感分析精度有限

虽然通过领域词增强和阈值调整提升准确率,但SnowNLP基于朴素贝叶斯的本质未变,对复杂语义(如反讽、转折)仍无法准确识别。

优化方向:

  1. 收集500~1000条标注好的剧本杀评论,使用SnowNLP的 sentiment.train()功能重新训练
  2. 换用更高精度的模型(如Transformers RoBERTa、PaddleNLP SKEP),但需解决网络下载和中文路径兼容问题

3.坐牢程度规则过于简单

关键词匹配无法识别否定句式(例如"一点都不坐牢"会被误判为坐牢)。

4.性能问题

逐条爬取评论时耗时较长(约需2~3分钟)。

5.反爬机制与平台依赖性(核心问题)

本次实验深刻暴露了爬虫项目的一大固有风险:对目标平台的强依赖性。

备选方案实践(正文在此)

一、设计思路

1.1 设计背景与目的

在日常生活中,天气状况直接影响人们的出行计划、旅游决策、穿衣搭配和健康管理。每逢周末和节假日,想来一场说走就走的旅行,却要在各种网页翻翻找找确定目的地;想找一个温度适宜、体感舒适的城市旅游,却需要逐个查询多个城市的天气预报,手动比较,这一过程中已经消磨了这份说走就走的勇气。

同时,天气预报数据本身具有很高的分析价值。通过采集历史天气数据,我们可以分析一个城市的温度变化规律;通过对比多城市预报,可以为旅游出行提供科学决策依据;通过实时监测,可以对高温、低温、湿度异常等极端天气进行预警。

基于以上需求,我决定开发一套天气数据采集与智能旅游决策系统。该系统基于Open-Meteo免费天气API,能够自动获取全国150多个主要城市的实时天气、7天预报和30天历史数据,通过舒适度评价算法为用户提供综合出行指南,并推出未来7天全国最佳推荐和周边游推荐两大旅游决策功能,帮助用户规划旅行目的地。
image

1.2 开发环境与技术栈

类别 选型 选型理由
编程语言 Python 3.11 语法简洁、生态丰富,适合API调用与数据分析
HTTP请求 Requests 轻量级HTTP库,适合调用RESTful API
JSON解析 json Python内置,处理API返回数据
舒适度分析 自定义加权算法 温度70%权重 + 湿度30%权重,可解释性强
距离计算 Haversine公式 计算球面距离,精度满足实际需求
数据可视化 Matplotlib 绘制温度趋势图、分布直方图
GUI界面 Tkinter Python内置库,跨平台轻量级
数据导出 csv Python内置,导出历史数据
开发工具 PyCharm 插件丰富,支持断点调试

1.3 微信公众号交互扩展

在完成天气数据采集与智能旅游决策系统的基础上,为进一步提升系统的实用性和便捷性,我为其增加了微信公众号交互查询功能。用户无需打开电脑或使用GUI界面,只需在微信中关注测试号并发送城市名,即可实时获取该城市的天气信息及出行建议。

这一扩展使得天气查询从“桌面工具”升级为“随身助手”,真正实现了随时随地、轻量化的天气服务。该功能基于微信公众号测试号平台与Flask Web框架,通过内网穿透工具将本地服务暴露到公网,实现与微信服务器的双向通信。

技术选型:

类别 选型 选型理由
Web框架 Flask 轻量级、快速上手,适合小型API服务
微信消息解析 wechatpy 封装了微信消息加解密、解析、回复等操作
内网穿透 natapp 免费、稳定,将本地80端口暴露到公网
HTTP请求 requests 已用于天气API调用,无额外依赖

功能实现:

用户通过微信向测试号发送城市名(如“北京”)

程序接收消息,调用原有天气API获取实时天气和三日预报

自动生成格式化的天气回复(含温度、湿度、风速、舒适度、穿衣建议)

通过微信回复给用户。

1.4 完整功能清单

本项目共实现了以下9项核心功能:

序号 功能 说明
1 实时天气查询 获取指定城市当前温度、湿度、风速、风向,计算舒适度等级
2 7天天气预报 获取未来7天每日最高温、最低温、天气状况
3 历史温度统计 获取近30天历史温度数据,计算最高/最低/平均温度
4 温度趋势可视化 绘制7天温度变化折线图和近30天温度分布直方图
5 综合出行指南 整合出行指数、穿衣建议、带伞提醒、异常预警四大模块
6 全国每日最佳 遍历50+热门旅游城市,选出未来7天每天最适宜游玩的城市
7 周边游推荐 基于用户位置和出行半径,推荐周边最舒适的Top 5城市
8 数据导出CSV 将历史温度数据导出为CSV文件,便于进一步分析
9 公众号自动回复 对于用户发送的城市自动生成格式化的天气回复(含温度、湿度、风速、舒适度、穿衣建议),通过微信回复给用户

二、系统模块与代码结构

本系统采用模块化设计,将API调用、数据分析、界面展示分离,便于调试和维护。
image

2.1 系统架构图

(test.py)

┌─────────────────────────────────────────────────────┐
│                    GUI(Tkinter)                    
│          用户选择省份/城市,点击功能按钮              
└─────────────────────────┬───────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                 API调用模块(Requests)              
│         ┌─────────────┐    ┌─────────────┐         
│           当前天气API    →     7天预报API              
│         └─────────────┘    └─────────────┘         
│                ↓                   ↓                
│         ┌─────────────┐    ┌─────────────┐         
│            历史数据API   →    返回JSON数据             
│         └─────────────┘    └─────────────┘         
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                   数据分析引擎                      
│   ┌─────────────────┐      ┌─────────────────┐     
│     舒适度评价算法            出行指数算法            
│     温度+湿度加权             多条件规则判断           
│   └─────────────────┘      └─────────────────┘     
│   ┌─────────────────┐      ┌─────────────────┐     
│     全国最佳推荐             周边游推荐              
│     多城市遍历排序           距离计算+Haversine公式   
│   └─────────────────┘      └─────────────────┘     
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                    可视化与输出                      
│      ┌───────────┐ ┌──────────┐ ┌──────────┐      
│       Tkinter       Matplotlib    CSV导出           
│       界面展示        趋势图       文件导出           
│      └───────────┘ └──────────┘ └──────────┘      
└─────────────────────────────────────────────────────┘

2.2 各模块职责

模块 职责 关键技术和数据结构
主程序(单文件) 用户交互界面、线程管理、结果展示 Tkinter控件、threading多线程
API调用模块 请求Open-Meteo API获取天气数据 requests库、JSON解析
舒适度分析模块 基于温度和湿度计算舒适度评分 加权评分算法(温度0.7,湿度0.3)
距离计算模块 计算城市间球面距离 Haversine公式、经纬度坐标
出行指南模块 生成综合出行建议(指数+穿衣+带伞+预警) 多条件判断、规则引擎
全国最佳模块 遍历城市找出每日温度舒适度最高的城市 排序算法、多城市比较
周边推荐模块 基于位置和半径筛选并推荐城市 距离筛选 + 舒适度排序
可视化模块 绘制温度趋势图和分布图 Matplotlib、FigureCanvasTkAgg

三、核心模块实现

3.1 API调用模块

3.1.1 数据源选择

本系统选择Open-Meteo作为数据源,理由如下:

对比维度 Open-Meteo 其他天气API
是否免费 完全免费 大多需付费或有限额
是否需要注册 无需注册 需注册获取API Key
是否有反爬 无(官方公开API) 有频率限制
数据丰富度 当前/预报/历史全支持 视服务而定
# 安装依赖
pip install requests

3.1.2 当前天气API调用

Open-Meteo的当前天气接口支持获取温度、湿度、风速、风向等数据:

def get_current_weather(city_name):
    if city_name not in ALL_CITIES:
        return None
    coords = ALL_CITIES[city_name]
    url = f"https://api.open-meteo.com/v1/forecast?latitude={coords['lat']}&longitude={coords['lon']}&current=temperature_2m,relative_humidity_2m,windspeed_10m,winddirection_10m&timezone=Asia/Shanghai"
    try:
        resp = requests.get(url, timeout=10)
        data = resp.json()
        current = data.get('current', {})
        return {
            "城市": city_name,
            "温度": current.get('temperature_2m', 'N/A'),
            "湿度": current.get('relative_humidity_2m', 'N/A'),
            "风速": current.get('windspeed_10m', 'N/A'),
            "风向": current.get('winddirection_10m', 'N/A'),
            "时间": current.get('time', 'N/A')
        }
    except Exception as e:
        print(f"获取当前天气出错: {e}")
        return None

3.1.3 预报与历史API

def get_forecast(city_name, days=7):
    """获取未来7天预报"""
    coords = ALL_CITIES[city_name]
    url = f"https://api.open-meteo.com/v1/forecast?latitude={coords['lat']}&longitude={coords['lon']}&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=Asia/Shanghai&forecast_days={days}"
    # 解析返回数据

def get_historical(city_name, days=30):
    """获取历史天气数据(使用Archive API)"""
    url = f"https://archive-api.open-meteo.com/v1/archive?latitude={coords['lat']}&longitude={coords['lon']}&start_date={start_date}&end_date={end_date}&daily=temperature_2m_max,temperature_2m_min&timezone=Asia/Shanghai"
    # 解析返回数据

3.2 舒适度评价算法

舒适度是温度与湿度的综合反映。系统采用“温度70%权重 + 湿度30%权重”的加权评分方案:

温度区间 温度得分 湿度区间 湿度得分
18~25°C 1.0 40%~70% 1.0
1518°C或2530°C 0.7 30%40%或70%80% 0.6
1015°C或3035°C 0.4 <30%或>80% 0.3
<10°C或>35°C 0.2
def analyze_comfort(temperature, humidity=None):
    t = float(temperature)
    # 温度评分
    if 18 <= t <= 25: temp_score = 1.0
    elif 15 <= t < 18 or 25 < t <= 30: temp_score = 0.7
    elif 10 <= t < 15 or 30 < t <= 35: temp_score = 0.4
    else: temp_score = 0.2
    
    # 湿度评分
    if humidity is not None:
        h = float(humidity)
        if 40 <= h <= 70: hum_score = 1.0
        elif 30 <= h < 40 or 70 < h <= 80: hum_score = 0.6
        else: hum_score = 0.3
    else: hum_score = 0.5
    
    total_score = temp_score * 0.7 + hum_score * 0.3
    if total_score >= 0.8: label = "非常舒适"
    elif total_score >= 0.6: label = "舒适"
    elif total_score >= 0.4: label = "一般"
    else: label = "不舒适"
    return label, total_score

3.3 综合出行指南模块

出行指南整合了四个维度的建议,形成一站式出行参考:

def generate_comprehensive_guide(city, current, forecast, history):
    temp = float(current['温度'])
    humidity = float(current['湿度']) if current['湿度'] != 'N/A' else None
    wind = float(current['风速'])
    
    # 1. 出行指数(满分10分,根据温度、湿度、风速扣分)
    travel_score = 10
    if temp < 0 or temp > 35: travel_score -= 4
    elif temp < 5 or temp > 30: travel_score -= 2
    if humidity is not None:
        if humidity < 30 or humidity > 80: travel_score -= 2
    if wind > 20: travel_score -= 2
    travel_score = max(0, min(10, travel_score))
    
    # 2. 穿衣建议(基于温度区间)
    if temp >= 30: dress = "短袖+短裤,注意防晒"
    elif temp >= 25: dress = "短袖+薄外套"
    elif temp >= 18: dress = "长袖+薄外套"
    elif temp >= 10: dress = "毛衣+厚外套"
    else: dress = "羽绒服+围巾手套"
    
    # 3. 带伞提醒(基于天气代码判断是否下雨)
    rain_today = forecast[0].get('天气代码', 0) in [51, 61, 71, 80, 81, 82]
    umbrella = "今天有雨,记得带伞!" if rain_today else "今天无雨,不用带伞"
    
    # 4. 异常预警(高温/低温/湿度异常/温度突变)
    alerts = []
    if temp >= 35: alerts.append("🔥 高温预警!注意防暑降温")
    if humidity is not None and humidity >= 80: alerts.append("💧 湿度过高!体感闷热")
    # ...
    return report

3.4 全国每日最佳推荐模块

该模块通过遍历50+热门旅游城市,为未来7天每天选出温度舒适度最高的城市:

def get_weekly_best_cities(city_list=TOUR_CITIES):
    all_forecasts = {}
    for city in city_list:
        forecast = WeatherAPI.get_forecast(city, 7)
        if forecast:
            all_forecasts[city] = forecast
    
    best_cities = []
    for day_idx in range(7):
        best_city = None
        best_score = -1
        for city, fore in all_forecasts.items():
            if len(fore) <= day_idx: continue
            day_data = fore[day_idx]
            avg_temp = (day_data['最高温'] + day_data['最低温']) / 2
            score = get_comfort_score_from_forecast(avg_temp)
            if score > best_score:
                best_score = score
                best_city = city
        best_cities.append({"日期": date_str, "城市": best_city, ...})
    return best_cities

3.5 周边城市推荐模块

该功能专为周末短途出游设计。

3.5.1 距离计算(Haversine公式)

import math

def calc_distance(lat1, lon1, lat2, lon2):
    """计算两个经纬度之间的直线距离(公里),使用Haversine公式"""
    R = 6371  # 地球半径(公里)
    dlat = math.radians(lat2 - lat1)
    dlon = math.radians(lon2 - lon1)
    a = math.sin(dlat/2)**2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon/2)**2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
    return R * c

3.5.2 周边筛选与推荐

def get_nearby_cities(base_city, radius_km=300):
    """获取距离基准城市一定半径内的所有城市"""
    base = ALL_CITIES[base_city]
    nearby = []
    for city, info in ALL_CITIES.items():
        if city == base_city: continue
        dist = calc_distance(base['lat'], base['lon'], info['lat'], info['lon'])
        if dist <= radius_km:
            nearby.append({"城市": city, "距离": round(dist, 1)})
    return sorted(nearby, key=lambda x: x['距离'])

def recommend_nearby_cities(base_city, radius_km=300):
    """推荐周边舒适度最高的Top 5城市"""
    nearby = get_nearby_cities(base_city, radius_km)
    results = []
    for item in nearby:
        current = WeatherAPI.get_current_weather(item['城市'])
        if current:
            comfort_label, score = analyze_comfort(current['温度'], current['湿度'])
            results.append({...})
    results.sort(key=lambda x: x['舒适分'], reverse=True)
    return results[:5]

3.6 数据可视化模块

使用Matplotlib绘制两种图表,直观展示温度变化趋势:

# 7天温度趋势折线图
ax1.plot(dates, highs, 'r-o', label='最高温')
ax1.plot(dates, lows, 'b-o', label='最低温')
ax1.set_title(f'{city} 7天温度趋势')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 历史温度分布直方图
ax2.hist(hist_temps, bins=10, color='skyblue', edgecolor='black')
ax2.set_title(f'{city} 近30天温度分布')

3.7 微信公众号消息处理模块

该模块的核心是Flask服务,它接收微信服务器的POST请求,解析用户发送的文本消息,调用天气查询函数,并将结果返回给微信。

3.7.1 服务器验证

微信要求在首次配置URL时需要验证服务器有效性,验证过程通过GET请求完成:

@app.route('/wechat', methods=['GET', 'POST'])
def wechat():
    if request.method == 'GET':
        try:
            check_signature(TOKEN, signature, timestamp, nonce)
            return make_response(echostr)
        except Exception:
            return "验证失败", 403
    # POST请求处理...

3.7.2 消息解析与回复

用户发送的文本消息通过wechatpy库解析,提取城市名后调用天气函数:

msg = parse_message(request.data)
if msg.type == 'text':
    city = msg.content.strip()
    reply_text = get_weather_text(city)   # 复用原有天气逻辑
    reply = create_reply(reply_text, msg)
else:
    reply = create_reply('请发送城市名', msg)
return make_response(reply.render())

3.7.3 内网穿透配置

由于微信服务器必须访问公网URL,而开发环境在本地电脑,使用natapp进行内网穿透:

.\natapp.exe -authtoken=你的authtoken

运行后生成公网地址(如 http://s2f66a3a.natappfree.cc ),将该地址填入微信测试号后台的URL配置中(需加/wechat后缀)。

## 四、调试过程与问题解决

### 4.1 数据源选择与API调试

#### 问题1:选择哪个天气API?

- 发现:需要找一个免费、稳定、无需登录的天气数据源。
- 尝试:先后搜索了和风天气(需要API Key且免费额度有限)、OpenWeatherMap(国外源,延迟较高)、中国天气网(页面结构复杂)。
- 解决:最终选择Open-Meteo。完全免费、无需注册、无需API Key,且返回标准JSON格式,非常易于集成。

#### 问题2:获取湿度数据失败

- 发现:最初使用current_weather参数只能获取温度、风速、风向,无法获取湿度。
- 尝试:查阅Open-Meteo官方文档,发现需要使用新的current参数。
- 解决:修改API URL为current=temperature_2m,relative_humidity_2m,windspeed_10m,成功获取湿度数据。

### 4.2 舒适度算法设计

#### 问题3:科学量化"舒适度"?

- 发现:单纯看温度不能全面反映人体感受,湿度对体感温度影响很大。
- 尝试:查阅人体舒适度相关文献,温湿指数(THI)计算较为复杂。
- 解决:设计简化的加权评分方案——温度70%权重、湿度30%权重。该方案能区分"干热"和"湿热"、"干冷"和"湿冷"的体感差异。

### 4.3 GUI界面设计与交互优化

#### 问题4:城市列表过长,用户查找不便

- 解决:增加省份筛选下拉框,用户先选省份,城市下拉框自动更新为该省份的城市列表。

```python
def on_province_change(self, event):
    province = self.province_var.get()
    if province == "全部":
        cities = list(ALL_CITIES.keys())
    else:
        cities = CITY_BY_PROVINCE.get(province, [])
    self.city_combo['values'] = cities
    if cities:
        self.city_var.set(cities[0])

4.4 中文显示问题

问题:图表标题、坐标轴标签中的中文全部显示为方块。

屏幕截图 2026-06-16 204756
解决:使用Matplotlib的rcParams指定多个备选字体。

plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC', 'Heiti TC']
plt.rcParams['axes.unicode_minus'] = False

4.5 微信公众号接入与调试

在将天气系统接入微信公众号的过程中,遇到了以下几个典型问题:

问题1:内网穿透工具的选择与配置

最初尝试使用ngrok,但注册时遭遇Google验证码加载失败(ERR_NGROK_1205),无法完成注册。
屏幕截图 2026-06-21 224718
换用国内工具natapp后,注册与配置顺利,成功将本地80端口映射到公网。
屏幕截图 2026-06-21 224902
屏幕截图 2026-06-21 225021
屏幕截图 2026-06-21 225246
屏幕截图 2026-06-21 225428

问题2:微信服务器URL验证失败

屏幕截图 2026-06-21 225600
屏幕截图 2026-06-21 230058

在测试号后台填写URL和Token后,始终显示“验证失败”。排查发现:

URL末尾遗漏了/wechat路径 → 已修正

Token与代码中TOKEN变量不一致 → 已统一

修正后验证通过。

4.6 微信公众号定时推送调试

在完成微信公众号实时查询功能后,我尝试进一步实现每日定时推送天气的功能(设计目标为每天早上6:10自动推送)。然而在调试过程中遇到了模板消息内容为空的问题,经历了多次排查仍未完全解决,现将调试过程与遇到的问题记录如下。

问题:定时任务执行后,微信能够收到模板消息的标题(“天气提醒”),但正文内容为空白,仅有标题显示。微信服务器返回的响应为 {'errcode': 0, 'errmsg': 'ok'},表示消息已成功送达,但实际内容未能正确渲染。

# scheduler.py
import requests
from apscheduler.schedulers.background import BackgroundScheduler
from weather_system import get_morning_weather

# ===== 填写你的真实配置 =====
APPID = "appid"                # 测试号后台的 appID
APPSECRET = "appsecret"        # 测试号后台的 appsecret
OPENID = "真实openid"           # 用户列表里复制的 OpenID
TEMPLATE_ID = "模板消息 ID"  # 生成模板的ID

def get_access_token():
    """获取微信 access_token"""
    url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={APPID}&secret={APPSECRET}"
    try:
        resp = requests.get(url, timeout=10).json()
        token = resp.get("access_token")
        if token:
            print("获取 access_token 成功")
            return token
        else:
            print(f"获取 access_token 失败: {resp}")
            return None
    except Exception as e:
        print(f"请求 access_token 异常: {e}")
        return None

def morning_push():
    """推送早安天气"""
    city = "北京"
    weather_info = get_morning_weather(city)
    print("weather_info 内容:", repr(weather_info))   # 调试输出
    
    if not weather_info:
        print("天气信息为空,推送终止")
        return

    access_token = get_access_token()
    if not access_token:
        print("无 access_token,推送终止")
        return

    url = f"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={access_token}"
    data = {
        "touser": OPENID,
        "template_id": TEMPLATE_ID,
        "data": {
            "city": {"value": city, "color": "#173177"},
            "weather": {"value": weather_info, "color": "#000000"},
        }
    }
    try:
        resp = requests.post(url, json=data, timeout=10).json()
        print("微信返回完整响应:", resp)   # 调试输出
        if resp.get("errcode") == 0:
            print("早安推送成功 ✅")
        else:
            print(f"推送失败: {resp}")
    except Exception as e:
        print(f"推送请求异常: {e}")

# ===== 定时器:每天早上 6:10 执行 =====
scheduler = BackgroundScheduler()
scheduler.add_job(morning_push, 'cron', hour=6, minute=10)
scheduler.start()
print("定时任务已启动,每天 6:10 推送天气")

排查过程:

检查模板字段名:确认模板内容为 {{city.DATA}} 和 {{weather.DATA}},代码中发送的 data 字段键名分别为 city 和 weather,与模板完全一致。
屏幕截图 2026-06-21 231813
屏幕截图 2026-06-22 090130

检查天气数据内容:通过单独调用天气函数验证,weather_info 变量包含完整的天气信息字符串(如“☀️ 早安!北京今日天气:温度 25.2°C,湿度 47%,建议:天气适宜,穿长袖薄外套。”),数据本身正常。
屏幕截图 2026-06-21 232941

检查微信返回响应:打印微信 API 返回的完整响应,显示 errcode: 0,说明微信服务器已成功接收请求,但模板字段未被替换。

排查模板格式问题:微信模板消息对字段格式有严格要求,字段名必须与模板中的占位符完全匹配。此外,有开发者反馈模板消息内容为空可能与换行符有关——微信调整规范后不再支持换行符,换行符之后的内容可能被截断。也有分析指出,如果变量前面没有添加文字说明,可能被系统误判为首行内容或尾部备注而被去除。

尝试的解决方案:

多次核对模板字段名与代码中 data 字典的键名,确保完全一致

将天气信息中的换行符替换为空格,排除换行符干扰

重新创建模板并更新模板 ID

当前状态:

经过上述排查与调整,定时推送仍未能正常显示完整内容。由于时间限制,决定暂不继续深入调试该功能,转而专注于已稳定运行的实时查询功能。

4.7 华为云部署与调试

在完成本地开发与微信测试号接入后,我将项目部署到了华为云弹性云服务器(ECS)上,以实现 7×24 小时稳定运行。部署过程中遇到了若干问题,记录如下。

问题1:pip 安装依赖时网络超时

现象:执行 pip3 install flask wechatpy requests 时,下载过程中出现 TimeoutError: The read operation timed out,安装失败。

原因:pip 默认从国外官方源(PyPI)下载,国内网络访问速度慢,容易超时。

解决方案:换用国内镜像源(清华镜像),执行以下命令即可快速完成安装:

pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple flask wechatpy requests

问题2:服务器无法导入 tkinter 模块

现象:启动 run_wechat.py 时报错 ModuleNotFoundError: No module named 'tkinter'。

原因:test.py 中包含了 import tkinter as tk 和 matplotlib.use('TkAgg') 等图形界面相关代码,而云服务器是命令行环境,没有图形界面支持。

解决方案:创建了一个不含 GUI 的精简版天气模块 weather_api.py,仅保留天气 API 调用和数据处理函数,供微信服务调用。原 test.py 保留用于本地 GUI 测试,两者互不干扰。
![image](https://img2024.cnblogs.com/blog/3777270/202606/3777270-20260622114712250-493555383.png)

问题3:matplotlib 后端加载失败

现象:安装 matplotlib 后启动服务,报错 ImportError: Cannot load backend 'TkAgg' which requires the 'tk' interactive framework, as 'headless' is currently running。

原因:服务器为无图形界面的 headless 环境,matplotlib 默认尝试加载 TkAgg 后端,但该后端依赖 tk 图形框架,无法在服务器上运行。

解决方案:同上,使用精简版 weather_api.py,完全移除了 matplotlib 和 tkinter 的导入,微信服务不再依赖任何图形界面库。


## 五、使用指南与运行结果

### 5.1 环境配置与安装指南

```bash
# 安装依赖库
pip install requests
pip install matplotlib

# 如需使用微信推送扩展功能
pip install requests  

5.2 使用说明

  1. 启动程序:在终端执行 python 天气系统.py,弹出Tkinter窗口。
  2. 选择省份/城市:从下拉框中选择目标省份和城市。
  3. 获取天气数据:点击【获取数据】,系统自动调用API显示当前天气、7天预报、历史统计和温度图表。
  4. 查看出行指南:点击【出行指南】,弹出完整出行建议(指数+穿衣+带伞+预警)。
  5. 全国最佳推荐:点击【全国最佳】,显示未来7天全国最舒适城市。
  6. 周边推荐:设置"我的位置"和"半径",点击【周边推荐】获取附近最佳出游地。
  7. 导出数据:点击【导出CSV】,保存历史温度数据。

5.3 运行结果展示

image
image
image
image
image
image
image
image

5.4 微信公众号查询

配置步骤:

申请微信测试号( https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo ),获取appID、appsecret

在“接口配置信息”中填写URL(http://你的natapp域名/wechat)和Token,提交验证
屏幕截图 2026-06-21 230926

在“模板消息接口”中无需配置(本功能不使用模板消息)

用手机微信扫描测试号二维码关注

使用方法:

在微信中打开测试号聊天窗口,直接发送城市名(如“上海”),即可收到包含当前天气、温湿度、风速、舒适度及未来三天预报的回复。

运行效果:

用户发送“北京”后,微信自动回复:
15538c42a009b81e081a40a6e6dc659e

六、项目部署与上线

为了让微信公众号天气查询服务能够 7×24 小时稳定运行,不再依赖本地电脑的持续运行和内网穿透工具(如 natapp),我将该项目部署到了华为云弹性云服务器(ECS)上。

6.1 华为云服务器配置

6.1.1 服务器选购

在华为云官网购买了一台弹性云服务器(ECS),具体配置如下:

操作系统: Ubuntu 22.04 LTS
CPU: 2核
内存: 2GB
带宽: 5Mbps
公网IP: 自动分配弹性公网IP
image

6.1.2 安全组配置

安全组需要开放特定端口。在华为云控制台进行以下配置:

进入华为云控制台 → 弹性云服务器 → 安全组

配置规则的入方向规则需包含:

优先级:1

策略:允许

协议端口:TCP 80

源地址:0.0.0.0/0(允许所有IP访问)
image

6.1.3 服务器环境配置

通过 SSH 远程连接服务器:
image
image

环境配置:

# 更新系统
sudo apt update 

# 安装 Python3 和 pip
sudo apt install python3 python3-pip -y

# 安装项目依赖
pip3 install flask wechatpy requests

image
image

6.1.4 项目文件上传

使用 scp 命令将本地项目文件上传到服务器:

scp -r D:/PythonProject root@你的服务器公网IP:/root/

文件较多,实际改用只上传三个代码文件的方式:(先Ctrl+c退出进程后重新远程连接)
image

6.1.5 更新接口配置URL
屏幕截图 2026-06-22 114208

6.1.6 服务运行与守护
屏幕截图 2026-06-22 114225

为了让 Flask 应用在断开 SSH 连接后继续运行,使用 nohup 命令将其设置为后台服务:

cd /root/PythonProject
nohup python3 run_wechat.py > output.log 2>&1 &

nohup:使进程忽略挂断信号
output.log:将输出重定向到日志文件

2>&1:将错误输出也重定向到同一文件

&:将进程放到后台运行

查看运行状态和日志:

tail -f output.log

如需停止服务:

ps aux | grep run_wechat.py  # 查找进程ID
kill 进程ID                   # 停止服务

6.2 部署成果

部署完成后,将微信测试号后台的接口配置信息(URL)更新为 http://服务器公网IP/wechat,提交后接口配置信息验证一次通过。

至此,天气查询服务成功实现云端在线。用户只需关注测试号并发送城市名,即可随时获取天气信息,无需担心本地电脑关机或内网穿透工具不稳定的问题。

6.3 与本地开发方式的对比

对比维度 本地 + natapp 华为云 ECS
运行稳定性 依赖本地电脑持续开机 7×24 小时稳定运行
公网访问 依赖内网穿透工具,免费版域名可能变化 固定公网 IP,长期有效
维护成本 需手动启动 natapp 和 Flask 配置一次,自动运行
适用场景 开发调试 生产环境部署

七、设计不足与优化空间

7.1 舒适度算法仍可优化

当前舒适度评价仅考虑了温度和湿度两个因素,未纳入风力、日照强度、气压等影响体感的重要变量。

7.2 全国最佳推荐的城市多样性不足

当前仅基于温度舒适度排序,未考虑城市特色、旅游旺季、交通便利度等因素。

7.3 用户位置自动识别缺失

当前需要用户手动选择基准城市。
优化方向:集成IP定位服务自动识别用户所在城市。

7.4 微信交互的进一步扩展

当前已实现用户通过微信公众号主动查询天气的功能,用户发送城市名即可获取实时天气信息。在此基础上,未来可进一步扩展:

主动推送预警:当检测到极端天气(高温、暴雨、大风等)时,通过模板消息主动向用户推送预警,无需用户主动查询。

多城市订阅:允许用户绑定多个关注城市,每日定时推送各城市的天气摘要。

更丰富的交互:支持语音输入、发送定位自动获取天气、发送“穿衣”“带伞”等关键词获取专项建议。

7.5 计算速度较慢

八、实验心得与总结

8.1 爬虫技术的进阶应用
从简单的requests库到Playwright浏览器自动化,深刻体会了动态页面爬取和反爬策略应对的复杂性。原来爬虫不仅仅是发请求、解析HTML那么简单,还要考虑登录态、Cookie管理、页面结构变化等一系列工程问题。

8.2 模块化设计的重要性
两个项目均采用功能分块的结构,将不同模块清晰分离,便于调试和修改。每个函数的职责单一,降低了出错时的排查难度。无论是爬虫项目还是API调用项目,良好的代码结构都是项目可维护的基础。

8.3 技术选择要务实
从Requests库的基本用法到处理超时、异常、JSON解析,我掌握了调用第三方API的完整流程。Open-Meteo的免费、无需认证、稳定可靠,让项目得以顺利推进。与之前需要处理Cookie、签名、验证码的爬虫相比,API调用大大降低了开发成本。
在PaddleNLP和Transformers接连因为网络、路径兼容问题无法使用后,我深刻体会到:再好的工具,装不上就是零分。(到底当时为什么要把用户名设成中文啊啊啊啊啊!)

8.4 算法设计的权衡
在设计舒适度评价算法时,面临精度与简洁的权衡。虽然温湿指数(THI)在学术上更科学,但为了代码的可读性和可维护性,选择了简化版加权评分方案。

8.5 单一模型无法解决所有问题
在失败项目中,情感分析模型虽然能判断文本的积极/消极倾向,但它识别不出"坐牢感"这种剧本杀特有的负面体验。因此,在AI模型之外补充基于规则的"坐牢程度"评判。这种"多维度评价"的思路后来也被迁移到天气系统中——舒适度评价同时考虑温度和湿度,而不是单一指标。

九、我学了什么

本学期《Python程序设计》课程系统学习了Python编程的核心知识,从基础语法到高级应用,形成了相对完整的知识体系。下面是我对课程内容的梳理和总结:

课程从Python的基本语法开始,包括变量、数据类型(数字、字符串、布尔型)、运算符、输入输出等。在此基础上,我学习了条件判断和循环语句,掌握了序列(列表、元组、字典、集合)这三种数据结构。列表是可变的有序序列,元组是不可变的,字典是键值对存储方式,集合则用于去重和集合运算。在不同的业务场景下,选择合适的数据结构能够显著提升代码的执行效率。

字符串是Python中最常用的数据类型之一,我学会了字符串的索引、切片、格式化等操作,还学习了正则表达式的使用,能够从杂乱文本中精准提取想要的信息。这个让我第一次感觉到python的简洁高效。每次写c语言读取内容的时候我都要思考好久,用什么类型的变量,用什么读取函数,会不会越界会不会遇到特殊字符断掉......

函数是组织代码的基本单元,我学会了函数的定义和调用、形参与实参的区别、默认参数和关键字参数的使用。面向对象部分,我理解了类和对象的关系,类是对具有相同属性和行为的一类事物的统称,对象则是根据类创建出来的具体实例,以及__init__构造方法在对象实例化时自动执行的作用。此外,我还学习了文件操作、异常处理的结构、数据库的基本操作。

在数据类型方面,我学会了区分结构化数据和非结构化数据。在综合实践中,天气API返回的JSON数据就是结构化数据,字段清晰、易于解析;而小红书爬到的帖子正文则是非结构化数据,需要经过清洗、分词、情感分析等一系列预处理后才能进行量化分析。这种从原始数据到可用数据的处理流程,让我对数据预处理有了更具体的认识。

网络爬虫开发我觉得是最有挑战性的部分。选取爬虫实验更是不断遇到问题:从最开始安装xhs库导入失败,到改用Playwright后选择器超时,再到Cookie过期、正文提取到导航栏内容而非真实正文……一开始没有被小红书发现时,我还觉得挺神奇的,毕竟看着代码自动控制浏览器执行搜索、滚动、提取内容,像是一个机器人在替我操作,体验非常新鲜。但随着爬取次数增多,最终还是触发了反爬机制,这也让我对爬虫的法律边界和技术风险有了直观认识。

(对Python的认识)

在学习Python之前,我对编程的理解停留在“写代码”层面——学语法、记函数、背格式,感觉学一门编程语言好难。但通过这门课的学习,我发现Python真正的价值不在于背了多少语法,而在于能否用它解决实际问题。

老师在课堂上用“蛋炒饭”和“盖浇饭”的比喻,让我一下子就理解了面向对象的核心优势。蛋炒饭把所有东西混在一起,牵一发而动全身;盖浇饭则是米饭打底、浇头分开,可以灵活搭配、自由扩展。让我体会到面向对象语言在代码组织上的极大优势:思路更加直接,扩展更加灵活。

再加上Python拥有极其丰富的第三方库生态,让我在编写程序时真正感受到了这门语言的魅力——Python最强大的不是语言本身,而是它的生态。几十万个第三方库几乎覆盖了所有领域:写爬虫有Scrapy、Playwright,做数据分析有Pandas、NumPy,做可视化有Matplotlib、Pyecharts,做人工智能有PyTorch、TensorFlow。这种“开箱即用”的体验,让开发者可以站在巨人的肩膀上,把精力集中在“做什么”上,而不是纠结于“怎么实现底层机制”。

(实践体会)

动手实践是最好的学习方式。 在调试过程中遇到的每一个问题,几乎都是书本课件上没有的“真实世界”问题:选择器失效怎么办?Cookie过期怎么处理?GUI界面卡死了如何解决?我越来越深刻地体会到,在编程中系统不是要么这样要么那样——它还会冒出各种各样意想不到的问题。在这门语言的学习上,失败才是学习最重要的一部分,因为每解决一个意外状况,你对这门语言的理解就会加深一层。

(意见和建议)

  1. 关于课程进度:课程前半段内容简单,但后半段章节较多且难度有所提升(文件操作、数据库、GUI、爬虫、Web框架等),内容密度较大,需要理解的更多,节奏偏紧,建议适当调整。

  2. 关于实践环节:建议增加更多“真实场景”的练习题,也可以适当增加课堂和课后练习呀。

很感谢王老师一个学期的悉心教学。这门课对我而言,更多的是一次完整的“从想法到实现”的项目体验(虽然我的剧本杀伟大项目中道崩殂了)。我不仅学会了一门编程语言,更学会了如何用技术去解决一个真实的问题,让我真正体会到了编程的乐趣和成就感。

附录:完整代码

posted @ 2026-06-16 22:45  山河13  阅读(25)  评论(0)    收藏  举报