之前文中有人提到:想要一個簡單易用、能快速實現多線程爬蟲的方案,而且目標是小網站,基本可以確定對反爬蟲措施要求不高,這些就比較簡單了。
以往我肯定要考慮常見的編程語言中哪些適合爬蟲。Python、JavaScript(Node.js)、Go、Java這些應該都是候選。因為需要省事,所以可能更傾向于語法簡單、庫豐富的語言。
Python的話,有requests和Scrapy框架,還有aiohttp做異步。多線程的話,Python的threading模塊可能因為GIL限制效率,但如果是IO密集型任務,比如網絡請求,多線程還是有效的。不過異步可能更好,比如asyncio,但用戶明確說多線程,可能需要用concurrent.futures的ThreadPoolExecutor。Python的優點是庫多,代碼簡潔,適合小項目,省時間。
針對多線程爬取小網站的需求,推薦以下語言和方案,按優先級排序:
1. Python(首選)
-
優勢:
- 生態完善:
requests
/aiohttp
+BeautifulSoup
/parsel
組合開箱即用。 - 快速開發:代碼簡潔,20 行內即可實現多線程爬蟲。
- 線程池簡化:
concurrent.futures.ThreadPoolExecutor
兩行代碼實現線程池。 - Scrapy 框架:內置異步引擎,無需手動管理線程,適合規則化爬取。
- 生態完善:
-
代碼示例(極簡線程池):
import requests from concurrent.futures import ThreadPoolExecutordef fetch(url):return requests.get(url).texturls = ["http://example.com/page1", ...] with ThreadPoolExecutor(max_workers=10) as executor:results = list(executor.map(fetch, urls))
2. Go(高性能場景)
-
優勢:
- 原生并發:
goroutine
+channel
輕松管理百萬級并發。 - 編譯高效:二進制部署,資源占用低。
- 標準庫強大:
net/http
+goquery
滿足基礎需求。
- 原生并發:
-
適合場景:需要高頻請求或未來擴展至大規模爬蟲。
-
代碼片段:
package main import ("net/http"; "sync") func fetch(url string, wg *sync.WaitGroup) {defer wg.Done()http.Get(url) // 實際需處理響應 } func main() {var wg sync.WaitGroupurls := []string{"http://example.com/1", ...}for _, u := range urls {wg.Add(1)go fetch(u, &wg)}wg.Wait() }
3. Node.js(適合異步I/O)
- 優勢:
- 事件循環:非阻塞IO天然適合高并發請求。
- 語法靈活:
axios
+cheerio
類似前端開發體驗。
- 注意點:回調需用
Promise.all
或worker_threads
實現真并行。
避坑建議:
- 頻率控制:即使小網站也需添加延遲(如
time.sleep(1)
),避免觸發封禁。 - 錯誤處理:務必捕獲異常,網絡爬蟲需處理超時、重試邏輯。
- 合規性:檢查
robots.txt
,避免敏感數據抓取。
總結:
- 追求 極速開發 → 選Python(腳本級開發速度)。
- 需要 高性能/資源控制 → 選Go(協程開銷極低)。
- 前端背景想復用JS技能 → Node.js(但注意線程模型差異)。
現在我以 Python 為例,手把手教你寫一個多線程爬蟲,10分鐘即可跑通,適合新手快速上手。
目標:多線程爬取豆瓣電影Top250的標題和評分
網址:
https://movie.douban.com/top250
第一步:安裝依賴
pip install requests parsel concurrent-log-handler # 核心庫:請求 + 解析 + 線程池
第二步:完整代碼
import requests
from parsel import Selector
from concurrent.futures import ThreadPoolExecutor
import time# 偽裝瀏覽器 + 全局Headers
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
}def scrape_page(url):"""爬取單個頁面"""try:response = requests.get(url, headers=HEADERS, timeout=5)response.raise_for_status() # 自動識別HTTP錯誤(如404)return response.textexcept Exception as e:print(f"請求失敗: {url} | 錯誤: {e}")return Nonedef parse_data(html):"""解析頁面數據"""selector = Selector(html)movies = []for item in selector.css(".item"):title = item.css(".title::text").get()rating = item.css(".rating_num::text").get()movies.append({"title": title, "rating": rating})return moviesdef worker(page):"""線程任務函數:處理單頁"""url = f"https://movie.douban.com/top250?start={(page-1)*25}"html = scrape_page(url)if html:movies = parse_data(html)print(f"第{page}頁爬取完成,共{len(movies)}部電影")return moviesreturn []def main():# 創建線程池(限制為5線程,避免封IP)with ThreadPoolExecutor(max_workers=5) as executor:# 提交25頁任務(豆瓣Top250共10頁,這里測試用3頁)futures = [executor.submit(worker, page) for page in range(1, 4)]# 等待所有任務完成并合并結果all_movies = []for future in futures:all_movies.extend(future.result())time.sleep(1) # 每頁間隔1秒,降低被封風險# 打印結果print("\n===== 爬取結果 =====")for movie in all_movies:print(f"《{movie['title']}》 評分:{movie['rating']}")if __name__ == "__main__":main()
第三步:逐行解釋
- 偽裝瀏覽器:通過
HEADERS
模擬Chrome瀏覽器,繞過基礎反爬。 - 線程池控制:
ThreadPoolExecutor
自動管理線程,max_workers=5
限制并發數。 - 任務分發:通過
executor.submit
提交頁碼(1~3頁)到線程池。 - 間隔防封:每處理完一頁后強制等待1秒(
time.sleep(1)
)。 - 異常處理:
scrape_page
中捕獲超時、HTTP錯誤等,避免程序崩潰。
運行效果
第1頁爬取完成,共25部電影
第2頁爬取完成,共25部電影
第3頁爬取完成,共25部電影===== 爬取結果 =====
《肖申克的救贖》 評分:9.7
《霸王別姬》 評分:9.6
《阿甘正傳》 評分:9.5
...
(共75條數據)
升級方向(根據需求擴展)
- 代理IP:在
requests.get
中添加proxies
參數應對封IP。 - 異步加速:改用
aiohttp
+asyncio
實現更高并發。 - 存儲數據:添加
with open('movies.json', 'w')
保存結果。 - 動態頁面:若遇到JavaScript渲染,換用
selenium
或playwright
。
為什么選Python?
- 代碼量少:25行核心邏輯完成多線程爬蟲。
- 調試方便:直接打印中間結果,無需編譯。
- 生態豐富:遇到驗證碼、登錄等復雜場景有現成庫(如
pytesseract
)。
最后適合不適合就得結合自己的項目,嘗試跑起來看看吧!遇到問題隨時調整線程數和間隔時間即可~