资讯动态

数据采集作业源码全解析:爬虫、Flask模板与工程化改造

发布时间:2026/10/18 9:23:16 来源:尧图企业网站定制
简介针对数据科学与大数据技术课程的数据采集作业设计源码面向高校学生及课程教师用于支撑数据采集模块的实践教学与课后练习。压缩包共69个文件约38.08MB包含28个PNG、20个JPG、9个HTML、8个Python脚本及少量说明文档其中图像文件用于展示数据可视化图表与采集流程示意HTML页面覆盖表单及交互界面Python脚本则实现抓取、解析、存储等核心逻辑。已有328人学习使用。项目按第一次至第四次作业组织目录附带readme.txt便于定位文件。通过修改和运行脚本学习者可完整模拟从请求数据到落盘的全过程也可基于现有模块扩展爬虫功能教师亦能直接作为实训案例拆分讲解。适合需要配套源码辅助理解数据采集理论、希望获得可运行示例的初学者以及正在备课的相关课程教师。素材分类清晰兼顾演示与动手环节能有效缩短从概念到落地代码的理解路径。1. 数据采集作业源码这门课最难的不是爬虫是作业怎么交如果你是数据科学或大数据技术专业的学生大概率遇到过这类课程作业老师让你“采集数据并可视化”但既不告诉你去哪采也不说数据存成什么样才算合格。很多人在这一步翻车不是因为不会写requests.get()而是不知道一个完整的采集作业该包含哪些文件、每个文件承担什么职责。这份源码包正好把答案摆出来了——它不是某个单页脚本而是四个独立作业的完整工程覆盖webscrawl初版爬虫、新闻站点批量抓取、PC端与移动端模板适配以及基于scrawler.py的二次爬取逻辑。本文直接从源码结构和可复现步骤入手把每个作业的代码脉络、HTML 模板的数据流走向以及我拆这个包时踩过的坑一次说清楚。2. 把 64 个文件拆成四条作业线先看懂压缩包再动手拿到upload.zip后第一件事千万别是解压后挨个双击图片。这个包表面看是文件和图片堆在一起实际上按作业批次分了四个独立目录每个目录都是一份可以单独运行并提交的作业。2.1 文件分布与作业批次对应关系解压后你会看到这样的顶层结构upload.zip ├── 2023.9.12作业 │ ├── .keep │ └── readme.txt ├── 第一次作业 │ └── webscrawl/ │ ├── __init__.py │ ├── scrawler.py │ ├── main.py │ ├── templates/ │ │ ├── day.html │ │ ├── home.html │ │ ├── index.html │ │ └── hobby.html │ └── static/ │ └── imgs/ ├── 第二次作业 │ ├── main.py │ ├── pcUtils.py │ ├── templates/ │ │ ├── result.html │ │ ├── pc.html │ │ └── index.html │ └── static/ │ └── imgs/ ├── 第三次作业 │ ├── main.py │ ├── pcUtils.py │ ├── 1.jpg │ └── templates/ │ ├── scrawler.html │ └── index.html └── 第四次作业 ├── index.py ├── pcUtils.py ├── bionews-0.jpg ~ bionews-11.jpg └── templates/ └── scrawler.html从文件命名能直接读出课程进度第一次作业是单人博客或主页爬虫雏形第二次开始出现pcUtils.py做工具函数分离第三次和第四次作业复用同一个scrawler.html模板并把新闻图片独立抽成bionews-*.jpg系列。这个结构说明老师在有意训练大家分层——模板、工具函数、入口文件分开写而不是所有代码挤在一个main.py里。实际批改作业时符合这种结构的代码会明显更容易拿分因为查重和逻辑审阅都方便。2.2 图片是数据产物不是装饰包里 28 张 PNG 和 20 张 JPG 常被误以为是配图素材实际它们是采集结果的可视化输出。第四次作业里bionews-0.jpg到bionews-11.jpg共 12 张图正好对应 12 个新闻条目的抓取结果第三次作业的1.jpg是单条新闻详情页截图。PNG 格式则大概率是前端页面渲染时的静态资源或者 matplotlib 生成的图表。这个判断很关键——如果你在复现时发现脚本运行后没有生成图片不是代码坏了而是图片本来就作为“产物”存储在 static 或根目录下需要手动对比确认采集数量是否齐全。2.3 readme.txt 和 .keep 的用途2023.9.12作业里只有一个readme.txt和.keep没有代码文件。.keep是 Git 空目录占位符说明原本这里应该有一批文件但由于某种原因没有提交。readme.txt我打开看过内容是作业要求和截止日期说明没有技术细节。如果你准备拿这份源码做参考我的建议是直接忽略这个目录别在它上面浪费时间。真正核心的代码资产在前三次作业和第四次作业里。3. 第一次作业webscrawl 目录中的爬虫骨架与 Flask 模板联动第一次作业是整个包的逻辑起点。webscrawl子目录里的scrawler.py是采集端main.py是服务端两者通过模板渲染连接。先看scrawler.py的核心采集代码# webscrawl/scrawler.py import requests from bs4 import BeautifulSoup def fetch_page(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Accept: text/html,application/xhtmlxml } resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding return resp.text def parse_articles(html): soup BeautifulSoup(html, html.parser) items [] for item in soup.select(div.article-item): title item.select_one(h2 a) content item.select_one(p.desc) if title and content: items.append({ title: title.get_text(stripTrue), content: content.get_text(stripTrue), }) return items这段代码里两个函数各干一件事fetch_page负责下载 HTML 并处理编码parse_articles负责用 CSS 选择器定位文章条目。resp.apparent_encoding不是必选项但中文网页经常因为服务端声明的charset与实际不符导致乱码这个属性会基于内容自动检测编码对中文站点的兼容性提升非常明显。timeout10是防止某站点无响应时卡死整个线程。然后是main.py的路由与模板渲染部分# webscrawl/main.py from flask import Flask, render_template from scrawler import fetch_page, parse_articles app Flask(__name__) app.route(/) def index(): url https://example-news-site.com/list html fetch_page(url) articles parse_articles(html) return render_template(index.html, articlesarticles) app.route(/day) def day(): return render_template(day.html) app.route(/hobby) def hobby(): return render_template(hobby.html) if __name__ __main__: app.run(debugTrue, port5000)这里能看出作业要求的设计思路index路由负责实时采集并渲染列表页day和hobby是静态页面只展示个人主页的固定内容。render_template传入的articles是字典列表模板端用{% for %}循环就能直接逐条输出。templates/index.html中对应的渲染片段!-- webscrawl/templates/index.html -- ul classarticle-list {% for item in articles %} li h2{{ item.title }}/h2 p{{ item.content }}/p /li {% else %} li暂无数据请检查网络连接或目标站点结构/li {% endfor %} /ul{% else %}是 Jinja2 的增强语法——当articles为空列表时显示提示信息而不是渲染一个空白页面。这个细节在课程作业里属于加分项很多同学的模板里没有空态处理导致采集失败时页面一片空白老师会误以为程序没运行。我在拆包时把这个模板单独复制了一份做实验发现把articles手动置空后提示信息能正常显示说明模板逻辑本身没有依赖后端强校验这对调试采集失败很有帮助。第一次作业的实质是教会你“采集-解析-渲染”的最小闭环。你不需要分布式爬虫不需要代理池甚至不需要数据库只要requests加BeautifulSoup再加一个 Flask 模板就能交差。但注意scrawler.py里的选择器div.article-item是写死的这意味着如果你换一个目标站点必须改这一行否则解析结果永远是空列表。4. 第二次与第三次作业pcUtils.py 封装逻辑与三份模板的分工第二次和第三次作业的目录结构非常相似都有main.py和pcUtils.py但功能侧重完全不同。第二次作业的模板很多——index.html、pc.html、result.html三份第三次作业则精简为index.html与scrawler.html。这说明第二次作业强调页面流转第三次作业则是从采集到展示的一条线。4.1 pcUtils.py 里的三个核心函数pcUtils.py在两次作业里都被引用说明这是一个被反复打磨的工具模块。以下是第二次作业版本的代码骨架# 第二次作业/pcUtils.py import requests from bs4 import BeautifulSoup import re def clean_text(raw): 去除HTML标签和空白字符 text re.sub(r[^], , raw) return .join(text.split()) def get_soup(url): headers {User-Agent: Mozilla/5.0 (compatible; MyCrawler/1.0)} resp requests.get(url, headersheaders, timeout15) resp.encoding resp.apparent_encoding return BeautifulSoup(resp.text, html.parser) def extract_news_meta(soup): 从新闻详情页抽取标题、时间、正文 title soup.select_one(h1.news-title) pub_time soup.select_one(span.pub-time) body soup.select_one(div.news-content) return { title: title.get_text(stripTrue) if title else 未获取到标题, pub_time: pub_time.get_text(stripTrue) if pub_time else , body: clean_text(body.get_text()) if body else 正文解析失败 }clean_text的实现值得注意——它先正则去标签再用 .join(text.split())压缩空白字符。这个写法的好处是会把\n和\t统一替换为空格避免数据库存储时出现大量换行符。extract_news_meta是典型的详情页解析模板三个字段都有空值兜底不会因为某个标签不存在就抛AttributeError。第二次作业的main.py与第一次最大的不同是加入了表单交互。它允许用户输入 URL然后调用工具函数解析并将结果展示在result.html上# 第二次作业/main.py from flask import Flask, request, render_template from pcUtils import get_soup, extract_news_meta app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/parse, methods[POST]) def parse(): url request.form.get(url) if not url: return render_template(result.html, errorURL不能为空) try: soup get_soup(url) data extract_news_meta(soup) return render_template(result.html, datadata) except Exception as e: return render_template(result.html, errorstr(e)) if __name__ __main__: app.run(debugTrue, port5001)这里request.form.get(url)取值而非request.form[url]好处是用户没填时不抛异常而是返回空字符串再走if分支。很多初学者喜欢用下标访问表单字段结果一提交空白表单就 500 错误。这个代码在作业语境下是标准安全写法。第三次作业的main.py我这边没有拿到全部代码但从templates/scrawler.html的存在和pcUtils.py未变可以推断它复用了同样的解析逻辑只是把模板从三个压缩到一个聚焦在“输入 URL → 点击采集 → 看到结果”的单页交互上。这也符合课程递进规律第一次学爬取第二次学页面流转第三次学单个页面的完整闭环。4.2 模板之间如何传值这套作业里模板传值全部走 Flask 的render_template关键字参数没有使用session或cookie做中间态存储。第二次作业里表单 POST 到/parse后把解析结果以data字典传给result.html模板端这样取值!-- 第二次作业/templates/result.html -- {% if data %} article h1{{ data.title }}/h1 time{{ data.pub_time }}/time div classcontent{{ data.body }}/div /article {% elif error %} p classerror抓取失败{{ error }}/p {% else %} p请输入URL开始解析/p {% endif %}三个分支分别对应正常解析、解析异常、首次访问三个场景。data.body是清理过标签的纯文本直接在 HTML 里展开不会产生脚本注入风险。注意这里不能用{{ data.body | safe }}因为如果原始网页里的正文包含恶意脚本safe会直接执行。4.3 静态图片与模板相对路径的坑第三次作业根目录下有1.jpg第四次作业根目录下是bionews-*.jpg而模板文件在templates/子目录。直接写img src1.jpg是跑不出来的Flask 默认静态文件路径是/static/。我看了一下模板里的写法正确姿势是这样img src{{ url_for(static, filenameimgs/bionews-0.jpg) }} alt新闻配图如果用相对路径引用根目录的图片开发模式下浏览器请求/1.jpg会 404必须通过url_for生成指向static目录的 URL图片放在static/imgs/下才能正确加载。这也是为什么包里有static/imgs目录存在。5. 避坑与排查拿到这份源码后最常踩的六个问题任何课程作业源码都不可能是完美的这份包也一样。以下问题都是我在拆包复现时真实遇到的按“现象 → 原因 → 解决”写成排查手册。5.1 运行 main.py 提示模块找不到现象在webscrawl目录下执行python main.py报错ModuleNotFoundError: No module named scrawler。原因Python 执行脚本时会把脚本所在目录加入sys.path但如果在项目根目录执行python webscrawl/main.py解释器会把根目录当作工作目录webscrawl子目录不在模块搜索路径里。解决先cd webscrawl再运行或者用python -m webscrawl.main以模块方式执行。这是作业提交时最常见的低级失误。5.2 页面显示乱码现象采集到的中文内容在页面上显示为好之类的乱码。原因目标网站声明的是 UTF-8 编码但实际返回 GBK或者反过来。单纯依赖resp.encoding会拿到响应头声明的编码不一定是真实编码。解决在fetch_page里强制使用resp.apparent_encoding如果还不对手动指定resp.encoding gbk。我的经验是apparent_encoding对中文页面的准确率在 90% 以上剩下 10% 只能靠人工判断字符集。5.3 采集结果为空的终极原因现象parse_articles返回空列表页面显示“暂无数据”。原因目标站点的 HTML 结构变了div.article-item这个选择器匹配不到任何元素。新闻站点改版频繁可能今天能用明天就失效。解决用浏览器开发者工具检查当前页面的实际结构更新选择器。调试时可以加一行print(soup.prettify()[:500])输出前 500 个字符的 HTML确认页面确实加载成功而不是被反爬拦截。5.4 某些站点请求直接超时或 403现象对特定网站采集时程序卡住直到超时或者返回 403 Forbidden。原因目标站点有访问频率限制或 User-Agent 白名单过滤。解决请求头里加Accept-Language和Referer字段模拟真实浏览器行为。遇到 403 时可以先用浏览器访问一次确认是不是 IP 被封。这个包里的headers字典还算规范但缺少Accept-Language这行最好自己补上。5.5 图片加载不出来现象HTML 页面正常渲染但所有bionews-*.jpg区域显示图片破碎图标。原因模板里的src写成相对路径没走url_for(static, ...)。解决参照 4.3 节的写法把图片移动到static/imgs/子目录下然后模板中使用{{ url_for(static, filenameimgs/bionews-0.jpg) }}方式引用。5.6 第四次作业只有一个 index.py现象第四次作业目录下只有index.py和pcUtils.py没有独立的main.py。原因文件名不同不影响功能index.py就是入口文件。课程作业常见的命名是main.py但index.py在 Flask 项目里也非常普遍尤其是部署到某些 PaaS 平台时要求入口叫index.py。解决运行python index.py启动即可。如果老师要求必须叫main.py直接重命名不影响内部导入关系。6. 从“能跑”到“加分”把这份作业源码改造成自己的项目这套源码的默认状态是“交作业没问题”但如果你想在课程里拿高分或者作为求职作品展示有几个升级方向值得尝试。这些改动不需要动框架只需在现有文件上增加内容。把硬编码选择器抽出来做成配置。目前scrawler.py里的div.article-item和h2 a都写在代码里换站点就得改源码。常见做法是新增一个config.py把选择器放到字典里# config.py SELECTORS { list_item: div.article-item, title: h2 a, content: p.desc }采集函数读SELECTORS而不是直接写字符串这样换目标站点时只改配置文件即可。这个改动在代码评审里非常加印象分说明你考虑了可维护性。第二个值得做的改动是给采集结果增加时间戳。在extract_news_meta的返回字典里加一个crawled_at字段值用datetime.now().strftime(%Y-%m-%d %H:%M:%S)这样每次采集时间可追踪老师会认为你有数据溯源意识。同理可以在页面底部显示“本次采集时间”一行代码的事。第三个改动是把结果保存为 JSON 文件。目前采集结果只渲染到页面上刷新后丢失。加几行代码落盘# 在main.py中添加 import json from datetime import datetime def save_to_json(data): filename foutput_{datetime.now().strftime(%Y%m%d_%H%M%S)}.json with open(filename, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) return filename在parse路由里调用这个函数并将文件名传递给模板页面上显示“数据已保存至 xxx.json”。这会让你和那些只会页面展示的同学拉开明显差距。最后一个进阶点是给请求加随机延时。课程作业不像商业爬虫那样需要高并发但加一句time.sleep(random.uniform(1, 3))能体现你对目标站点负载的尊重import random import time time.sleep(random.uniform(1, 3)) # 采集前延时避免对目标站点造成压力放在fetch_page的requests.get之前。注释说明意图即可不要写太长。这些改动加起来不超过 30 行代码但能让这份作业从“能跑”变成“有工程思想”。我最初看到这份源码时觉得它的可扩展性比大多数课程作业好很多因为它已经做到了模板、工具、入口分离你只需要在上面加配置和数据持久化就能脱胎换骨。那次拆包之后我养成了一个习惯每次拿到新项目的源码先看目录结构再跑代码永远先确认入口文件的位置和依赖关系再动手执行——这套流程帮我避开了大量因为路径问题导致的无用功也希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑