本文展示使用 Python gevent 協程庫實現高併發爬蟲,批量下載「懶人圖庫」網站的 JavaScript 特效模板壓縮包。包含完整程式碼、關鍵技術點解析與反爬蟲應對策略。
gevent(協程)、BeautifulSoup4(HTML 解析)、urllib(網路請求)。gevent.monkey.patch_socket() 猴子補丁將標準庫阻塞 I/O 轉為協程非阻塞,單進程即可達成高併發。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import urllib, os, sys
import gevent, re
from gevent import monkey
from bs4 import BeautifulSoup
gevent.monkey.patch_socket() # 關鍵:必須在 import 其他網路模組前執行
HTTP_URL = 'https://www.lanrentuku.com%s'
DOWNLOAD_URL = 'https://www.lanrentuku.com/js/d%szip'
ID_REGEX = re.compile(r'\d{1,}.+')
def create_dir(filename):
"""建立下載目錄"""
base = os.path.join(os.getcwd(), u'JS代碼模板')
if not os.path.exists(base):
os.makedirs(base)
return os.path.join(base, filename)
def schedule(a, b, c):
"""下載進度顯示"""
per = 100.0 * a * b / c
if per > 100: per = 100
sys.stdout.write('\r%.1f%%' % per)
sys.stdout.flush()
def get_url_list(page_url):
"""解析列表頁,回傳 {詳情頁URL: 標題} 字典"""
url_dict = {}
try:
html = urllib.urlopen(page_url).read()
soup = BeautifulSoup(html, 'html.parser')
div = soup.find('div', class_='list-pngjs')
if not div: return url_dict
for a_tag in div.find_all('a'):
href = a_tag.get('href')
title = a_tag.get_text(strip=True)
if href and title:
url_dict[HTTP_URL % href] = title
except Exception as e:
print(f"解析列表頁失敗 {page_url}: {e}")
return url_dict
def download(detail_url, title):
"""下載單個 ZIP 檔案"""
try:
m = ID_REGEX.search(detail_url)
if not m: return
zip_url = DOWNLOAD_URL % m.group(0)
filename = title + zip_url[-4:] # 保留 .zip
filepath = create_dir(filename)
urllib.urlretrieve(zip_url, filepath, schedule)
print(f"\n完成: {filename}")
except Exception as e:
print(f"\n下載失敗 {detail_url}: {e}")
def main():
# 生成 1-48 頁列表頁 URL
page_urls = ['https://www.lanrentuku.com/js/p%s.html' % i for i in range(1, 49)]
jobs = []
for page_url in page_urls:
items = get_url_list(page_url)
for detail_url, title in items.items():
jobs.append(gevent.spawn(download, detail_url, title))
print(f"共 {len(jobs)} 個下載任務,開始併發下載...")
gevent.joinall(jobs)
print("\n所有任務完成。")
if __name__ == '__main__':
main()
from gevent import monkey
monkey.patch_socket() # 或 monkey.patch_all()
必須在 import urllib、import requests 等網路模組之前執行,將底層 Socket 操作替換為非阻塞版本,使同步程式碼自動具備異步能力。
預設 gevent.joinall 會同時啟動所有綠色執行緒,可能導致連線數過多被封 IP。生產環境建議使用 gevent.pool.Pool 限制併發:
pool = gevent.pool.Pool(20) # 限制同時 20 個協程
jobs = [pool.spawn(download, url, title) for url, title in items.items()]
gevent.joinall(jobs)
User-Agent 模擬瀏覽器。gevent.sleep(random.uniform(0.5, 1.5))。多協程同時寫入 stdout 會導致進度條顯示混亂。解決方案:
threading.Lock 保護 sys.stdout.write。tqdm 等支援多執行緒的進度條庫。urllib.urlopen → urllib.request.urlopenurllib.urlretrieve → urllib.request.urlretrievexrange → rangeprint 語句 → print() 函式BeautifulSoup(html) → BeautifulSoup(html, 'html.parser') 指定解析器