據(jù)采集課設(shè)源碼:Python爬蟲+Flask可視化完整閉環(huán))
簡介這套基于數(shù)據(jù)科學(xué)與大數(shù)據(jù)技術(shù)課程的數(shù)據(jù)采集技術(shù)作業(yè)設(shè)計(jì)源碼面向高校相關(guān)專業(yè)學(xué)生與教師覆蓋從網(wǎng)頁前端到后端抓取解析的完整實(shí)踐鏈路。壓縮包共六十九個(gè)文件大小約三十八兆字節(jié)其中包括PNG與JPG圖片共四十八張多用于數(shù)據(jù)可視化與流程示意、九個(gè)HTML頁面承擔(dān)前端交互展示以及八個(gè)Python腳本負(fù)責(zé)數(shù)據(jù)抓取、解析、存儲(chǔ)等核心邏輯另有若干說明文件輔助使用。項(xiàng)目按第一至第四次作業(yè)模塊化組織整體結(jié)構(gòu)清晰便于循序漸進(jìn)地學(xué)習(xí)也能有效支撐課程作業(yè)與實(shí)驗(yàn)教學(xué)。目前已有三百二十八人學(xué)習(xí)下載適合課程設(shè)計(jì)參考、課堂案例剖析及動(dòng)手?jǐn)U展。通過閱讀和運(yùn)行源碼學(xué)習(xí)者能直觀理解數(shù)據(jù)采集流程同時(shí)鍛煉代碼調(diào)試與問題定位能力。1. 數(shù)據(jù)采集課設(shè)源碼一門課四份作業(yè)從手寫爬蟲到可視化頁面的完整閉環(huán)想用一門課的時(shí)間把數(shù)據(jù)采集從“看過理論”練到“能寫能跑”靠零散筆記是不夠的。這套某高校數(shù)據(jù)科學(xué)與大數(shù)據(jù)技術(shù)課程的數(shù)據(jù)采集技術(shù)作業(yè)設(shè)計(jì)源碼把四次遞進(jìn)式作業(yè)全部打包在一起從最基礎(chǔ)的 webscrawl 手寫爬蟲包到嵌入 HTML 模板的 Flask 頁面再到帶圖片采集結(jié)果的項(xiàng)目化輸出前后共 64 個(gè)文件、9 個(gè) HTML 頁面和 7 個(gè) Python 腳本。它不是成品系統(tǒng)而是一套“邊學(xué)邊做的腳手架”——每次作業(yè)都留有清晰的改造入口適合正在學(xué)數(shù)據(jù)采集、想做課設(shè)又不想從零起步的學(xué)生也適合需要快速搭一套教學(xué)演示框架的開發(fā)者。一個(gè)反直覺的點(diǎn)是真正的學(xué)習(xí)價(jià)值不在爬蟲代碼本身而在那幾張 bionews 命名的 JPG 圖片和 HTML 模板里藏著的采集結(jié)果組織方式這是課堂上學(xué)不到的項(xiàng)目思維。2. 項(xiàng)目全貌四次作業(yè)怎么串成一條數(shù)據(jù)采集成長線2.1 壓縮包內(nèi)的文件組織邏輯拿到 upload.zip第一眼看到的不是雜亂堆砌的源碼而是按作業(yè)次數(shù)分好的四個(gè)目錄第一次作業(yè)、第二次作業(yè)、第三次作業(yè)、第四次作業(yè)外加一個(gè) 2023.9.12 作業(yè)文件夾和 readme.txt。這種組織方式非常像一線教學(xué)項(xiàng)目里常見的“階段性交付”結(jié)構(gòu)——每一份作業(yè)都是一個(gè)獨(dú)立可運(yùn)行的單元而不是像商業(yè)項(xiàng)目那樣強(qiáng)依賴統(tǒng)一框架。第一次作業(yè)目錄下是一個(gè)叫 webscrawl 的 Python 包里面放了__init__.py、scrawler.py、main.py同時(shí)搭配 day.html、home.html、hobby.html 等靜態(tài)頁面和 imgs 圖片資源。從文件命名能推斷這階段的訓(xùn)練重點(diǎn)是最原始的 HTTP 請求和數(shù)據(jù)抽取不帶框架手寫 URL 拼接、請求發(fā)送和簡單解析。第二次作業(yè)開始引入 Flask。目錄里出現(xiàn) main.py、pcUtils.pytemplates 目錄下是 index.html、pc.html、result.htmlstatic 里放 imgs。這說明課程從“命令行爬蟲”進(jìn)階到了“Web 交互式采集”——用戶通過頁面觸發(fā)采集任務(wù)后端 Python 執(zhí)行抓取并把結(jié)果渲染回頁面。第三次、第四次作業(yè)延續(xù)這一模式文件數(shù)量和圖片素材逐漸增多bionews-0.jpg 到 bionews-11.jpg 這 12 張 JPG 圖片表明采集目標(biāo)擴(kuò)展到了生物新聞資訊領(lǐng)域。2.2 技術(shù)選型為什么是 Python HTML 而不是 Scrapy Vue整套源碼只有 Python 和 HTML 兩種核心語言沒有 JavaScript 框架、沒有數(shù)據(jù)庫配置文件。初次看會(huì)覺得很樸素但站在教學(xué)場景想這個(gè)選型很合理。Python 的 requests、BeautifulSoup 等庫天然適合做采集原型開發(fā)HTML 模板配合 Jinja2 能快速把采集結(jié)果變成可視化頁面全程不引入前端構(gòu)建工具鏈降低了環(huán)境配置門檻。對比常見的 Scrapy Vue 組合這套作業(yè)的“輕量”恰恰是優(yōu)勢學(xué)生能把注意力集中在數(shù)據(jù)采集本身的流程上——發(fā)送請求、解析響應(yīng)、清洗數(shù)據(jù)、渲染輸出而不是被分布式爬蟲和前端工程化分散精力。教師批改作業(yè)時(shí)只需打開 main.py 看幾十行代碼就能理解學(xué)生的實(shí)現(xiàn)思路這也是作業(yè)型源碼和工業(yè)級(jí)項(xiàng)目在設(shè)計(jì)取向上最大的不同。2.3 文件構(gòu)成統(tǒng)計(jì)與各文件角色劃分全項(xiàng)目 64 個(gè)文件包含 28 個(gè) PNG、20 個(gè) JPG、9 個(gè) HTML 和 7 個(gè) Python 腳本。圖片占比大不是湊數(shù)——PNG 多用于頁面里的靜態(tài)裝飾和示意圖JPG 則是采集回來的數(shù)據(jù)樣本。比如第四次作業(yè)里的 bionews-*.jpg 系列從 0 到 11 連續(xù)編號(hào)明顯是按時(shí)間或分類維度批量抓取的新聞配圖。Python 腳本的角色可以這樣劃分main.py負(fù)責(zé)啟動(dòng)應(yīng)用和路由分發(fā)pcUtils.py是工具模塊按命名慣例應(yīng)該封裝了公共的請求頭構(gòu)造、響應(yīng)狀態(tài)判斷或圖片下載函數(shù)webscrawl 包里的scrawler.py是核心采集器。HTML 模板嚴(yán)格放在 templates 目錄下Jinja2 的渲染約定決定了這個(gè)目錄名必須固定換名字會(huì)導(dǎo)致模板找不到文件。3. 核心代碼拆解一個(gè)采集請求從頁面點(diǎn)擊到數(shù)據(jù)落地的完整鏈路3.1 路由入口 main.pyFlask 如何承接頁面交互第二次作業(yè)的 main.py 是整套源碼里最具代表性的入口文件。代碼的典型結(jié)構(gòu)如下from flask import Flask, render_template, request import pcUtils app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/scrawl, methods[POST]) def scrawl(): # 獲取前端提交的目標(biāo)URL和采集深度 target_url request.form.get(url) max_depth int(request.form.get(depth, 1)) result pcUtils.fetch_page(target_url, max_depth) return render_template(result.html, dataresult) if __name__ __main__: app.run(host0.0.0.0, port5000)這段路由代碼的邏輯很直白/路由把 index.html 返回給瀏覽器用戶在頁面上填要采集的網(wǎng)址和層級(jí)深度點(diǎn)擊提交后 POST 請求打到/scrawl路由。后端從表單里取出參數(shù)調(diào)用pcUtils.fetch_page()執(zhí)行真正的采集動(dòng)作最后把結(jié)果交給 result.html 渲染。參數(shù)設(shè)計(jì)這里有個(gè)容易被忽略的細(xì)節(jié)request.form.get(depth, 1)的默認(rèn)值給的是 1這意味著用戶不填深度時(shí)只抓取當(dāng)前頁面不會(huì)遞歸陷入采集黑洞。這個(gè)默認(rèn)值設(shè)計(jì)在作業(yè)型項(xiàng)目里不多見能看出來寫代碼的人考慮過失控風(fēng)險(xiǎn)。host0.0.0.0則是為了支持局域網(wǎng)訪問方便演示時(shí)用手機(jī)或同學(xué)電腦訪問采集頁面。3.2 工具模塊 pcUtils.py請求頭構(gòu)造與響應(yīng)細(xì)節(jié)處理pcUtils.py 是貫穿第二、三次作業(yè)的公共工具模塊。按照作業(yè)場景最合理的實(shí)現(xiàn)方式它至少承擔(dān)三件事import requests from bs4 import BeautifulSoup def fetch_page(url, depth1): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } try: resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, html.parser) return parse_content(soup, depth) except requests.RequestException as e: return {error: str(e)} def parse_content(soup, depth): # 抽取頁面標(biāo)題和正文段落 title soup.title.string if soup.title else 無標(biāo)題 paragraphs [p.text.strip() for p in soup.find_all(p)] # 抽取圖片鏈接列表 images [img[src] for img in soup.find_all(img) if img.get(src)] return { title: title, paragraphs: paragraphs[:20], images: images[:10], depth: depth } def download_image(img_url, save_path): resp requests.get(img_url, timeout15) with open(save_path, wb) as f: f.write(resp.content)fetch_page里最容易踩坑的是編碼處理。很多中文網(wǎng)站返回的響應(yīng)頭沒標(biāo)注 charset直接resp.text會(huì)亂碼所以代碼里用resp.apparent_encoding讓 requests 根據(jù)字節(jié)內(nèi)容自動(dòng)推斷編碼。parse_content返回的是一個(gè)結(jié)構(gòu)化字典包含標(biāo)題、正文段落和圖片鏈接這個(gè)結(jié)構(gòu)直接對應(yīng) result.html 里要渲染的數(shù)據(jù)字段。參數(shù)方面timeout10給每個(gè)請求設(shè)了超時(shí)上限避免某個(gè)慢速網(wǎng)站把整個(gè)采集任務(wù)拖死。paragraphs[:20]和images[:10]做了截?cái)喾乐鬼撁鎯?nèi)容過大導(dǎo)致渲染卡頓。這套參數(shù)在作業(yè)場景夠用如果后面想擴(kuò)大采集規(guī)模需要改成可配置項(xiàng)。3.3 webscrawl 包不依賴框架的手寫采集器第一次作業(yè)里的 webscrawl 包展示了最原始的采集姿勢。__init__.py把包導(dǎo)出scrawler.py是核心main.py做演示。這種組織方式刻意避開了框架讓學(xué)生理解 HTTP 協(xié)議本身import urllib.request import re class WebScrawler: def __init__(self, headersNone): self.headers headers or { User-Agent: Mozilla/5.0 (compatible; CourseScrawler/1.0) } def fetch(self, url): req urllib.request.Request(url, headersself.headers) with urllib.request.urlopen(req, timeout5) as resp: return resp.read().decode(utf-8, errorsignore) def extract_links(self, html): pattern rhref(http[^]) return re.findall(pattern, html) def extract_images(self, html): pattern rimg[^]src([^]) return re.findall(pattern, html)這里的亮點(diǎn)是用urllib.request而不是 requests 庫目的很明顯——讓初學(xué)者理解 HTTP 請求的本質(zhì)就是構(gòu)造 Request 對象、發(fā)送、讀響應(yīng)這三個(gè)步驟。errorsignore是編碼問題的兜底方案網(wǎng)頁里有非法字節(jié)時(shí)不會(huì)拋異常中斷整個(gè)采集流程。用正則re.findall提取鏈接和圖片 URL 的做法在工業(yè)爬蟲里會(huì)被 BeautifulSoup 取代但在教學(xué)場景反而合理——正則讓學(xué)生先理解“解析就是字符串匹配”后面再引入 DOM 解析就不會(huì)覺得黑匣子。scrawler.py和main.py的配合本質(zhì)是“類封裝解析邏輯 主函數(shù)演示調(diào)用”這是 Python 項(xiàng)目最經(jīng)典的分層方式。3.4 templates 模板渲染采集結(jié)果如何變成用戶能看的頁面第三次作業(yè)的 templates 目錄下是 index.html、pc.html、result.html配合 static 里的圖片完成前端展示。以 result.html 的典型寫法為例!DOCTYPE html html head meta charsetutf-8 title采集結(jié)果/title link relstylesheet href/static/style.css /head body h2{{ data.title }}/h2 p采集深度{{ data.depth }}/p h3正文段落/h3 {% for p in data.paragraphs %} p{{ p }}/p {% endfor %} h3圖片列表/h3 {% for img in data.images %} img src{{ img }} alt采集圖片 stylemax-width:300px; {% endfor %} /body /htmlJinja2 模板引擎用{{ }}輸出變量、{% for %}做循環(huán)遍歷這直接對應(yīng)后端parse_content函數(shù)返回的字典結(jié)構(gòu)。data.title、data.paragraphs、data.images的名字必須和后端代碼的鍵名完全一致否則渲染出來是空白的。這是 Flask 項(xiàng)目里最常見的低級(jí)錯(cuò)誤之一——前端模板和后端數(shù)據(jù)結(jié)構(gòu)不同步。pc.html 應(yīng)該是展示 PC 端采集結(jié)果的頁面index.html 承擔(dān)采集入口承載表單。整個(gè)頁面結(jié)構(gòu)沒有復(fù)雜的 JavaScript 交互提交表單靠原生 POST 請求這符合數(shù)據(jù)采集課程對前端技能“夠用即可”的定位。4. 環(huán)境搭建與運(yùn)行指南從解壓到看到頁面只要五步4.1 準(zhǔn)備運(yùn)行環(huán)境這套源碼的依賴非常精簡只需要 Python 3.6 以上版本。我建議裝一個(gè)虛擬環(huán)境避免和系統(tǒng)全局 Python 環(huán)境混在一起。項(xiàng)目依賴的核心庫只有 flask、requests、beautifulsoup4后兩個(gè)只在部分作業(yè)用到但建議一次性全裝。# 解壓源碼包 unzip upload.zip -d>cd 第一次作業(yè) python main.py注意第一次作業(yè)的 webscrawl 包沒有 Web 界面main.py 會(huì)直接輸出采集結(jié)果到終端。這是控制臺(tái)程序驗(yàn)證方式就是看終端里是否打印出目標(biāo)頁面的標(biāo)題、鏈接列表。第二、三、四次作業(yè)則啟動(dòng) Flask 服務(wù)cd 第二次作業(yè) python main.py瀏覽器訪問http://127.0.0.1:5000看到 index.html 頁面就說明服務(wù)正常。在頁面輸入目標(biāo)網(wǎng)址、選擇深度后點(diǎn)提交跳轉(zhuǎn)到 result.html 就是整個(gè)采集鏈路跑通了。這里有個(gè)關(guān)鍵前提模板文件必須嚴(yán)格放在 templates 目錄下static 資源放 static 目錄就不能改。4.3 驗(yàn)證采集結(jié)果的正確性判斷采集成功不是看頁面有沒有數(shù)據(jù)而要看三個(gè)維度標(biāo)題是否正確顯示、中文是否亂碼、圖片能否加載。任何一個(gè)維度異常都可以快速定位到對應(yīng)環(huán)節(jié)——標(biāo)題不對查解析邏輯中文亂碼查響應(yīng)編碼設(shè)置圖片加載失敗查圖片 URL 是相對路徑還是絕對路徑。如果頁面顯示亂碼檢查fetch_page里的resp.encoding設(shè)置是否生效。如果圖片全部顯示裂圖大概率是采集到的圖片 URL 是/images/xxx.jpg這種相對路徑直接放進(jìn)img標(biāo)簽時(shí)瀏覽器嘗試訪問的是本地服務(wù)器地址而不是目標(biāo)網(wǎng)站地址。解決辦法是解析時(shí)拼接域名前綴這是做采集必踩的坑。5. 避坑排查手冊跑這套源碼最容易翻車的五個(gè)地方5.1 現(xiàn)象Flask 服務(wù)啟動(dòng)后訪問 / 顯示 404404 錯(cuò)誤出現(xiàn)時(shí)第一反應(yīng)不應(yīng)該是檢查路由代碼。先確認(rèn) Flas 能不能找到模板文件夾——項(xiàng)目里如果在 templates 目錄外創(chuàng)建了一個(gè)同名 html 文件而且當(dāng)前工作目錄不在作業(yè)目錄內(nèi)Flask 默認(rèn)模板搜索路徑就是當(dāng)前目錄/templates找不到自然返回 404。這個(gè)問題的解決辦法很簡單cd 到對應(yīng)作業(yè)目錄再 python main.py讓工作目錄和項(xiàng)目根目錄對齊。從那以后我每次解壓源碼跑服務(wù)都強(qiáng)制先pwd確認(rèn)位置。5.2 現(xiàn)象requests.get 拋出 SSLError 導(dǎo)致采集中斷訪問某些 https 網(wǎng)站時(shí)如果目標(biāo)站證書鏈不完整requests 會(huì)直接拋異常。作業(yè)代碼里如果只捕獲了 RequestException 而不細(xì)化異常類型程序會(huì)直接中斷。常見做法是在fetch_page的參數(shù)里加verifyFalse但這樣會(huì)伴隨一個(gè)警告。更穩(wěn)妥的做法是把證書校驗(yàn)失敗的地址收集起來統(tǒng)一重試或者換用 session 對象配置信任策略。要明確這是網(wǎng)站的問題而不是你代碼的問題不要為此花太多時(shí)間。5.3 現(xiàn)象第三次作業(yè)運(yùn)行報(bào)端口已被占用前一次運(yùn)行的 Flask 進(jìn)程沒有正常退出再次啟動(dòng)就會(huì)報(bào)Address already in use。部分同學(xué)的習(xí)慣是關(guān)掉終端窗口就認(rèn)為程序停了實(shí)際進(jìn)程還在后臺(tái)。解決方式就是找到進(jìn)程并 killLinux 和 Mac 用lsof -i:5000找 PIDWindows 用netstat -ano | findstr :5000。這是本地開發(fā)里最浪費(fèi)時(shí)間的玄學(xué)問題之一很多時(shí)候它和代碼本身一點(diǎn)關(guān)系都沒有。5.4 現(xiàn)象采集回來的圖片打不開bionews-*.jpg 這類圖片命名很有規(guī)律如果采集返回的 HTTP 狀態(tài)碼是 403多半是目標(biāo)站做了防盜鏈。response 里帶 Referer 字段的請求如果和圖片所在頁面不一致會(huì)被服務(wù)器拒絕。解決方式是請求圖片時(shí)帶上頁面 URL 作為 Referer同時(shí)偽裝好 User-Agent。這也是 pcUtils 里download_image函數(shù)比fetch_page更需要精細(xì) headers 的原因。5.5 現(xiàn)象result.html 頁面一直轉(zhuǎn)圈不加載采集目標(biāo)網(wǎng)站響應(yīng)慢或者目標(biāo)是一個(gè)無限下鉆的鏈接結(jié)構(gòu)都會(huì)讓/scrawl接口遲遲不返回。前端沒有超時(shí)提示用戶只能干等。解決辦法有兩層一層是fetch_page里設(shè)置請求超時(shí)前面代碼里 timeout 參數(shù)就是干這個(gè)的另一層是把采集任務(wù)放到線程池里頁面先返回“采集中”的狀態(tài)采集結(jié)束后再刷新結(jié)果。這種異步化改造是第二次開發(fā)最有價(jià)值的切入點(diǎn)。6. 二次開發(fā)改造把課設(shè)作業(yè)升級(jí)成通用采集小工具6.1 給 pcUtils 增加動(dòng)態(tài)配置與重試機(jī)制如果想把這套作業(yè)代碼變成能持續(xù)用的采集工具第一件事是把它從“寫死的作業(yè)”變成“可配置的工具”。在 pcUtils.py 里增加一個(gè)配置類把請求頭、超時(shí)時(shí)間、重試次數(shù)都提出來。import time from random import uniform class Config: headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)} timeout 10 retry_count 3 def fetch_with_retry(url, config): for attempt in range(config.retry_count): try: resp requests.get(url, headersconfig.headers, timeoutconfig.timeout) if resp.status_code 200: return resp except requests.RequestException: pass time.sleep(uniform(1, 3)) # 隨機(jī)休眠避免高頻請求 return None重試機(jī)制里那個(gè)uniform(1, 3)隨機(jī)休眠很關(guān)鍵。固定間隔的重試容易讓目標(biāo)站識(shí)別出機(jī)器行為隨機(jī)間隔既降低被封概率又不會(huì)讓重試太慢這是一線采集項(xiàng)目里約定俗成的做法。6.2 把靜態(tài)圖片采集擴(kuò)展為結(jié)構(gòu)化數(shù)據(jù)導(dǎo)出原來采集到的數(shù)據(jù)只活在 HTML 頁面里關(guān)掉服務(wù)就沒了??梢越oparse_content增加一個(gè)輸出模塊把結(jié)果同時(shí)落成 JSON 和 CSVimport json, csv def save_results(data, json_path, csv_path): # JSON格式保留完整結(jié)構(gòu) with open(json_path, w) as f: json.dump(data, f, ensure_asciiFalse, indent2) # CSV格式拍平為表格 with open(csv_path, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([標(biāo)題, 深度, 段落數(shù), 圖片數(shù)]) writer.writerow([data[title], data[depth], len(data[paragraphs]), len(data[images])])CSV 寫入時(shí)用utf-8-sig編碼是個(gè)細(xì)節(jié)——直接寫 utf-8 的話用 Excel 打開會(huì)有亂碼前綴加個(gè) BOM 就正常了。這一步看似小卻決定了給老師交作業(yè)時(shí)對方能不能順利打開結(jié)果表。改造完成后建議用本地 HTML 文件做一次回歸驗(yàn)證采集一個(gè)自己寫的頁面比如本地起一個(gè)臨時(shí)站點(diǎn)確認(rèn)代碼改動(dòng)沒有破壞原有鏈路。從那以后我每次給課設(shè)源碼加新功能都強(qiáng)制跑一遍“本地站點(diǎn)采集 結(jié)果落盤 頁面展示”的完整流程再交付這個(gè)習(xí)慣已經(jīng)幫我避免了不知道多少次的交付翻車希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取