ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python作业4全攻略:从环境配置到算法、爬虫与并发优化

Python作业4全攻略:从环境配置到算法、爬虫与并发优化 很多刚把Python学到第四次作业的朋友基本都会撞上同一堵墙环境装了一半代码一跑全是红字算法题不知道从哪下手画个图坐标轴还能挤成一团。这篇文章就围绕“python 作业4”这个阶段的典型任务把从环境配置、基础语法到动态规划、层次聚类、数据可视化、爬虫采集再到协程与多进程优化的完整链路串一遍。既适合零基础自学者对照落地也适合正在做编程作业、准备课程项目的人拿来当“抄作业”的参考。后面所有内容都是我实际跑过、踩过坑之后整理出来的尽量把每一步的为什么也讲清楚。1. 开工前的环境地基装对Python后面少走一半弯路1.1 版本选择与安装方式别在第一步就埋雷Python作业阶段最常见的环境问题不是代码写错而是解释器压根没配好。很多新手从网上下载了Python安装包安装时一路点“Next”装完打开终端输入python却弹出一行“python was not found; run without arguments to install from the Microsoft Store”人直接懵了。这种情况十有八九是安装时没有勾选“Add Python to PATH”。PATH是什么你可以把它理解成操作系统的“通讯录”。你输入python这个命令时系统要去PATH记录的路径里逐个找有没有叫python.exe的程序。安装时没把Python的安装目录加进这个“通讯录”系统自然找不到只能转而建议你去微软商店装一个。版本选择上作业阶段不用追求最新选3.10或3.11这个级别的最稳。3.12、3.13虽然新但个别第三方库可能还没有对应的预编译包遇到安装失败还要折腾编译环境作业阶段没必要。安装时注意两点一是64位系统就别下32位安装包否则内存受限不说有些库也装不上二是安装路径尽量保持默认或者用纯英文路径后面在命令行操作时会省很多事。1.2 虚拟环境与编辑器配置很多同学装好Python之后所有库全部往全局环境里塞今天装这个项目要的pandas明天装那个项目要的Django后来说不清哪个版本冲突了一运行就报错。避免这个问题的方法就是虚拟环境。虚拟环境相当于给每个项目开一个独立的“小房间”每个房间里都有自己的一套Python解释器和第三方库互不干扰。用Python自带的venv模块就能创建# 在项目目录下创建名为 .venv 的虚拟环境 python -m venv .venv # Windows下激活虚拟环境 .venv\Scripts\activate # macOS / Linux 下激活虚拟环境 source .venv/bin/activate激活之后命令行提示符前面会出现一个(.venv)前缀这时候再用pip install装的库都只会装进这个环境里。编辑器方面VSCode里按CtrlShiftP输入“Python: Select Interpreter”选择刚才那个.venv下的Python解释器就不会出现在终端能运行、编辑器里却提示ModuleNotFoundError的诡异问题了。1.3 换国内镜像源告别下载超时pip install默认从PyPI官方源下载国内网络环境经常慢到怀疑人生还动不动超时。解决办法是配置国内镜像源。我用得比较多的是清华源稳定且更新及时pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple阿里云源https://mirrors.aliyun.com/pypi/simple/、中科大源https://pypi.mirrors.ustc.edu.cn/simple/也可以。如果只是临时装某个包也可以不修改全局配置直接指定源pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple这里有一个细节配置完镜像源之后如果之前已经下载了一半的缓存损坏装包时可能报“THESE PACKAGES DO NOT MATCH THE HASHES”。先执行pip cache purge清掉缓存再装基本就能解决。2. 基础语法别看简单作业里扣分的往往是小细节2.1 动态类型与强制类型转换Python是动态类型语言同一个变量可以先存整数再存字符串语法上完全合法。但作业里犯的一个典型错误就是把各种类型混在一起运算。比如从控制台读到的年龄默认是字符串age input(请输入年龄) next_age age 1 # 报错TypeError: can only concatenate strinput返回的是str直接和整数相加必然报错。正确做法是先转换再运算age int(input(请输入年龄)) next_age age 1看起来简单但我在实际批改作业时发现很多同学卡在这里。还有一个容易被忽略的坑int(3.14)会报错因为int()只能转换纯数字字符串要转小数应该用float(3.14)。反过来str(3.14)倒是可以正常转换。类型转换的核心原则是“先确认源类型再选择转换函数”不确定时可以先用type()看一眼。2.2 函数参数位置参数与关键字参数的边界作业里写函数时文档里经常会出现“位置参数”“关键字参数”这些词。位置参数就是按顺序传入的参数关键字参数就是通过参数名值方式传入的。def greet(name, age18): print(f{name}今年{age}岁) greet(小明) # 使用默认参数 greet(小明, 20) # 按位置传参 greet(name小红, age19) # 按关键字传参需要注意两点一是位置参数必须放在关键字参数前面否则解释器直接报SyntaxError二是如果函数已经给了默认值调用时不传它会用默认值但新手的常见误区是“默认参数可以随便设成可变对象”。比如写成def func(lst[]):多次调用时默认列表会累积上次的数据这是Python的一个经典陷阱正确写法应该用None作为默认值在函数内部再初始化为空列表。2.3 内置函数和推导式的加分用法作业里如果能把一些内置函数用对代码会显得干净很多。abs()求绝对值没什么坑但要注意它对复数返回的是模长abs(34j)的结果是5.0。round()做四舍五入时要小心Python采用的是“银行家舍入”round(2.5)结果是2而不是3如果你真的需要传统四舍五入可以用Decimal或自己写个浮点判断。列表推导式也是作业里非常实用的语法用一个表达式替代好几行循环# 生成0到19之间的偶数平方 squares [x ** 2 for x in range(20) if x % 2 0]不过推导式虽好不要无脑嵌套。超过两层嵌套的推导式可读性断崖式下降这种时候老老实实写循环反而更好作业评分里代码可读性也是重要一环。3. 动态规划、聚类这类算法作业核心是建模思路3.1 01背包从二维DP到一维滚动数组算法类作业里01背包和最少硬币是出现频率极高的两道题。很多同学上课听懂了思路一写代码就无从下手本质上是“状态”和“转移”这两个概念没落地。01背包问题有n件物品每件物品有重量w[i]和价值v[i]背包容量为W每件物品只能选一次求能装入的最大价值。第一步定义状态dp[i][j]表示“在前i件物品中挑选总重量不超过j时能获得的最大价值”。第二步写转移方程dp[i][j] max(dp[i-1][j], dp[i-1][j - w[i]] v[i])不选第i件物品就是前i-1件在容量j下的最优解选第i件物品就得给它在容量里腾出w[i]的空间再加上它的价值v[i]。基础代码如下def knapsack(W, weights, values): n len(weights) dp [[0] * (W 1) for _ in range(n 1)] for i in range(1, n 1): for j in range(W 1): if weights[i - 1] j: dp[i][j] max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] values[i - 1]) else: dp[i][j] dp[i - 1][j] return dp[n][W]观察转移方程会发现第i行的计算只用到了第i-1行所以可以压缩成一维数组。但这里有个关键点内层循环必须倒序遍历容量j。原因是正序遍历时dp[j - weights[i-1]]可能已经被本轮更新过相当于同一件物品被选了多次就变成“完全背包”了。倒序遍历能保证每个状态只基于上一轮的结果从而维持“每件物品最多选一次”的约束。3.2 最少硬币自底向上的递推思路最少硬币问题也很典型给定硬币面额coins [1, 2, 5]目标金额amount 11求最少用多少枚硬币凑出目标金额。状态定义是dp[m]表示凑出金额m需要的最少硬币数。初始时dp[0] 0其余金额设为一个很大的数表示“暂时凑不出来”。转移时对每个金额m尝试每一种硬币面额coin如果m coin就更新dp[m] min(dp[m], dp[m - coin] 1)完整代码def min_coins(coins, amount): dp [float(inf)] * (amount 1) dp[0] 0 for m in range(1, amount 1): for coin in coins: if m coin: dp[m] min(dp[m], dp[m - coin] 1) return dp[amount] if dp[amount] ! float(inf) else -1这道题的坑在于初始化不能用一个太大的整数比如999999万一题目数据量大999999 1不会溢出但语义不明。用float(inf)更规范。另外如果返回结果是-1说明这个金额没法用现有硬币凑出来很多同学会漏掉这个边界情况。写完算法后我建议一定要手动构造几个测试用例比如coins[2]、amount3确保返回-1的逻辑正确。3.3 层次聚类库函数与手写实现层次聚类是数据分析类作业里常见的内容目标是把样本逐层合并成一棵聚类树。如果作业允许调库用scipy可以非常方便地实现from scipy.cluster.hierarchy import linkage, fcluster import numpy as np X np.array([[1, 2], [2, 3], [10, 12], [12, 13]]) Z linkage(X, methodward, metriceuclidean) labels fcluster(Z, t2, criterionmaxclust)linkage计算的是聚类树的连接矩阵fcluster按指定条件把样本分成若干簇。但如果作业要求自己实现核心逻辑就三步初始化每个样本自己单独成一簇计算所有簇两两之间的距离找到最近的两个簇合并更新距离矩阵重复直到满足终止条件。最常用的距离是“单连接”即两个簇之间的最短距离等于两簇中任意两个样本间的最小距离。这个思想有点像“朋友圈扩散”两个人只要各有朋友在对方的圈子里两个圈子就能聚到一起。作业里建议先写一个小数据集的朴素版本验证聚合顺序无误再考虑用堆或矩阵优化性能否则一上来就上复杂数据结构bug定位会非常痛苦。4. 数据可视化作业里最常见的三个坑及处理方案4.1 横坐标轴标签密集重叠很多同学第一次用matplotlib做图拿到的数据可能有一百多个日期或者几十个分类名称直接plt.xticks()默认全画出来结果就是坐标轴底下一片黑字全压在一起根本看不清。热搜词里“python画图横坐标太密集”能上火说明这个问题太普遍了。解决方案有三个层次。最省事的是旋转标签plt.xticks(rotation45)但这只是治标不治本标签多到一定程度旋转后还是会叠。第二个方案是每隔N个显示一个x range(len(data)) plt.xticks(x[::5], labels[::5]) # 每隔5个显示一次第三个方案是是用MaxNLocator自动控制刻度数量import matplotlib.pyplot as plt from matplotlib.ticker import MaxNLocator ax plt.gca() ax.xaxis.set_major_locator(MaxNLocator(nbins10))nbins10表示尽量把横轴刻度控制在10个以内。我个人的建议是作业阶段先看数据量少于30个标签旋转45度基本可以超过30个直接用MaxNLocator观感会专业很多。4.2 中文乱码不是代码逻辑的问题matplotlib默认字体不支持中文所以图里一旦出现“销售额”“时间”“温度”这些词就会变成一个个方块非常典型。解决办法是设置中文字体和unicode负号plt.rcParams[font.sans-serif] [SimHei] # Windows常用黑体 plt.rcParams[axes.unicode_minus] False # 解决负号显示异常在macOS上可以把SimHei换成Arial Unicode MS或PingFang SC。如果设置了字体还是乱大概率是系统里没有对应字体文件。Windows下可以在C:\Windows\Fonts里确认是否有simhei.ttf没有就换Microsoft YaHei微软雅黑试试。4.3 图形样式与导出质量作业里交图导师不仅看数据对不对还会看图的整体质感。有几个小技巧能让图立刻“高级”起来指定画布尺寸plt.figure(figsize(10, 5))避免默认6x4导致比例不合适保存时开高dpiplt.savefig(result.png, dpi150, bbox_inchestight)bbox_inchestight会裁剪掉边缘多余空白画多条线时要加图例plt.legend()并在每条线里通过label参数命名。另外要注意plt.show()和plt.savefig()的顺序。先调用plt.show()会把当前图形清空再plt.savefig()保存下来的可能是一张空白图。正确的顺序是先savefig再show或者先show后用plt.gcf().savefig()来保存当前的Figure对象。5. 爬虫和数据采集类作业先学会跟网页“说话”5.1 requests请求与Headers伪装Python爬虫是很多同学学Python的理由作业里也经常出现“采集某个网站的数据并分析”的题目。最基础的爬虫流程只有五步确定URL、发送请求、解析响应、提取数据、保存结果。用requests发送请求是最直接的入口import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(https://example.com, headersheaders, timeout10) resp.encoding resp.apparent_encoding print(resp.status_code)headers里的User-Agent用来告诉服务器“我是一个浏览器”如果不加很多网站会直接返回403禁止访问。timeout参数一定要设置否则程序可能一直卡在网络请求上。resp.encoding resp.apparent_encoding是自动检测网页编码避免读取出来全是乱码。作业阶段我特别建议先打印出resp.status_code和resp.text[:500]看一眼确认服务器确实返回了预期内容再进行解析否则后面全是在调bug。5.2 BeautifulSoup解析与字段提取拿到网页文本后通常用BeautifulSoup来提取结构化数据。比如要提取页面上所有标题from bs4 import BeautifulSoup soup BeautifulSoup(resp.text, html.parser) for h in soup.find_all(h2): print(h.get_text(stripTrue))解析方式无非三种find()找第一个匹配标签find_all()找所有匹配标签select()用CSS选择器精确定位。get_text()时加上stripTrue可以去掉首尾空白提取出来的数据更干净。这里的常见错误是试图从JSON接口和HTML页面用同一套代码解析导致一会儿找到一会儿找不到。先打开浏览器开发者工具看一眼页面结构再写选择器效率会高很多。还有一个正则表达式的场景如果有些字段藏在script标签里的JSON串中用re.search配合json.loads提取往往比HTML解析更稳。5.3 数据清洗与入库爬下来的数据极少是直接能用的通常要经过清洗。比如金额字段里带人民币符号、日期格式不统一、缺失值为空字符串都需要处理。如果数据量不大直接用pandas最方便import pandas as pd df pd.DataFrame(data_list) df[价格] df[价格].str.replace(¥, ).astype(float) df df.dropna(subset[标题]) df.to_csv(output.csv, indexFalse, encodingutf-8-sig)这里要注意保存CSV时用utf-8-sig而不是utf-8。因为Excel打开UTF-8文件时默认用ANSI编码不指定BOM会导致中文乱码utf-8-sig能规避这个问题。这也算是我踩过几次坑之后沉淀下来的经验。写爬虫作业的时候一定注意礼貌设置time.sleep(1)控制请求频率不要对目标服务器发起并发攻击式请求。作为学习用途采集公开数据没有问题但不要爬取需要登录或明确禁止的接口也不要大规模存储涉及隐私的内容。6. 想让代码跑得更快协程与多进程的正确打开方式6.1 什么时候该用协程如果你的作业里有一段代码要频繁请求网络接口比如爬取50个页面每个请求等1秒串行跑就是50秒。这时候该考虑的不是多线程而是“协程”。协程适合IO密集型任务也就是程序大部分时间在等待外部响应网络、磁盘而不是在疯狂计算。它的核心思想是在等待IO的时候先去执行其他任务而不是干等。Python里用asyncio实现协程import asyncio async def fetch(url): print(f开始请求 {url}) await asyncio.sleep(1) # 模拟IO等待 print(f完成请求 {url}) async def main(): tasks [fetch(fhttps://example.com/page/{i}) for i in range(5)] await asyncio.gather(*tasks) asyncio.run(main())asyncio.gather把多个协程任务合并起来并发执行总耗时接近单个任务的耗时而不是累加。这里最容易出错的是忘了写await协程对象如果不被await它只是一个对象连跑都不会跑。另外requests库是同步的会阻塞事件循环协程里要发HTTP请求得用aiohttp这类异步库这个坑我在第一次写异步爬虫时踩得很惨。6.2 什么时候该用多进程协程处理计算密集型任务并不合适。比如你的作业要算大量矩阵乘法、大量排序这些任务CPU一直在满负荷运转协程帮不上忙还因为事件循环的调度开销反而更慢。多进程可以解决计算密集问题。每个进程有独立的Python解释器和内存空间可以真正并行利用多个CPU核心。标准库multiprocessing提供了一个很友好的接口from multiprocessing import Pool def square(x): return x * x if __name__ __main__: with Pool(4) as pool: results pool.map(square, range(10)) print(results)这里的要点是if __name__ __main__:保护必须写。在多进程环境下子进程会重新导入主模块如果没有这个保护Windows上会无限递归创建进程直接报RuntimeError。这个错误我见过太多次了。6.3 实战给作业加一个并发加速开关面对作业题“用协程还是多进程”本质上取决于瓶颈在哪里。一个方便的判断方式是程序在等什么如果是在等网络响应、等文件读写选协程如果是在等CPU计算完选多进程。我在辅导作业时发现一个特别好的实践把并发逻辑封装成一个开关方便对比串行和并发的效果。比如爬虫作业里import asyncio, requests SYNC True def crawl_sync(urls): results [] for url in urls: results.append(requests.get(url, timeout5).status_code) return results async def fetch_one(url): return requests.get(url, timeout5).status_code async def crawl_async(urls): tasks [fetch_one(url) for url in urls] return await asyncio.gather(*tasks) urls [https://example.com] * 10 if SYNC: print(crawl_sync(urls)) else: print(asyncio.run(crawl_async(urls)))这样写的好处是作业报告里可以直接展示两种方式的耗时对比结论一目了然老师也愿意给高分。7. 报错排查实录这几类错误在作业里出现频率最高7.1 “python was not found”的背后我在前面提到过很多同学双击安装包时装完就关根本没注意到Add Python to PATH被默认取消勾选。常见表现就是命令行里输入python没有任何输出或者直接弹出微软商店页面。解决方法有两种。一是重装Python在安装向导第一步勾选“Add Python to PATH”二是不重装手动把Python安装目录比如C:\Users\你的用户名\AppData\Local\Programs\Python\Python311\和它的Scripts子目录加入系统环境变量。加入后新开的终端窗口才会生效已经打开的终端需要重开。排查这个问题的顺序我建议先运行where python如果显示了路径说明Python其实装好了只是python命令的入口不对如果没显示再去检查环境变量。7.2 ModuleNotFoundError不止是没装库ModuleNotFoundError: No module named requests这类错误第一反应通常是pip install requests。但有时候装完还是报错原因往往是环境错位。最典型的场景在VSCode里运行脚本时右下角选中的解释器和命令行里pip对应的解释器不是同一个。命令行里pip install requests装进了A环境的库里但VSCode用的却是B环境的解释器自然找不到模块。解决方案是和前面说的一样先创建并激活虚拟环境然后在VSCode里选中同一个环境。还有一种容易被忽视的情况自己的脚本文件名刚好和第三方库重名。比如把文件命名为requests.py再在另一个脚本里import requestsPython会优先导入当前目录下的同名模块于是你看到的是自己那个完全没用的空文件然后各种AttributeError就来了。这是很多新手百思不得其解的“灵异事件”根源。7.3 UnicodeDecodeError与文件编码读取本地文件时常常遇到UnicodeDecodeError: gbk codec cant decode byte 0x...。Windows下Python默认用GBK编码打开文本文件而很多数据文件是UTF-8编码两边对不上就会报错。最简单稳妥的方法是打开文件时显式指定编码with open(data.json, r, encodingutf-8) as f: data f.read()如果你不确定源文件是什么编码可以用chardet自动检测with open(data.json, rb) as f: raw f.read() encoding chardet.detect(raw)[encoding]这类问题在数据分析作业里特别常见因为CSV文件可能是从Excel另存的也可能是别人在Linux上生成的。养成写文件时都显式传encoding参数的习惯能省掉大量调编码的时间。7.4 路径中反斜杠的坑Windows文件路径习惯用反斜杠比如C:\Users\name\data.csv。但在Python字符串里\U、\n、\t都是转义字符直接写很容易被解释成别的东西。# 错误写法 path C:\Users\name\data.csv # 正确写法1原始字符串 path rC:\Users\name\data.csv # 正确写法2正斜杠 path C:/Users/name/data.csv我推荐优先使用正斜杠写法因为它在Windows和Linux下都能正常工作代码换到服务器上跑不用改。这种小细节不算难但在作业多人协作或跨平台运行场景下非常实用。最后分享一点体会做完“python 作业4”这个阶段的项目我最深的感受是编程作业和真正的项目开发差的不是语法量而是“把大任务拆成小模块”的意识和排查问题的系统性。环境出问题先看解释器和PATH报错先看最后一行和回溯栈里的具体行号。拿到一个综合作业别急着写代码先在纸上画出数据流输入是什么、中间经过哪些处理、输出什么。拆清楚了代码的量感就没那么吓人了。另外一个特别推荐的习惯是每个算法或模块写完顺手写几个assert断言来验证关键函数assert knapsack(10, [2, 3, 4], [3, 4, 5]) 9 assert min_coins([1, 2, 5], 11) 3不要小看这几行测试它能让你在改代码时立刻发现“回归”否则很多时候你以为修好了A其实弄坏了B。作业本身只是一个阶段性的检验真正有价值的是过程中建立的这套调试思路和代码习惯后续做爬虫、数据分析、算法项目时都会持续复用。
返回列表