題的入門實戰(zhàn)指南)
簡介一份以實戰(zhàn)為導(dǎo)向的Python爬蟲入門資源面向具備基本編程基礎(chǔ)、希望快速掌握網(wǎng)絡(luò)數(shù)據(jù)抓取的初學(xué)者可解決從零編寫簡單爬蟲的常見困惑。實例以requests庫發(fā)送HTTP請求、BeautifulSoup解析HTML文檔完整演示從網(wǎng)頁中提取標(biāo)題的流程涵蓋GET請求發(fā)送、狀態(tài)碼判斷、文檔解析、標(biāo)簽定位等基礎(chǔ)步驟并延伸介紹了CSS選擇器與異常處理思路。資源為單個docx文檔體積僅14KB內(nèi)容緊湊無冗余便于離線閱讀隨堂實踐。文檔中提供了可復(fù)制的完整代碼包括依賴庫安裝命令、請求失敗時的狀態(tài)碼提示、遍歷并打印標(biāo)題文本的具體寫法并建議讀者自行替換URL進行驗證同時通過示例強調(diào)實際操作需遵守robots.txt規(guī)則、控制訪問頻率避免對目標(biāo)網(wǎng)站造成過大負(fù)載幫助讀者建立規(guī)范的爬蟲開發(fā)習(xí)慣。目前已有133人瀏覽學(xué)習(xí)適合作為爬蟲入門的第一份手邊參考也適合作為教學(xué)輔助材料快速上手。1. 從Requests到BeautifulSoup一次只抓的Web Scraping入門不少人學(xué)爬蟲第一反應(yīng)是找復(fù)雜的框架、上分布式采集結(jié)果被環(huán)境配置勸退。我的建議正好相反先用 requests 發(fā)一個 HTTP GET 請求再用 BeautifulSoup 從返回的 HTML 里挑出全部標(biāo)簽把這條鏈路跑通再談其他。requests 負(fù)責(zé)把網(wǎng)頁內(nèi)容原樣搬回來BeautifulSoup 負(fù)責(zé)從這堆帶尖括號的文本里精準(zhǔn)切出標(biāo)題文字兩者分工明確加起來不到二十行代碼。這篇筆記適合有基礎(chǔ) Python 語法、想快速上手網(wǎng)絡(luò)數(shù)據(jù)采集的人代碼可以直接復(fù)制運行重點放在參數(shù)怎么調(diào)、失敗時看什么輸出。2. 搭建環(huán)境與第一個請求requests的選型理由與關(guān)鍵參數(shù)2.1 為什么是 requests 而不是 urllibPython 標(biāo)準(zhǔn)庫里其實自帶 urllib也能發(fā) HTTP 請求但處理 Cookie、重定向、請求頭拼接時要寫不少樣板代碼遇到需要維持會話的站點更是麻煩。requests 把這些底層細(xì)節(jié)統(tǒng)一封裝成 get、post、put 這類方法調(diào)用一個方法對應(yīng)一個動作代碼可讀性高很多。對于入門階段requests 幾乎是社區(qū)公認(rèn)的首選。另一個值得知道的點是requests 底層用的是 urllib3它把連接池復(fù)用、TLS 握手、自動解壓這些臟活全接管了。你看到 response requests.get(url) 這一行背后其實是 DNS 解析、TCP 連接、發(fā)送請求頭、接收響應(yīng)體的完整流程。理解這一點對排查問題很有用——比如狀態(tài)碼返回 200但 response.text 里是一段「訪問異?!固崾締栴}大概率不在 requests而在對方服務(wù)器識別出這不是瀏覽器發(fā)出的請求。2.2 安裝與最小請求三步驗證網(wǎng)絡(luò)通不通安裝很簡單在終端里執(zhí)行下面這行命令即可pip install requests beautifulsoup4我習(xí)慣先在虛擬環(huán)境里操作避免把全局 Python 環(huán)境弄亂。裝好后別急著寫完整爬蟲先跑一個最小請求驗證網(wǎng)絡(luò)鏈路通不通import requests url http://example.com response requests.get(url, timeout10) print(response.status_code) print(response.text[:200])這段代碼的作用是發(fā)送 GET 請求并打印狀態(tài)碼和響應(yīng)體前 200 個字符先確認(rèn)目標(biāo)網(wǎng)站真的能訪問。status_code 等于 200 說明請求成功如果打印出 403 或 406說明服務(wù)器拒絕了這次請求后面需要加請求頭偽裝瀏覽器。先把這一步跑通再往下做解析能省很多排查時間。2.3 響應(yīng)對象的關(guān)鍵成員拿數(shù)據(jù)前先讀懂它requests.get() 返回的是一個 Response 對象里面有幾個成員在爬蟲里幾乎天天要用。response.status_code 是 HTTP 狀態(tài)碼response.text 是響應(yīng)體按推測編碼解碼后的字符串中文網(wǎng)站偶爾會解碼錯誤需要手動指定 encodingresponse.content 是原始字節(jié)下載圖片或二進制文件時用它response.headers 里能看到 Content-Type、Server 字段判斷返回內(nèi)容到底是什么類型。params {page: 1, limit: 20} response requests.get(url, paramsparams, timeout(3, 10)) print(response.url)params 參數(shù)會自動把字典拼成查詢串追加到 URL 后面比如 ?page1limit20這樣不用手動拼接字符串。timeout 推薦傳元組 (3, 10)前一個數(shù)是連接超時后一個數(shù)是讀取超時單位都是秒。只傳一個數(shù)的話連接和讀取共用同一個值如果服務(wù)器一直不返回數(shù)據(jù)腳本可能長時間卡住這是新手常踩的坑。不同狀態(tài)碼在爬蟲場景里的含義也值得記一下狀態(tài)碼含義爬蟲里常見場景200請求成功正常返回頁面內(nèi)容301/302重定向requests 默認(rèn)自動跟隨403被拒絕缺少 UA 或觸發(fā)反爬策略404地址不存在鏈接失效或?qū)戝e路徑429請求過于頻繁觸發(fā)限流需要放慢速度500服務(wù)器內(nèi)部錯誤目標(biāo)站本身出問題重試也未必有用實際調(diào)試時優(yōu)先看 response.text 的開頭幾百字而不是只看狀態(tài)碼。很多反爬頁面為了穩(wěn)定會故意返回 200但內(nèi)容已經(jīng)換成了驗證提示。提示如果目標(biāo)頁面需要登錄才能訪問requests.get() 里還可以加 cookies 參數(shù)或者先用 requests.post() 提交表單獲取會話后續(xù)章節(jié)的 Session 對象會更適合這種場景。3. 解析HTML提取標(biāo)題find_all與CSS選擇器的搭配用法3.1 find_all 與 Tag 對象從h1標(biāo)簽開始提取BeautifulSoup 的核心作用是把 HTML 文本解析成一棵標(biāo)簽樹。soup.find_all(h1) 會返回一個列表列表里每個元素是一個 Tag 對象代表頁面上的一個標(biāo)簽。要從 Tag 里取出純文字用 get_text() 方法。from bs4 import BeautifulSoup html_text htmlbodyh1第一個標(biāo)題/h1h1第二個標(biāo)題/h1/body/html soup BeautifulSoup(html_text, html.parser) h1_tags soup.find_all(h1) for h1 in h1_tags: print(h1.get_text(stripTrue))這里我用了一段靜態(tài) HTML 字符串做演示效果和解析真實網(wǎng)頁響應(yīng)是一樣的。get_text(stripTrue) 會去掉標(biāo)題首尾的空白字符和換行避免打印出來帶著一大串空格。注意 strip 參數(shù)只清理首尾不會動標(biāo)題內(nèi)部的空格如果正文里有連續(xù)空格需要另做清洗。find_all 還可以加參數(shù)縮小范圍比如 find_all(h1, limit3) 只取前三個結(jié)果find_all(h1, class_title) 只查找 class 為 title 的 h1 標(biāo)簽。這里 class_ 后面必須帶下劃線因為 class 是 Python 的保留關(guān)鍵字直接寫 class 會報語法錯誤。我見過不少人在這一步被卡住報錯信息里明確提示了這一點。3.2 select() 與 CSS Selectors層級定位比循環(huán)查找更省事find_all 按標(biāo)簽名和屬性篩選時還夠用但遇到「某個區(qū)域下的某類元素」這種層級關(guān)系寫起來就啰嗦了。比如要抓 id 為 content 的 div 里面所有 h2用 find_all 要先定位 div 再在結(jié)果上繼續(xù) find_all代碼會多一層嵌套。soup.select() 方法接受 CSS Selectors 語法可以直接用一行表達(dá)式完成同樣的定位。# 選取 class 為 title 的所有 h1 soup.select(h1.title) # 選取 id 為 header 的容器內(nèi)部的 h1 soup.select(#header h1) # 選取帶 href 屬性的所有 a 標(biāo)簽 soup.select(a[href]) # 選取 class 為 content 的 div 下的直接子元素 p soup.select(div.content p)select() 返回的同樣是 Tag 對象列表取值方式和 find_all 沒有區(qū)別。實際項目里我通常兩個方法混用先用 select() 把范圍鎖定到某個區(qū)塊再在區(qū)塊內(nèi)用 find_all() 提取同類型的多個元素。CSS Selectors 的另一個好用的地方是屬性選擇器比如抓所有帶 download 屬性的鏈接一行就能搞定不用寫循環(huán)去判斷。3.3 完整腳本與三個邊界情況空列表、嵌套標(biāo)簽和重復(fù)標(biāo)題把前面的內(nèi)容串成一個完整腳本加上異常處理和編碼兜底import requests from bs4 import BeautifulSoup url http://example.com try: response requests.get(url, timeout10) response.raise_for_status() except requests.exceptions.RequestException as e: print(請求失敗, e) exit() if text/html not in response.headers.get(Content-Type, ): print(目標(biāo)不是 HTML 內(nèi)容拒絕解析) exit() response.encoding response.apparent_encoding soup BeautifulSoup(response.text, html.parser) h1_tags soup.select(h1) print(f共找到 {len(h1_tags)} 個 h1 標(biāo)題) for h1 in h1_tags: title h1.get_text(stripTrue) if title: print(title)raise_for_status() 會在狀態(tài)碼為 4xx 或 5xx 時主動拋異常這樣就不用每次手動判斷狀態(tài)碼。response.encoding 先賦成 apparent_encoding這是從頁面內(nèi)容里推測出的編碼比 requests 默認(rèn)按響應(yīng)頭猜測的方式更可靠。腳本最后額外判斷了 h1 是否為空文本避免頁面里存在空標(biāo)簽時打印一堆空白行。邊界情況要提前想清楚。第一頁面上可能一個 h1 都沒有返回空列表代碼不報錯但也沒有輸出這不代表爬蟲壞了而是頁面結(jié)構(gòu)問題。第二h1 內(nèi)部可能嵌套了 a 鏈接或 spanget_text() 會連同子標(biāo)簽的文字一起取出拿到的是拼接后的完整文本。第三一個頁面有多個 h1 時腳本會全部打印如果你只想要第一個改成 soup.select_one(h1) 即可。注意解析器我默認(rèn)用 html.parser它不需要額外安裝第三方庫。lxml 解析速度更快但要單獨裝編譯好的二進制包入門階段沒必要增加這個變量。4. 爬蟲避坑指南五個會把新手逼瘋的翻車現(xiàn)場這一章完全來自實戰(zhàn)中反復(fù)遇到的故障。每個問題都按「現(xiàn)象 → 原因 → 解決」展開照著排查能省下很多翻論壇的時間。4.1 狀態(tài)碼200但抓到的是提示頁現(xiàn)象requests.get() 返回的狀態(tài)碼是 200打印 response.text 卻發(fā)現(xiàn)里面不是目標(biāo)內(nèi)容而是「您的訪問異?!够蛞坏莉炞C頁面。原因這是反爬策略的常見手法。服務(wù)器判斷請求頭里的 User-Agent 不像瀏覽器就返回一個兜底頁面但狀態(tài)碼仍然保持 200方便前端正常渲染。只看狀態(tài)碼完全發(fā)現(xiàn)不了問題。解決先打印 response.text 的前 200 個字符看一眼實際內(nèi)容。然后給請求加上瀏覽器請求頭至少帶上 User-Agentheaders { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } response requests.get(url, headersheaders, timeout10)設(shè)置 UA 后大部分提示頁問題都能解決。如果還不行可能需要先訪問首頁拿到 Cookie 再請求目標(biāo)頁這時就該用下一章的 Session 對象了。4.2 中文標(biāo)題亂碼encoding 的黑匣子現(xiàn)象抓回來的英文和數(shù)字正常但中文標(biāo)題全部變成亂碼比如「?¥????」這種無法辨認(rèn)的字符。原因requests 在解碼響應(yīng)體時會根據(jù)響應(yīng)頭里的 charset 推斷編碼。有些網(wǎng)站響應(yīng)頭里沒寫 charsetrequests 會回退到一個默認(rèn)值中文頁面在這種默認(rèn)編碼下解碼必然出亂碼。解決手動指定編碼優(yōu)先用 apparent_encoding它是從頁面內(nèi)容字節(jié)特征推測出來的response.encoding response.apparent_encoding也可以在抓取前先查看響應(yīng)頭里的聲明print(response.headers.get(Content-Type))如果里面有 charsetutf-8 之類直接賦給 response.encoding 也行。注意一點亂碼問題出在解碼環(huán)節(jié)BeautifulSoup 本身沒有責(zé)任soup 解析的是已經(jīng)解碼的字符串。4.3 find_all 結(jié)果為空動態(tài)渲染的鍋現(xiàn)象代碼沒有任何報錯response.status_code 是 200但 soup.find_all(h1) 返回空列表或者只能拿到頁面框架里那幾個無關(guān)標(biāo)題。原因頁面內(nèi)容由 JavaScript 在瀏覽器端動態(tài)渲染requests 拿到的 HTML 只是空殼骨架真實標(biāo)題是腳本執(zhí)行后生成的Requests 不執(zhí)行 JavaScript自然抓不到。還有一種情況是標(biāo)題不在 h1 里而是用 div 套了大量樣式來模擬標(biāo)題的視覺效果。解決用瀏覽器打開目標(biāo)網(wǎng)頁右鍵「查看網(wǎng)頁源代碼」對比源代碼和渲染后頁面。如果源代碼里沒有你要的數(shù)據(jù)說明是動態(tài)渲染需要改用 Selenium 或 Playwright 這類瀏覽器自動化工具。如果數(shù)據(jù)在源代碼里但標(biāo)簽不是 h1就調(diào)整選擇器比如改成 soup.select(div.article-title)。4.4 抓幾次后被拒絕403 和 429 的限流現(xiàn)象腳本前兩次運行正常第三次開始連續(xù)返回 403或者返回 429 Too Many Requests。原因短時間高頻率的請求觸發(fā)了服務(wù)器的限流機制。即使你偽裝了 UA單位時間內(nèi)訪問次數(shù)太多服務(wù)器同樣會識別為腳本行為。解決控制抓取節(jié)奏在每次請求之間加延時常見的做法是 time.sleep(2) 到 time.sleep(5)。同時查看目標(biāo)網(wǎng)站的 robots.txt路徑一般寫在域名根目錄下比如 http://example.com/robots.txt里面會明確列出哪些路徑不允許爬取。做技術(shù)練習(xí)時盡量選擇允許采集的站點。4.5 腳本卡死不動timeout 缺失的后遺癥現(xiàn)象requests.get() 執(zhí)行后長時間沒有反應(yīng)腳本像掛了最后才拋出一個超時異常。原因沒有顯式設(shè)置 timeoutrequests 會一直等待服務(wù)器響應(yīng)。有些服務(wù)器接受連接后不返回數(shù)據(jù)連接就一直掛著腳本也就卡住了。解決所有請求都顯式設(shè)置 timeout推薦用元組形式區(qū)分連接和讀取超時response requests.get(url, timeout(3, 10))再把每個請求包進 try-except捕獲 requests.exceptions.RequestException批量任務(wù)里配合循環(huán)實現(xiàn)失敗重試。從那以后我每次寫爬蟲都強制檢查三個地方有沒有 timeout、有沒有異常捕獲、有沒有控制頻率缺一個都不上線。5. 讓爬蟲更健壯Session、Headers與批量抓取三步進階前面的腳本每次請求都是獨立的遇到需要登錄或維持會話的站點就會失效。requests 的 Session 對象能保持連接和 Cookie配合自定義 Headers 可以模擬一個穩(wěn)定的瀏覽器會話。下面是一個批量抓取多個頁面標(biāo)題的完整示例import requests from bs4 import BeautifulSoup import time session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }) urls [http://example.com, http://example.com/about, http://example.com/contact] all_titles [] for url in urls: try: response session.get(url, timeout(3, 10)) response.raise_for_status() response.encoding response.apparent_encoding soup BeautifulSoup(response.text, html.parser) titles [h.get_text(stripTrue) for h in soup.select(h1) if h.get_text(stripTrue)] all_titles.extend(titles) time.sleep(2) except requests.exceptions.RequestException as e: print(f抓取失敗{url}原因{e}) print(f共采集到 {len(all_titles)} 個標(biāo)題) print(list(dict.fromkeys(all_titles)))Session 會復(fù)用底層連接連續(xù)請求同一個站點時比每次新建連接更快也更不容易被識別為異常流量。headers.update() 把 UA 統(tǒng)一設(shè)置到會話級別后續(xù)每個請求自動攜帶。time.sleep(2) 是給服務(wù)器留出的喘息時間批量采集時這個間隔建議只增不減。驗證環(huán)節(jié)我一般做兩步第一步對比采集數(shù)量和頁面實際數(shù)量是否一致不一致說明選擇器漏了元素或頁面有動態(tài)內(nèi)容第二步對標(biāo)題列表去重檢查是否有重復(fù)抓取。跑完這套流程一個入門級的標(biāo)題采集器就算真正能交付了。希望幫到你。本文還有配套的精品資源點擊獲取