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